diff --git a/.github/workflows/pr.test.yml b/.github/workflows/pr.test.yml index b21190d5..6fa231e5 100644 --- a/.github/workflows/pr.test.yml +++ b/.github/workflows/pr.test.yml @@ -6,6 +6,6 @@ on: jobs: test: - uses: Multiverse/Multiverse-Core/.github/workflows/generic.test.yml@main + uses: Multiverse/Multiverse-Core/.github/workflows/generic.test.yml@MV5 # todo: Change back to main before release with: plugin_name: multiverse-inventories diff --git a/README.md b/README.md index 4ff8241e..9a730db7 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,7 @@

-Multiverse Logo +Multiverse Logo

-# Multiverse Inventories - [![Modrinth](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/available/modrinth_vector.svg)](https://modrinth.com/plugin/multiverse-inventories) [![Hangar](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/available/hangar_vector.svg)](https://hangar.papermc.io/Multiverse/Multiverse-Inventories) [![Bukkit](https://raw.githubusercontent.com/intergrav/devins-badges/refs/heads/v3/assets/cozy/available/bukkit_vector.svg)](https://dev.bukkit.org/projects/multiverse-inventories) @@ -17,11 +15,11 @@ [Multiverse Inventories](https://dev.bukkit.org/projects/multiverse-inventories) is an add-on Plugin for [Multiverse core](https://dev.bukkit.org/projects/multiverse-core) that lets players have separate inventories **per world!** This makes it possible to create multi gamemode servers without Velocity or Bungee! -Now it's time to create your very own server with Multiverse Inventories, do check out our [Wiki](https://github.com/Multiverse/Multiverse-Core/wiki/Home-(Inventories)) and [Usage Guide](https://github.com/Multiverse/Multiverse-Core/wiki/Basics-(Inventories)) to get started. Feel free to hop onto our [Discord](https://discord.gg/NZtfKky) if you have any questions or just want to have a chat with us! +Now it's time to create your very own server with Multiverse Inventories, do check out our [Wiki](https://mvplugins.org) and [Usage Guide](https://mvplugins.org/inventories/fundamentals/basic-usage/) to get started. Feel free to hop onto our [Discord](https://discord.gg/NZtfKky) if you have any questions or just want to have a chat with us! ## Our other amazing sub-modules: -With just [Multiverse Core](https://github.com/multiverse/multiverse) and any of the below plugins, you can access all of these other related features in the Multiverse ecosystem. +With just [Multiverse Core](https://github.com/multiverse/multiverse-core) and any of the below plugins, you can access all of these other related features in the Multiverse ecosystem. * [Multiverse-NetherPortals](https://github.com/Multiverse/Multiverse-NetherPortals) -> Have separate nether and end worlds for each of your overworlds! * [Multiverse-Portals](https://github.com/Multiverse/Multiverse-Portals) -> Make custom portals to go to any destination! @@ -38,7 +36,7 @@ Simply build the source with Gradle: **Want to help improve Multiverse Inventories?** There are several ways you can support and contribute to the project. * Take a look at our "Bug: Unconfirmed" issues, where you can find issues that need extra testing and investigation. * Want others to love Multiverse too? You can join the [Multiverse Discord community](https://discord.gg/NZtfKky) and help others with issues and setup! -* A Multiverse guru? You can update our [Wiki](https://github.com/Multiverse/Multiverse-Core/wiki) with your latest tips, tricks and guides! The wiki open for all to edit and improve. +* A Multiverse guru? You can update our [Wiki](https://github.com/Multiverse/multiverse-web) with your latest tips, tricks and guides! The wiki open for all to edit and improve. * Love coding? You could look at ["State: Open to PR"](https://github.com/Multiverse/Multiverse-Inventories/labels/State%3A%20Open%20to%20PR) and ["Resolution: Accepted"](https://github.com/Multiverse/Multiverse-Inventories/labels/Resolution%3A%20Accepted) issues. We're always happy to receive bug fixes and feature additions as [pull requests](https://www.freecodecamp.org/news/how-to-make-your-first-pull-request-on-github-3/). * If you'd like to make a financial contribution to the project, do consider joining our [Patreon](https://www.patreon.com/dumptruckman) or make a one-time donation [here](https://paypal.me/dumptruckman)! diff --git a/build.gradle b/build.gradle index bb0a9882..127b7194 100644 --- a/build.gradle +++ b/build.gradle @@ -1,106 +1,98 @@ plugins { - id 'java-library' - id 'maven-publish' id 'checkstyle' - id 'com.github.johnrengelman.shadow' version '7.1.2' + id 'org.mvplugins.multiverse-plugin' version '1.1.0' } -version = System.getenv('GITHUB_VERSION') ?: 'local' -group = 'com.onarandombox.multiverseinventories' +group = 'org.mvplugins.multiverse.inventories' description = 'Multiverse-Inventories' -java.sourceCompatibility = JavaVersion.VERSION_11 - repositories { - mavenLocal() - mavenCentral() - maven { - name = 'onarandombox' - url = uri('https://repo.onarandombox.com/content/groups/public') + name = 'codemc' + url = uri('https://repo.codemc.org/repository/maven-releases') } maven { - name ='papermc' - url = uri('https://papermc.io/repo/repository/maven-public/') + name = 'benwoo1110' + url = uri('https://repo.c0ding.party/multiverse-beta') } +} - maven { - name = 'jitpack.io' - url = uri('https://jitpack.io/') - } +configure(apiDependencies) { + serverApiVersion = '1.21.3-R0.1-SNAPSHOT' + mockBukkitServerApiVersion = '1.21' + mockBukkitVersion = '4.31.1' } dependencies { - // Spigot - implementation('org.bukkit:bukkit:1.14.4-R0.1-SNAPSHOT') { - exclude group: 'junit', module: 'junit' - } + // Server API + // TODO make our custom plugin target paper instead of spigot + externalPlugin 'io.papermc.paper:paper-api:1.18.2-R0.1-SNAPSHOT' // Core - implementation('com.onarandombox.multiversecore:Multiverse-Core:4.2.2') { - exclude group: 'me.main__.util', module: 'SerializationConfig' - } + // TODO update to correct version once we have it published + externalPlugin 'org.mvplugins.multiverse.core:multiverse-core:5.0.0-SNAPSHOT' // Config - api 'com.dumptruckman.minecraft:JsonConfiguration:1.1' - api ('com.googlecode.json-simple:json-simple:1.1.1') { - exclude group: 'junit', module: 'junit' - } + shadowed 'com.dumptruckman.minecraft:JsonConfiguration:1.2-SNAPSHOT' + shadowed 'net.minidev:json-smart:2.5.1' // Utils - api 'io.papermc:paperlib:1.0.7' - api('com.dumptruckman.minecraft:Logging:1.1.1') { + shadowed('com.dumptruckman.minecraft:Logging:1.1.1') { exclude group: 'junit', module: 'junit' } + // Caching + shadowed("com.github.ben-manes.caffeine:caffeine:3.2.0") + + // Luckperms for group context + compileOnly 'net.luckperms:api:5.4' + // Other plugins for import - implementation('uk.co:MultiInv:3.0.6') { + compileOnly('uk.co:MultiInv:3.0.6') { exclude group: '*', module: '*' } - implementation('me.drayshak:WorldInventories:1.0.2') { + compileOnly('me.drayshak:WorldInventories:1.0.2') { exclude group: '*', module: '*' } - - // Legacy Multiverse-Adventure - implementation('com.onarandombox.multiverseadventure:Multiverse-Adventure:2.5.0-SNAPSHOT') { + // perworldinventory is weird and has snakeyaml included in the jar, so we can only use compileOnly for build to work properly + compileOnly('me.ebonjaeger:perworldinventory-kt:2.3.2') { exclude group: '*', module: '*' } - // Tests - testImplementation 'com.github.MilkBowl:VaultAPI:1.7.1' - testImplementation 'junit:junit:4.13.2' - testImplementation 'org.mockito:mockito-core:3.11.2' -} - - -java { - withSourcesJar() - withJavadocJar() -} - -tasks.withType(JavaCompile).configureEach { - options.encoding = 'UTF-8' + // hk2 for annotation processing only + compileOnly('org.glassfish.hk2:hk2-api:3.0.3') { + exclude group: '*', module: '*' + } + annotationProcessor 'org.glassfish.hk2:hk2-metadata-generator:3.0.3' + testAnnotationProcessor 'org.glassfish.hk2:hk2-metadata-generator:3.0.3' } -tasks.withType(Javadoc).configureEach { - options.encoding = 'UTF-8' +shadowJar { + relocate 'com.dumptruckman.minecraft.util.Logging', 'org.mvplugins.multiverse.inventories.utils.InvLogging' + relocate 'com.dumptruckman.minecraft.util.DebugLog', 'org.mvplugins.multiverse.inventories.utils.DebugFileLogger' + relocate 'com.dumptruckman.bukkit.configuration', 'org.mvplugins.multiverse.inventories.utils.configuration' + relocate 'net.minidev', 'org.mvplugins.multiverse.inventories.utils.minidev' + relocate 'com.github.benmanes', 'org.mvplugins.multiverse.inventories.utils.benmanes' + relocate 'com.google.errorprone', 'org.mvplugins.multiverse.inventories.utils.errorprone' + + dependencies { + exclude(dependency { + it.moduleGroup == 'org.ow2.asm' + }) + exclude(dependency { + it.moduleGroup == 'org.jetbrains' + }) + } } - -configurations { - [apiElements, runtimeElements].each { - it.outgoing.artifacts.removeIf { it.buildDependencies.getDependencies(null).contains(jar) } - it.outgoing.artifact(shadowJar) - } +checkstyle { + toolVersion = '6.1.1' + configFile file('config/mv_checks.xml') + ignoreFailures = true } publishing { - publications { - maven(MavenPublication) { - from components.java - } - } repositories { maven { name = "GitHubPackages" @@ -110,57 +102,12 @@ publishing { password = System.getenv("GITHUB_TOKEN") } } + maven { - name = "multiverse" - def releasesRepoUrl = "https://repo.onarandombox.com/multiverse-releases" - def snapshotsRepoUrl = "https://repo.onarandombox.com/multiverse-snapshots" - url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl + // todo: remove before mv5 release + name = "multiverseBeta" + url = "https://repo.c0ding.party/multiverse-beta" credentials(PasswordCredentials) } } } - - -processResources { - def props = [version: "${project.version}"] - inputs.properties props - filteringCharset 'UTF-8' - filesMatching('plugin.yml') { - expand props - } - - // This task should never be skipped. The tests depend on this having been run but we want the new version number - // that is created after tests are run and before we run again to publish. - outputs.upToDateWhen { false } -} - - -checkstyle { - toolVersion = '6.1.1' - configFile file('config/mv_checks.xml') - ignoreFailures = true -} - - -javadoc { - source = sourceSets.main.allJava - classpath = configurations.compileClasspath -} - - -project.configurations.api.canBeResolved = true - -shadowJar { - relocate 'com.dumptruckman.minecraft.util.Logging', 'com.onarandombox.multiverseinventories.utils.InvLogging' - relocate 'com.dumptruckman.minecraft.util.DebugLog', 'com.onarandombox.multiverseinventories.utils.DebugFileLogger' - relocate 'com.dumptruckman.bukkit.configuration', 'com.onarandombox.multiverseinventories.utils.configuration' - relocate 'io.papermc.lib', 'com.onarandombox.multiverseinventories.utils.paperlib' - relocate 'net.minidev.json', 'com.onarandombox.multiverseinventories.utils.json' - - configurations = [project.configurations.api] - - archiveFileName = "$baseName-$version.$extension" -} - -build.dependsOn shadowJar -jar.enabled = false diff --git a/config/multiverse-banner.png b/config/multiverse-banner.png new file mode 100644 index 00000000..75877420 Binary files /dev/null and b/config/multiverse-banner.png differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f398c33c..5c40527d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle b/settings.gradle index 62b418fb..4a328fcc 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,4 +2,13 @@ * This file was generated by the Gradle 'init' task. */ +pluginManagement { + repositories { + gradlePluginPortal() + maven { + url = uri('https://repo.onarandombox.com/multiverse-releases') + } + } +} + rootProject.name = 'multiverse-inventories' diff --git a/src/main/java/com/onarandombox/multiverseinventories/AdventureListener.java b/src/main/java/com/onarandombox/multiverseinventories/AdventureListener.java deleted file mode 100644 index c17b4524..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/AdventureListener.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.onarandombox.multiverseinventories; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.MultiverseAdventure.event.MVAResetFinishedEvent; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainer; -import org.bukkit.OfflinePlayer; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; - -/** - * Listener for Multiverse-Adventure events. - */ -public class AdventureListener implements Listener { - - private MultiverseInventories inventories; - - public AdventureListener(MultiverseInventories inventories) { - this.inventories = inventories; - } - - /** - * @param event The Multiverse-Adventure event to handle when a world has finished resetting. - */ - @EventHandler - public void worldReset(MVAResetFinishedEvent event) { - ProfileContainer container = inventories.getWorldProfileContainerStore().getContainer(event.getWorld()); - for (OfflinePlayer player : inventories.getServer().getOfflinePlayers()) { - container.removeAllPlayerData(player); - } - Logging.info("Removed all inventories for Multiverse-Adventure world."); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/CoreDebugListener.java b/src/main/java/com/onarandombox/multiverseinventories/CoreDebugListener.java deleted file mode 100644 index cd9685d8..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/CoreDebugListener.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.onarandombox.multiverseinventories; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.MultiverseCore.event.MVDebugModeEvent; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; - -public class CoreDebugListener implements Listener { - - CoreDebugListener(MultiverseInventories plugin) { - plugin.getServer().getPluginManager().registerEvents(this, plugin); - } - - @EventHandler - public void onDebugModeChange(MVDebugModeEvent event) { - Logging.setDebugLevel(event.getLevel()); - } -} diff --git a/src/main/java/com/onarandombox/multiverseinventories/DataStrings.java b/src/main/java/com/onarandombox/multiverseinventories/DataStrings.java deleted file mode 100644 index baf6bfad..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/DataStrings.java +++ /dev/null @@ -1,302 +0,0 @@ -package com.onarandombox.multiverseinventories; - -import com.dumptruckman.minecraft.util.Logging; -import net.minidev.json.JSONArray; -import net.minidev.json.JSONObject; -import net.minidev.json.parser.JSONParser; -import net.minidev.json.parser.ParseException; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; - -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -/** - * This class handles the formatting of strings for data i/o. - */ -public class DataStrings { - - /** - * Delimiter to separate a key and it's value. - */ - public static final String VALUE_DELIMITER = ":"; - /** - * Player stats identifier. - */ - public static final String PLAYER_STATS = "stats"; - /** - * Player inventory contents identifier. - */ - public static final String PLAYER_INVENTORY_CONTENTS = "inventoryContents"; - /** - * Player armor contents identifier. - */ - public static final String PLAYER_ARMOR_CONTENTS = "armorContents"; - /** - * Player off hand item identifier. - */ - public static final String PLAYER_OFF_HAND_ITEM = "offHandItem"; - /** - * Ender chest inventory contents identifier. - */ - public static final String ENDER_CHEST_CONTENTS = "enderChestContents"; - /** - * Player bed spawn location identifier. - */ - public static final String PLAYER_BED_SPAWN_LOCATION = "bedSpawnLocation"; - /** - * Player last location identifier. - */ - public static final String PLAYER_LAST_LOCATION = "lastLocation"; - /** - * Player last world identifier. - */ - public static final String PLAYER_LAST_WORLD = "lastWorld"; - /** - * Player should load identifier. - */ - public static final String PLAYER_SHOULD_LOAD = "shouldLoad"; - /** - * Player last known name identifier. - */ - public static final String PLAYER_LAST_KNOWN_NAME = "lastKnownName"; - /** - * Player profile type identifier. - */ - public static final String PLAYER_PROFILE_TYPE = "profileType"; - /** - * Player health identifier. - */ - public static final String PLAYER_HEALTH = "hp"; - /** - * Player experience identifier. - */ - public static final String PLAYER_EXPERIENCE = "xp"; - /** - * Player total experience identifier. - */ - public static final String PLAYER_TOTAL_EXPERIENCE = "txp"; - /** - * Player experience level identifier. - */ - public static final String PLAYER_LEVEL = "el"; - /** - * Player food level identifier. - */ - public static final String PLAYER_FOOD_LEVEL = "fl"; - /** - * Player exhaustion identifier. - */ - public static final String PLAYER_EXHAUSTION = "ex"; - /** - * Player saturation identifier. - */ - public static final String PLAYER_SATURATION = "sa"; - /** - * Player fall distance identifier. - */ - public static final String PLAYER_FALL_DISTANCE = "fd"; - /** - * Player fire ticks identifier. - */ - public static final String PLAYER_FIRE_TICKS = "ft"; - /** - * Player remaining air identifier. - */ - public static final String PLAYER_REMAINING_AIR = "ra"; - /** - * Player max air identifier. - */ - public static final String PLAYER_MAX_AIR = "ma"; - /** - * Location x identifier. - */ - public static final String LOCATION_X = "x"; - /** - * Location y identifier. - */ - public static final String LOCATION_Y = "y"; - /** - * Location z identifier. - */ - public static final String LOCATION_Z = "z"; - /** - * Location world identifier. - */ - public static final String LOCATION_WORLD = "wo"; - /** - * Location pitch identifier. - */ - public static final String LOCATION_PITCH = "pi"; - /** - * Location yaw identifier. - */ - public static final String LOCATION_YAW = "ya"; - /** - * Potion type identifier. - */ - public static final String POTION_TYPE = "pt"; - /** - * Potion duration identifier. - */ - public static final String POTION_DURATION = "pd"; - /** - * Potion amplifier identifier. - */ - public static final String POTION_AMPLIFIER = "pa"; - - private DataStrings() { - throw new AssertionError(); - } - - /** - * @param locString Parses this string and creates Location. - * @return New location object or null if no location could be created. - * @deprecated Locations do not use special handling because they are - * {@link org.bukkit.configuration.serialization.ConfigurationSerializable}. - */ - @Deprecated - public static Location parseLocation(String locString) { - if (locString.isEmpty()) { - return null; - } - JSONObject jsonLoc; - try { - jsonLoc = (JSONObject) JSON_PARSER.parse(locString); - } catch (ParseException e) { - Logging.warning("Could not parse location! " + e.getMessage()); - return null; - } catch (ClassCastException e) { - Logging.warning("Could not parse location! " + e.getMessage()); - return null; - } - return parseLocMap(jsonLoc); - } - - /** - * @deprecated Locations do not use special handling because they are - * {@link org.bukkit.configuration.serialization.ConfigurationSerializable}. - */ - @Deprecated - public static Location parseLocation(Map locMap) { - return parseLocMap(locMap); - } - - @Deprecated - private static Location parseLocMap(Map locMap) { - World world = null; - double x = 0; - double y = 0; - double z = 0; - float pitch = 0; - float yaw = 0; - if (locMap.containsKey(LOCATION_WORLD)) { - world = Bukkit.getWorld(locMap.get(LOCATION_WORLD).toString()); - } - if (locMap.containsKey(LOCATION_X)) { - Object value = locMap.get(LOCATION_X); - if (value instanceof Number) { - x = ((Number) value).doubleValue(); - } - } - if (locMap.containsKey(LOCATION_Y)) { - Object value = locMap.get(LOCATION_Y); - if (value instanceof Number) { - y = ((Number) value).doubleValue(); - } - } - if (locMap.containsKey(LOCATION_Z)) { - Object value = locMap.get(LOCATION_Z); - if (value instanceof Number) { - z = ((Number) value).doubleValue(); - } - } - if (locMap.containsKey(LOCATION_PITCH)) { - Object value = locMap.get(LOCATION_PITCH); - if (value instanceof Number) { - pitch = ((Number) value).floatValue(); - } - } - if (locMap.containsKey(LOCATION_YAW)) { - Object value = locMap.get(LOCATION_YAW); - if (value instanceof Number) { - yaw = ((Number) value).floatValue(); - } - } - if (world == null) { - return null; - } - return new Location(world, x, y, z, yaw, pitch); - } - - /** - * @param potionsString A player's potion effects in string form to be parsed into - * {@link java.util.Collection}<{@link org.bukkit.potion.PotionEffect}>. - * @return a collection of potion effects parsed from potionsString. - * @deprecated PotionEffect do not use special handling because they are - * {@link org.bukkit.configuration.serialization.ConfigurationSerializable}. - */ - @Deprecated - public static PotionEffect[] parsePotionEffects(String potionsString) { - List potionEffectList = new LinkedList(); - if (potionsString.isEmpty()) { - return potionEffectList.toArray(new PotionEffect[potionEffectList.size()]); - } - JSONArray jsonPotions; - try { - jsonPotions = (JSONArray) JSON_PARSER.parse(potionsString); - } catch (ParseException e) { - Logging.warning("Could not parse potions! " + e.getMessage()); - return potionEffectList.toArray(new PotionEffect[potionEffectList.size()]); - } catch (ClassCastException e) { - Logging.warning("Could not parse potions! " + e.getMessage()); - return potionEffectList.toArray(new PotionEffect[potionEffectList.size()]); - } - for (Object obj : jsonPotions) { - if (obj instanceof JSONObject) { - JSONObject jsonPotion = (JSONObject) obj; - int type = -1; - int duration = -1; - int amplifier = -1; - if (jsonPotion.containsKey(POTION_TYPE)) { - Object value = jsonPotion.get(POTION_TYPE); - if (value instanceof Number) { - type = ((Number) value).intValue(); - } - } - if (jsonPotion.containsKey(POTION_AMPLIFIER)) { - Object value = jsonPotion.get(POTION_AMPLIFIER); - if (value instanceof Number) { - amplifier = ((Number) value).intValue(); - } - } - if (jsonPotion.containsKey(POTION_DURATION)) { - Object value = jsonPotion.get(POTION_DURATION); - if (value instanceof Number) { - duration = ((Number) value).intValue(); - } - } - if (type == -1 || duration == -1 || amplifier == -1) { - Logging.fine("Could not parse potion effect string: " + obj); - } else { - PotionEffectType pType = PotionEffectType.getById(type); - if (pType == null) { - Logging.warning("Could not parse potion effect type: " + type); - continue; - } - potionEffectList.add(new PotionEffect(pType, duration, amplifier)); - } - } else { - Logging.warning("Could not parse potion effect: " + obj); - } - } - return potionEffectList.toArray(new PotionEffect[potionEffectList.size()]); - } - - private static final JSONParser JSON_PARSER = new JSONParser(JSONParser.USE_INTEGER_STORAGE); -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/DefaultMessageProvider.java b/src/main/java/com/onarandombox/multiverseinventories/DefaultMessageProvider.java deleted file mode 100644 index 71e6b397..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/DefaultMessageProvider.java +++ /dev/null @@ -1,248 +0,0 @@ -package com.onarandombox.multiverseinventories; - -import com.onarandombox.multiverseinventories.locale.LazyLocaleMessageProvider; -import com.onarandombox.multiverseinventories.locale.LocalizationLoadingException; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.locale.NoSuchLocalizationException; -import com.onarandombox.multiverseinventories.util.Font; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.plugin.java.JavaPlugin; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Set; - -/** - * Implementation of MessageProvider. - */ -class DefaultMessageProvider implements LazyLocaleMessageProvider { - - /** - * Name of localization folder. - */ - public static final String LOCALIZATION_FOLDER_NAME = "localization"; - - private final HashMap>> messages; - private final JavaPlugin plugin; - - private Locale locale = DEFAULT_LOCALE; - - public DefaultMessageProvider(JavaPlugin plugin) { - this.plugin = plugin; - messages = new HashMap>>(); - - try { - loadLocale(locale); - } catch (NoSuchLocalizationException e) { - // let's take the defaults from the enum! - } - } - - /** - * Tries to load the locale. - * - * @param locale Locale to try to load. - * @throws com.onarandombox.multiverseinventories.locale.LocalizationLoadingException if the Locale could not be loaded. - */ - public void maybeLoadLocale(Locale locale) throws LocalizationLoadingException { - if (!isLocaleLoaded(locale)) { - try { - loadLocale(locale); - } catch (NoSuchLocalizationException e) { - throw e; - } - } - if (!isLocaleLoaded(locale)) - throw new LocalizationLoadingException("Couldn't load the localization: " - + locale.toString(), locale); - } - - /** - * Formats a list of strings by passing each through {@link #format(String, Object...)}. - * - * @param strings List of strings to format. - * @param args Arguments to pass in via %n. - * @return List of formatted strings. - */ - public List format(List strings, Object... args) { - List formattedStrings = new ArrayList(); - for (String string : strings) { - formattedStrings.add(format(string, args)); - } - return formattedStrings; - } - - /** - * Formats a string by replacing ampersand with the Section symbol and %n with the corresponding args - * object where n = argument index + 1. - * - * @param string String to format. - * @param args Arguments to pass in via %n. - * @return The formatted string. - */ - public String format(String string, Object... args) { - // Replaces & with the Section character - string = string.replaceAll("(&([a-fA-FkK0-9]))", Font.SECTION_SYMBOL + "$2"); - // If there are arguments, %n notations in the message will be - // replaced - if (args != null) { - for (int j = 0; j < args.length; j++) { - string = string.replace("%" + (j + 1), args[j].toString()); - } - } - return string; - } - - /** - * {@inheritDoc} - */ - @Override - public void loadLocale(Locale l) throws NoSuchLocalizationException { - messages.remove(l); - - InputStream resstream = null; - InputStream filestream = null; - - try { - filestream = new FileInputStream(new File(plugin.getDataFolder(), l.getLanguage() + ".yml")); - } catch (FileNotFoundException e) { - } - - try { - resstream = plugin.getResource(new StringBuilder(LOCALIZATION_FOLDER_NAME).append("/") - .append(l.getLanguage()).append(".yml").toString()); - } catch (Exception e) { - } - - if ((resstream == null) && (filestream == null)) - throw new NoSuchLocalizationException(l); - - messages.put(l, new HashMap>(Message.values().length)); - - FileConfiguration resconfig = (resstream == null) ? null : YamlConfiguration.loadConfiguration(new InputStreamReader(resstream)); - FileConfiguration fileconfig = (filestream == null) ? null : YamlConfiguration.loadConfiguration(new InputStreamReader(filestream)); - for (Message m : Message.values()) { - List values = m.getDefault(); - - if (resconfig != null) { - if (resconfig.isList(m.toString())) { - values = resconfig.getStringList(m.toString()); - } else { - values.add(resconfig.getString(m.toString(), values.get(0))); - } - } - if (fileconfig != null) { - if (fileconfig.isList(m.toString())) { - values = fileconfig.getStringList(m.toString()); - } else { - values.add(fileconfig.getString(m.toString(), values.get(0))); - } - } - - messages.get(l).put(m, values); - } - } - - /** - * {@inheritDoc} - */ - @Override - public Set getLoadedLocales() { - return messages.keySet(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isLocaleLoaded(Locale l) { - return messages.containsKey(l); - } - - /** - * {@inheritDoc} - */ - @Override - public String getMessage(Message key, Object... args) { - if (!isLocaleLoaded(locale)) { - return format(key.getDefault().get(0), args); - } else - return format(messages.get(locale).get(key).get(0), args); - } - - /** - * {@inheritDoc} - */ - @Override - public String getMessage(Message key, Locale locale, Object... args) { - try { - maybeLoadLocale(locale); - } catch (LocalizationLoadingException e) { - e.printStackTrace(); - return getMessage(key, args); - } - return format(messages.get(locale).get(key).get(0), args); - } - - /** - * {@inheritDoc} - */ - @Override - public List getMessages(Message key, Object... args) { - List result; - if (!isLocaleLoaded(locale)) { - result = format(key.getDefault(), args); - } else { - result = format(this.messages.get(locale).get(key), args); - } - return result; - } - - /** - * {@inheritDoc} - */ - @Override - public List getMessages(Message key, Locale locale, Object... args) { - try { - maybeLoadLocale(locale); - } catch (LocalizationLoadingException e) { - e.printStackTrace(); - return format(getMessages(key), args); - } - return format(messages.get(locale).get(key), args); - } - - /** - * {@inheritDoc} - */ - @Override - public Locale getLocale() { - return locale; - } - - /** - * {@inheritDoc} - */ - @Override - public void setLocale(Locale locale) { - if (locale == null) - throw new IllegalArgumentException("Can't set locale to null!"); - try { - maybeLoadLocale(locale); - } catch (LocalizationLoadingException e) { - if (!locale.equals(DEFAULT_LOCALE)) - throw new IllegalArgumentException("Error while trying to load localization for the given Locale!", e); - } - - this.locale = locale; - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/DefaultMessager.java b/src/main/java/com/onarandombox/multiverseinventories/DefaultMessager.java deleted file mode 100644 index 57008559..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/DefaultMessager.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.onarandombox.multiverseinventories; - -import com.onarandombox.multiverseinventories.locale.MessageProvider; -import com.onarandombox.multiverseinventories.locale.Messager; -import com.onarandombox.multiverseinventories.locale.Message; -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; -import org.bukkit.plugin.java.JavaPlugin; - -import java.util.List; - -/** - * Implementation of a Messager and MessageProvider using DefaultMessageProvider to implement the latter. - */ -final class DefaultMessager extends DefaultMessageProvider implements Messager, MessageProvider { - - public DefaultMessager(JavaPlugin plugin) { - super(plugin); - } - - private void send(Message message, String prefix, CommandSender sender, Object... args) { - List messages = this.getMessages(message, args); - if (!messages.isEmpty()) { - messages.set(0, prefix + " " + messages.get(0)); - sendMessages(sender, messages); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void bad(Message message, CommandSender sender, Object... args) { - send(message, ChatColor.RED.toString() + this.getMessage(Message.GENERIC_ERROR), sender, args); - } - - /** - * {@inheritDoc} - */ - @Override - public void normal(Message message, CommandSender sender, Object... args) { - send(message, "", sender, args); - } - - /** - * {@inheritDoc} - */ - @Override - public void good(Message message, CommandSender sender, Object... args) { - send(message, ChatColor.GREEN.toString() + this.getMessage(Message.GENERIC_SUCCESS), sender, args); - } - - /** - * {@inheritDoc} - */ - @Override - public void info(Message message, CommandSender sender, Object... args) { - send(message, ChatColor.YELLOW.toString() + this.getMessage(Message.GENERIC_INFO), sender, args); - } - - /** - * {@inheritDoc} - */ - @Override - public void help(Message message, CommandSender sender, Object... args) { - send(message, ChatColor.GRAY.toString() + this.getMessage(Message.GENERIC_HELP), sender, args); - } - - /** - * {@inheritDoc} - */ - @Override - public void sendMessage(CommandSender player, String message) { - List messages = com.onarandombox.multiverseinventories.util.Font.splitString(message); - sendMessages(player, messages); - } - - /** - * {@inheritDoc} - */ - @Override - public void sendMessages(CommandSender player, List messages) { - for (String s : messages) { - player.sendMessage(s); - } - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/DefaultPersistingProfile.java b/src/main/java/com/onarandombox/multiverseinventories/DefaultPersistingProfile.java deleted file mode 100644 index 186b5916..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/DefaultPersistingProfile.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.onarandombox.multiverseinventories; - -import com.onarandombox.multiverseinventories.profile.PlayerProfile; -import com.onarandombox.multiverseinventories.share.PersistingProfile; -import com.onarandombox.multiverseinventories.share.Shares; - -/** - * Simple implementation of PersistingProfile. - */ -final class DefaultPersistingProfile implements PersistingProfile { - - private Shares shares; - private PlayerProfile profile; - - public DefaultPersistingProfile(Shares shares, PlayerProfile profile) { - this.shares = shares; - this.profile = profile; - } - - /** - * {@inheritDoc} - */ - @Override - public Shares getShares() { - return this.shares; - } - - /** - * {@inheritDoc} - */ - @Override - public PlayerProfile getProfile() { - return this.profile; - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/FlatFileProfileDataSource.java b/src/main/java/com/onarandombox/multiverseinventories/FlatFileProfileDataSource.java deleted file mode 100644 index 040d31f6..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/FlatFileProfileDataSource.java +++ /dev/null @@ -1,598 +0,0 @@ -package com.onarandombox.multiverseinventories; - -import com.dumptruckman.bukkit.configuration.json.JsonConfiguration; -import com.dumptruckman.minecraft.util.Logging; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.onarandombox.multiverseinventories.profile.ProfileDataSource; -import com.onarandombox.multiverseinventories.profile.ProfileKey; -import com.onarandombox.multiverseinventories.profile.ProfileTypes; -import com.onarandombox.multiverseinventories.share.ProfileEntry; -import com.onarandombox.multiverseinventories.share.Sharable; -import com.onarandombox.multiverseinventories.share.SharableEntry; -import com.onarandombox.multiverseinventories.profile.container.ContainerType; -import com.onarandombox.multiverseinventories.profile.GlobalProfile; -import com.onarandombox.multiverseinventories.profile.PlayerProfile; -import com.onarandombox.multiverseinventories.profile.ProfileType; -import net.minidev.json.JSONObject; -import org.bukkit.Bukkit; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.FileConfiguration; -import org.json.simple.parser.JSONParser; - -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; - -class FlatFileProfileDataSource implements ProfileDataSource { - - private static final String JSON = ".json"; - - private final JSONParser JSON_PARSER = new JSONParser(); - - private final ExecutorService fileIOExecutorService = Executors.newSingleThreadExecutor(); - - // TODO these probably need configurable max sizes - private final Cache profileCache = CacheBuilder.newBuilder() - .expireAfterAccess(10, TimeUnit.MINUTES) - .maximumSize(1000) - .build(); - private final Cache globalProfileCache = CacheBuilder.newBuilder() - .expireAfterAccess(10, TimeUnit.MINUTES) - .maximumSize(500) - .build(); - - private final File worldFolder; - private final File groupFolder; - private final File playerFolder; - - FlatFileProfileDataSource(MultiverseInventories plugin) throws IOException { - // Make the data folders - plugin.getDataFolder().mkdirs(); - - // Check if the data file exists. If not, create it. - this.worldFolder = new File(plugin.getDataFolder(), "worlds"); - if (!this.worldFolder.exists()) { - if (!this.worldFolder.mkdirs()) { - throw new IOException("Could not create world folder!"); - } - } - this.groupFolder = new File(plugin.getDataFolder(), "groups"); - if (!this.groupFolder.exists()) { - if (!this.groupFolder.mkdirs()) { - throw new IOException("Could not create group folder!"); - } - } - this.playerFolder = new File(plugin.getDataFolder(), "players"); - if (!this.playerFolder.exists()) { - if (!this.playerFolder.mkdirs()) { - throw new IOException("Could not create player folder!"); - } - } - } - - private FileConfiguration waitForConfigHandle(File file) { - Future future = fileIOExecutorService.submit(new ConfigLoader(file)); - while (true) { - try { - return future.get(); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - } - } - } - - private static FileConfiguration getConfigHandleNow(File file) { - return JsonConfiguration.loadConfiguration(file); - } - - private static class ConfigLoader implements Callable { - private final File file; - - private ConfigLoader(File file) { - this.file = file; - } - - @Override - public FileConfiguration call() throws Exception { - return getConfigHandleNow(file); - } - } - - private File getFolder(ContainerType type, String folderName) { - File folder; - switch (type) { - case GROUP: - folder = new File(this.groupFolder, folderName); - break; - case WORLD: - folder = new File(this.worldFolder, folderName); - break; - default: - folder = new File(this.worldFolder, folderName); - break; - } - - if (!folder.exists()) { - folder.mkdirs(); - } - return folder; - } - - /** - * Retrieves the data file for a player based on a given world/group name, creating it if necessary. - * - * @param type Indicates whether data is for group or world. - * @param dataName The name of the group or world. - * @param playerName The name of the player. - * @return The data file for a player. - * @throws IOException if there was a problem creating the file. - */ - File getPlayerFile(ContainerType type, String dataName, String playerName) throws IOException { - File jsonPlayerFile = new File(this.getFolder(type, dataName), playerName + JSON); - if (!jsonPlayerFile.exists()) { - try { - jsonPlayerFile.createNewFile(); - } catch (IOException e) { - throw new IOException("Could not create necessary player data file: " + jsonPlayerFile.getPath() - + ". Data for " + playerName + " in " + type.name().toLowerCase() + " " + dataName - + " may not be saved.", e); - } - } - Logging.finer("got data file: %s. Type: %s, DataName: %s, PlayerName: %s", - jsonPlayerFile.getPath(), type, dataName, playerName); - return jsonPlayerFile; - } - - /** - * Retrieves the data file for a player for their global data, creating it if necessary. - * - * @param fileName The name of the file (player name or UUID) without extension. - * @param createIfMissing If true, the file will be created it it does not exist. - * @return The data file for a player. - * @throws IOException if there was a problem creating the file. - */ - File getGlobalFile(String fileName, boolean createIfMissing) throws IOException { - File jsonPlayerFile = new File(playerFolder, fileName + JSON); - if (createIfMissing && !jsonPlayerFile.exists()) { - try { - jsonPlayerFile.createNewFile(); - } catch (IOException e) { - throw new IOException("Could not create necessary player file: " + jsonPlayerFile.getPath() + ". " - + "There may be issues with " + fileName + "'s metadata", e); - } - } - return jsonPlayerFile; - } - - private void queueWrite(PlayerProfile profile) { - fileIOExecutorService.submit(new FileWriter(profile.clone())); - } - - private class FileWriter implements Callable { - private final PlayerProfile profile; - - private FileWriter(PlayerProfile profile) { - this.profile = profile; - } - - @Override - public Void call() throws Exception { - processProfileWrite(profile); - return null; - } - } - - private void processProfileWrite(PlayerProfile playerProfile) { - try { - File playerFile = this.getPlayerFile(playerProfile.getContainerType(), - playerProfile.getContainerName(), playerProfile.getPlayer().getName()); - FileConfiguration playerData = getConfigHandleNow(playerFile); - playerData.createSection(playerProfile.getProfileType().getName(), serializePlayerProfile(playerProfile)); - try { - playerData.save(playerFile); - } catch (IOException e) { - Logging.severe("Could not save data for player: " + playerProfile.getPlayer().getName() - + " for " + playerProfile.getContainerType().toString() + ": " + playerProfile.getContainerName()); - Logging.severe(e.getMessage()); - } - } catch (final Exception e) { - Logging.getLogger().log(Level.WARNING, "Error while attempting to write profile data.", e); - } - } - - private Map serializePlayerProfile(PlayerProfile playerProfile) { - Map playerData = new LinkedHashMap(); - JSONObject jsonStats = new JSONObject(); - for (SharableEntry entry : playerProfile) { - if (entry.getValue() != null) { - if (entry.getSharable().getSerializer() == null) { - continue; - } - Sharable sharable = entry.getSharable(); - if (sharable.getProfileEntry().isStat()) { - jsonStats.put(sharable.getProfileEntry().getFileTag(), - sharable.getSerializer().serialize(entry.getValue())); - } else { - playerData.put(sharable.getProfileEntry().getFileTag(), - sharable.getSerializer().serialize(entry.getValue())); - } - } - } - if (!jsonStats.isEmpty()) { - playerData.put(DataStrings.PLAYER_STATS, jsonStats); - } - return playerData; - } - - /** - * {@inheritDoc} - */ - @Override - public void updatePlayerData(PlayerProfile playerProfile) { - queueWrite(playerProfile); - } - - private PlayerProfile getPlayerData(ProfileKey key) { - PlayerProfile cached = profileCache.getIfPresent(key); - if (cached != null) { - return cached; - } - File playerFile = null; - try { - playerFile = getPlayerFile(key.getContainerType(), key.getDataName(), key.getPlayerName()); - } catch (IOException e) { - e.printStackTrace(); - // Return an empty profile - return PlayerProfile.createPlayerProfile(key.getContainerType(), key.getDataName(), key.getProfileType(), - Bukkit.getOfflinePlayer(key.getPlayerUUID())); - } - FileConfiguration playerData = this.waitForConfigHandle(playerFile); - if (convertConfig(playerData)) { - try { - playerData.save(playerFile); - } catch (IOException e) { - Logging.severe("Could not save data for player: " + key.getPlayerName() - + " for " + key.getContainerType().toString() + ": " + key.getDataName() + " after conversion."); - Logging.severe(e.getMessage()); - } - } - ConfigurationSection section = playerData.getConfigurationSection(key.getProfileType().getName()); - if (section == null) { - section = playerData.createSection(key.getProfileType().getName()); - } - PlayerProfile result = deserializePlayerProfile(key, convertSection(section)); - profileCache.put(key, result); - return result; - } - - @Override - public PlayerProfile getPlayerData(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID) { - return getPlayerData(ProfileKey.createProfileKey(containerType, dataName, profileType, playerUUID)); - } - - private PlayerProfile deserializePlayerProfile(ProfileKey pKey, Map playerData) { - PlayerProfile profile = PlayerProfile.createPlayerProfile(pKey.getContainerType(), pKey.getDataName(), - pKey.getProfileType(), Bukkit.getOfflinePlayer(pKey.getPlayerUUID())); - for (Object keyObj : playerData.keySet()) { - String key = keyObj.toString(); - if (key.equalsIgnoreCase(DataStrings.PLAYER_STATS)) { - final Object statsObject = playerData.get(key); - if (statsObject instanceof String) { - parseJsonPlayerStatsIntoProfile(statsObject.toString(), profile); - } else { - if (statsObject instanceof Map) { - parsePlayerStatsIntoProfile((Map) statsObject, profile); - } else { - Logging.warning("Could not parse stats for " + pKey.getPlayerName()); - } - } - } else { - if (playerData.get(key) == null) { - Logging.fine("Player data '" + key + "' is null for: " + pKey.getPlayerName()); - continue; - } - try { - Sharable sharable = ProfileEntry.lookup(false, key); - if (sharable == null) { - Logging.fine("Player fileTag '" + key + "' is unrecognized!"); - continue; - } - profile.set(sharable, sharable.getSerializer().deserialize(playerData.get(key))); - } catch (Exception e) { - Logging.fine("Could not parse fileTag: '" + key + "' with value '" + playerData.get(key) + "'"); - Logging.getLogger().log(Level.FINE, "Exception: ", e); - e.printStackTrace(); - } - } - } - Logging.finer("Created player profile from map for '" + pKey.getPlayerName() + "'."); - return profile; - } - - private void parsePlayerStatsIntoProfile(Map stats, PlayerProfile profile) { - for (Object key : stats.keySet()) { - Sharable sharable = ProfileEntry.lookup(true, key.toString()); - if (sharable != null) { - profile.set(sharable, sharable.getSerializer().deserialize(stats.get(key).toString())); - } else { - Logging.warning("Could not parse stat: '" + key + "' for player '" - + profile.getPlayer().getName() + "' for " + profile.getContainerType() + " '" - + profile.getContainerName() + "'"); - } - } - } - - private void parseJsonPlayerStatsIntoProfile(String stats, PlayerProfile profile) { - if (stats.isEmpty()) { - return; - } - org.json.simple.JSONObject jsonStats = null; - try { - jsonStats = (org.json.simple.JSONObject) JSON_PARSER.parse(stats); - } catch (org.json.simple.parser.ParseException e) { - Logging.warning("Could not parse stats for player'" + profile.getPlayer().getName() + "' for " + - profile.getContainerType() + " '" + profile.getContainerName() + "': " + e.getMessage()); - } catch (ClassCastException e) { - Logging.warning("Could not parse stats for player'" + profile.getPlayer().getName() + "' for " + - profile.getContainerType() + " '" + profile.getContainerName() + "': " + e.getMessage()); - } - if (jsonStats == null) { - Logging.warning("Could not parse stats for player'" + profile.getPlayer().getName() + "' for " + - profile.getContainerType() + " '" + profile.getContainerName() + "'"); - return; - } - parsePlayerStatsIntoProfile(jsonStats, profile); - } - - // TODO Remove this conversion - private boolean convertConfig(FileConfiguration config) { - ConfigurationSection section = config.getConfigurationSection("playerData"); - if (section != null) { - config.set(ProfileTypes.SURVIVAL.getName(), section); - config.set(ProfileTypes.CREATIVE.getName(), section); - config.set(ProfileTypes.ADVENTURE.getName(), section); - config.set("playerData", null); - Logging.finer("Migrated old player data to new multi-profile format"); - return true; - } - return false; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean removePlayerData(ContainerType containerType, String dataName, ProfileType profileType, String playerName) { - if (profileType == null) { - try { - File playerFile = getPlayerFile(containerType, dataName, playerName); - return playerFile.delete(); - } catch (IOException ignore) { - Logging.warning("Attempted to delete file that did not exist for player " + playerName - + " in " + containerType.name().toLowerCase() + " " + dataName); - return false; - } - } else { - File playerFile; - try { - playerFile = getPlayerFile(containerType, dataName, playerName); - } catch (IOException e) { - Logging.warning("Attempted to delete " + playerName + "'s data for " - + profileType.getName().toLowerCase() + " mode in " + containerType.name().toLowerCase() - + " " + dataName + " but the file did not exist."); - return false; - } - FileConfiguration playerData = this.waitForConfigHandle(playerFile); - playerData.set(profileType.getName(), null); - try { - playerData.save(playerFile); - } catch (IOException e) { - Logging.severe("Could not delete data for player: " + playerName - + " for " + containerType.toString() + ": " + dataName); - Logging.severe(e.getMessage()); - return false; - } - return true; - } - } - - private Map convertSection(ConfigurationSection section) { - Map resultMap = new HashMap(); - for (String key : section.getKeys(false)) { - Object obj = section.get(key); - if (obj instanceof ConfigurationSection) { - resultMap.put(key, convertSection((ConfigurationSection) obj)); - } else { - resultMap.put(key, obj); - } - } - return resultMap; - } - - @Override - @Deprecated - public GlobalProfile getGlobalProfile(String playerName) { - return getGlobalProfile(playerName, Bukkit.getOfflinePlayer(playerName).getUniqueId()); - } - - @Override - public GlobalProfile getGlobalProfile(String playerName, UUID playerUUID) { - GlobalProfile cached = globalProfileCache.getIfPresent(playerUUID); - if (cached != null) { - return cached; - } - File playerFile; - - // Migrate old data if necessary - try { - playerFile = getGlobalFile(playerName, false); - } catch (IOException e) { - // This won't ever happen - e.printStackTrace(); - return GlobalProfile.createGlobalProfile(playerName); - } - if (playerFile.exists()) { - GlobalProfile profile = loadGlobalProfile(playerFile, playerName, playerUUID); - if (!migrateGlobalProfileToUUID(profile, playerFile)) { - Logging.warning("Could not properly migrate player global data file for " + playerName); - } - globalProfileCache.put(playerUUID, profile); - return profile; - } - - // Load current format - try { - playerFile = getGlobalFile(playerUUID.toString(), true); - } catch (IOException e) { - e.printStackTrace(); - return GlobalProfile.createGlobalProfile(playerName, playerUUID); - } - GlobalProfile profile = loadGlobalProfile(playerFile, playerName, playerUUID); - globalProfileCache.put(playerUUID, profile); - return profile; - } - - private boolean migrateGlobalProfileToUUID(GlobalProfile profile, File playerFile) { - updateGlobalProfile(profile); - return playerFile.delete(); - } - - private GlobalProfile loadGlobalProfile(File playerFile, String playerName, UUID playerUUID) { - FileConfiguration playerData = this.waitForConfigHandle(playerFile); - ConfigurationSection section = playerData.getConfigurationSection("playerData"); - if (section == null) { - section = playerData.createSection("playerData"); - } - return deserializeGlobalProfile(playerName, playerUUID, convertSection(section)); - } - - private GlobalProfile deserializeGlobalProfile(String playerName, UUID playerUUID, - Map playerData) { - GlobalProfile globalProfile = GlobalProfile.createGlobalProfile(playerName, playerUUID); - for (String key : playerData.keySet()) { - if (key.equalsIgnoreCase(DataStrings.PLAYER_LAST_WORLD)) { - globalProfile.setLastWorld(playerData.get(key).toString()); - } else if (key.equalsIgnoreCase(DataStrings.PLAYER_SHOULD_LOAD)) { - globalProfile.setLoadOnLogin(Boolean.valueOf(playerData.get(key).toString())); - } else if (key.equalsIgnoreCase(DataStrings.PLAYER_LAST_KNOWN_NAME)) { - globalProfile.setLastKnownName(playerData.get(key).toString()); - } - } - return globalProfile; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean updateGlobalProfile(GlobalProfile globalProfile) { - File playerFile = null; - try { - playerFile = this.getGlobalFile(globalProfile.getPlayerUUID().toString(), true); - } catch (IOException e) { - e.printStackTrace(); - return false; - } - FileConfiguration playerData = this.waitForConfigHandle(playerFile); - playerData.createSection("playerData", serializeGlobalProfile(globalProfile)); - try { - playerData.save(playerFile); - } catch (IOException e) { - Logging.severe("Could not save global data for player: " + globalProfile); - Logging.severe(e.getMessage()); - return false; - } - return true; - } - - private Map serializeGlobalProfile(GlobalProfile profile) { - Map result = new HashMap(2); - if (profile.getLastWorld() != null) { - result.put(DataStrings.PLAYER_LAST_WORLD, profile.getLastWorld()); - } - result.put(DataStrings.PLAYER_SHOULD_LOAD, profile.shouldLoadOnLogin()); - result.put(DataStrings.PLAYER_LAST_KNOWN_NAME, profile.getLastKnownName()); - return result; - } - - @Override - @Deprecated - // TODO replace for UUID - public void updateLastWorld(String playerName, String worldName) { - GlobalProfile globalProfile = getGlobalProfile(playerName); - globalProfile.setLastWorld(worldName); - updateGlobalProfile(globalProfile); - } - - @Override - @Deprecated - // TODO replace for UUID - public void setLoadOnLogin(final String playerName, final boolean loadOnLogin) { - final GlobalProfile globalProfile = getGlobalProfile(playerName); - globalProfile.setLoadOnLogin(loadOnLogin); - updateGlobalProfile(globalProfile); - } - - @Override - public void migratePlayerData(String oldName, String newName, UUID uuid, boolean removeOldData) throws IOException { - File[] worldFolders = worldFolder.listFiles(File::isDirectory); - if (worldFolders == null) { - throw new IOException("Could not enumerate world folders"); - } - File[] groupFolders = groupFolder.listFiles(File::isDirectory); - if (groupFolders == null) { - throw new IOException("Could not enumerate group folders"); - } - - for (File worldFolder : worldFolders) { - ProfileKey key = ProfileKey.createProfileKey(ContainerType.WORLD, worldFolder.getName(), - ProfileTypes.ADVENTURE, uuid, oldName); - updatePlayerData(getPlayerData(key)); - updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.CREATIVE))); - updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.SURVIVAL))); - } - - for (File groupFolder : groupFolders) { - ProfileKey key = ProfileKey.createProfileKey(ContainerType.GROUP, groupFolder.getName(), - ProfileTypes.ADVENTURE, uuid, oldName); - updatePlayerData(getPlayerData(key)); - updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.CREATIVE))); - updatePlayerData(getPlayerData(ProfileKey.createProfileKey(key, ProfileTypes.SURVIVAL))); - } - - if (removeOldData) { - for (File worldFolder : worldFolders) { - removePlayerData(ContainerType.WORLD, worldFolder.getName(), ProfileTypes.ADVENTURE, oldName); - removePlayerData(ContainerType.WORLD, worldFolder.getName(), ProfileTypes.CREATIVE, oldName); - removePlayerData(ContainerType.WORLD, worldFolder.getName(), ProfileTypes.SURVIVAL, oldName); - } - for (File groupFolder : groupFolders) { - removePlayerData(ContainerType.GROUP, groupFolder.getName(), ProfileTypes.ADVENTURE, oldName); - removePlayerData(ContainerType.GROUP, groupFolder.getName(), ProfileTypes.CREATIVE, oldName); - removePlayerData(ContainerType.GROUP, groupFolder.getName(), ProfileTypes.SURVIVAL, oldName); - } - } - } - - @Override - public void clearProfileCache(ProfileKey key) { - profileCache.invalidate(key); - } - - void clearCache() { - globalProfileCache.invalidateAll(); - profileCache.invalidateAll(); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/GameModeShareHandler.java b/src/main/java/com/onarandombox/multiverseinventories/GameModeShareHandler.java deleted file mode 100644 index 55434da2..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/GameModeShareHandler.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.onarandombox.multiverseinventories; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.event.GameModeChangeShareHandlingEvent; -import com.onarandombox.multiverseinventories.event.ShareHandlingEvent; -import com.onarandombox.multiverseinventories.profile.ProfileType; -import com.onarandombox.multiverseinventories.profile.ProfileTypes; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainer; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.GameMode; -import org.bukkit.entity.Player; - -import java.util.List; - -/** - * GameMode change implementation of ShareHandler. - */ -final class GameModeShareHandler extends ShareHandler { - - private final GameMode fromGameMode; - private final GameMode toGameMode; - private final ProfileType fromType; - private final ProfileType toType; - private final String world; - private final ProfileContainer worldProfileContainer; - private final List worldGroups; - - GameModeShareHandler(MultiverseInventories inventories, Player player, - GameMode fromGameMode, GameMode toGameMode) { - super(inventories, player); - this.fromGameMode = fromGameMode; - this.toGameMode = toGameMode; - this.fromType = ProfileTypes.forGameMode(fromGameMode); - this.toType = ProfileTypes.forGameMode(toGameMode); - this.world = player.getWorld().getName(); - this.worldProfileContainer = inventories.getWorldProfileContainerStore().getContainer(world); - this.worldGroups = getAffectedWorldGroups(); - - prepareProfiles(); - } - - private List getAffectedWorldGroups() { - return this.inventories.getGroupManager().getGroupsForWorld(world); - } - - @Override - protected ShareHandlingEvent createEvent() { - return new GameModeChangeShareHandlingEvent(player, affectedProfiles, fromGameMode, toGameMode); - } - - private void prepareProfiles() { - Logging.finer("=== " + player.getName() + " changing game mode from: " + fromType - + " to: " + toType + " for world: " + world + " ==="); - - setAlwaysWriteProfile(worldProfileContainer.getPlayerData(fromType, player)); - - if (isPlayerAffectedByChange()) { - addProfiles(); - } - } - - private boolean isPlayerAffectedByChange() { - if (isPlayerBypassingChange()) { - logBypass(); - return false; - } - return true; - } - - private boolean isPlayerBypassingChange() { - return Perm.BYPASS_WORLD.hasBypass(player, world) - || Perm.BYPASS_GAME_MODE.hasBypass(player, toGameMode.name().toLowerCase()); - } - - private void addProfiles() { - if (hasWorldGroups()) { - worldGroups.forEach(this::addProfilesForWorldGroup); - } else { - Logging.finer("No groups for world."); - addReadProfile(worldProfileContainer.getPlayerData(toType, player), Sharables.allOf()); - } - } - - private boolean hasWorldGroups() { - return !worldGroups.isEmpty(); - } - - private void addProfilesForWorldGroup(WorldGroup worldGroup) { - ProfileContainer container = worldGroup.getGroupProfileContainer(); - addWriteProfile(container.getPlayerData(fromType, player), Sharables.allOf()); - addReadProfile(container.getPlayerData(toType, player), Sharables.allOf()); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/InventoriesConfig.java b/src/main/java/com/onarandombox/multiverseinventories/InventoriesConfig.java deleted file mode 100644 index 867e1a97..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/InventoriesConfig.java +++ /dev/null @@ -1,332 +0,0 @@ -package com.onarandombox.multiverseinventories; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.share.Shares; -import com.onarandombox.multiverseinventories.util.CommentedYamlConfiguration; -import io.papermc.lib.PaperLib; -import org.bukkit.configuration.file.FileConfiguration; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * Provides methods for interacting with the configuration of Multiverse-Inventories. - */ -public final class InventoriesConfig { - - /** - * Enum for easily keeping track of config paths, defaults and comments. - */ - public enum Path { - /** - * Locale name config path, default and comments. - */ - LANGUAGE_FILE_NAME("settings.locale", "en", "# This is the locale you wish to use."), - /** - * First Run flag config path, default and comments. - */ - FIRST_RUN("settings.first_run", true, "# If this is true it will generate world groups for you based on MV worlds."), - /** - * First Run flag config path, default and comments. - */ - USE_BYPASS("settings.use_bypass", false, "# If this is set to true, it will enable bypass permissions (Check the wiki for more info.)"), - - /** - * Whether or not to make ungrouped worlds use the default group. - */ - DEFAULT_UNGROUPED_WORLDS("settings.default_ungrouped_worlds", false, "# If set to true, any world not listed in a group will automatically use the settings for the default group!"), - - /** - * Whether or not to save/load player data on log out/in. - */ - LOGGING_SAVE_LOAD("settings.save_load_on_log_in_out", false, - "# The default and suggested setting for this is FALSE.", - "# False means Multiverse-Inventories will not attempt to load or save any player data when they log in and out.", - "# That means that MINECRAFT will handle that exact thing JUST LIKE IT DOES NORMALLY.", - "# Changing this to TRUE will have Multiverse-Inventories save player data when they log out and load it when they log in.", - "# The biggest potential drawback here is that if your server crashes, player stats/inventories may be lost/rolled back!"), - - USE_OPTIONALS_UNGROUPED("shares.optionals_for_ungrouped_worlds", true, - "# When set to true, optional shares WILL be utilized in cases where a group does not cover their uses for a world.", - "# An example of this in action would be an ungrouped world using last_location. When this is true, players will return to their last location in that world.", - "# When set to false, optional shares WILL NOt be utilized in these cases, effectively disabling it for ungrouped worlds."), - /** - * First Run flag config path, default and comments. - */ - OPTIONAL_SHARES("shares.use_optionals", new ArrayList(), - "# You must specify optional shares you wish to use here or they will be ignored.", - "# The only built in optional shares are \"economy\" and \"last_location\"."), - /** - * Whether or not to split data based on game modes. - */ - USE_GAME_MODE_PROFILES("settings.use_game_mode_profiles", false, - "# If this is set to true, players will have different inventories/stats for each game mode.", - "# Please note that old data migrated to the version that has this feature will have their data copied for both game modes."); - - private String path; - private Object def; - private List comments; - - Path(String path, Object def, String... comments) { - this.path = path; - this.def = def; - this.comments = Arrays.asList(comments); - } - - /** - * Retrieves the path for a config option. - * - * @return The path for a config option. - */ - private String getPath() { - return this.path; - } - - /** - * Retrieves the default value for a config path. - * - * @return The default value for a config path. - */ - private Object getDefault() { - return this.def; - } - - /** - * Retrieves the comment for a config path. - * - * @return The comments for a config path. - */ - private List getComments() { - return this.comments; - } - } - - private final CommentedYamlConfiguration config; - private final MultiverseInventories plugin; - - InventoriesConfig(MultiverseInventories plugin) throws IOException { - this.plugin = plugin; - // Make the data folders - if (plugin.getDataFolder().mkdirs()) { - Logging.fine("Created data folder."); - } - - // Check if the config file exists. If not, create it. - File configFile = new File(plugin.getDataFolder(), "config.yml"); - boolean configFileExists = configFile.exists(); - if (!configFileExists) { - Logging.fine("Created config file."); - configFile.createNewFile(); - } - - // Load the configuration file into memory - boolean supportsCommentsNatively = PaperLib.getMinecraftVersion() > 17; - config = new CommentedYamlConfiguration(configFile, !configFileExists || !supportsCommentsNatively); - - // Sets defaults config values - this.setDefaults(); - - config.getConfig().options().header("Multiverse-Inventories Settings"); - - // Saves the configuration from memory to file - config.save(); - - Logging.setDebugLevel(this.getGlobalDebug()); - } - - - /** - * Loads default settings for any missing config values. - */ - private void setDefaults() { - for (InventoriesConfig.Path path : InventoriesConfig.Path.values()) { - config.addComment(path.getPath(), path.getComments()); - if (this.getConfig().get(path.getPath()) == null) { - if (path.getDefault() != null) { - Logging.fine("Config: Defaulting '" + path.getPath() + "' to " + path.getDefault()); - this.getConfig().set(path.getPath(), path.getDefault()); - } else { - this.getConfig().createSection(path.getPath()); - } - } - } - - } - - private Boolean getBoolean(Path path) { - return this.getConfig().getBoolean(path.getPath(), (Boolean) path.getDefault()); - } - - private Integer getInt(Path path) { - return this.getConfig().getInt(path.getPath(), (Integer) path.getDefault()); - } - - private String getString(Path path) { - return this.getConfig().getString(path.getPath(), (String) path.getDefault()); - } - - FileConfiguration getConfig() { - return this.config.getConfig(); - } - - /** - * Sets globalDebug level. - * - * @param globalDebug The new value. 0 = off. - */ - public void setGlobalDebug(int globalDebug) { - plugin.getCore().getMVConfig().setGlobalDebug(globalDebug); - } - - /** - * Gets globalDebug level. - * - * @return globalDebug. - */ - public int getGlobalDebug() { - return plugin.getCore().getMVConfig().getGlobalDebug(); - } - - /** - * Retrieves the locale string from the config. - * - * @return The locale string. - */ - public String getLocale() { - return this.getString(Path.LANGUAGE_FILE_NAME); - } - - /** - * Tells whether this is the first time the plugin has run as set by a config flag. - * - * @return True if first_run is set to true in config. - */ - public boolean isFirstRun() { - return this.getBoolean(Path.FIRST_RUN); - } - - /** - * Sets the first_run flag in the config so that the plugin no longer thinks it is the first run. - * - * @param firstRun What to set the flag to in the config. - */ - void setFirstRun(boolean firstRun) { - this.getConfig().set(Path.FIRST_RUN.getPath(), firstRun); - } - - /** - * @return True if we should check for bypass permissions. - */ - public boolean isUsingBypass() { - return this.getBoolean(Path.USE_BYPASS); - } - - /** - * @param useBypass Whether or not to check for bypass permissions. - */ - public void setUsingBypass(boolean useBypass) { - this.getConfig().set(Path.USE_BYPASS.getPath(), useBypass); - } - - /** - * Tells whether Multiverse-Inventories should save on player logout and load on player login. - * - * @return True if should save and load on player log out and in. - */ - public boolean usingLoggingSaveLoad() { - return this.getBoolean(Path.LOGGING_SAVE_LOAD); - } - - /** - * Sets whether Multiverse-Inventories should save on player logout and load on player login. - * - * @param useLoggingSaveLoad true if should save and load on player log out and in. - */ - public void setUsingLoggingSaveLoad(boolean useLoggingSaveLoad) { - this.getConfig().set(Path.LOGGING_SAVE_LOAD.getPath(), useLoggingSaveLoad); - } - - private Shares optionalSharables = null; - - /** - * @return A list of optional {@link com.onarandombox.multiverseinventories.share.Sharable}s to be treated as - * regular {@link com.onarandombox.multiverseinventories.share.Sharable}s throughout the code. - * A {@link com.onarandombox.multiverseinventories.share.Sharable} marked as optional is ignored if it is not - * contained in this list. - */ - public Shares getOptionalShares() { - if (this.optionalSharables == null) { - List list = this.getConfig().getList(Path.OPTIONAL_SHARES.getPath()); - if (list != null) { - this.optionalSharables = Sharables.fromList(list); - } else { - Logging.warning("'" + Path.OPTIONAL_SHARES.getPath() + "' is setup incorrectly!"); - this.optionalSharables = Sharables.noneOf(); - } - } - return this.optionalSharables; - } - - /** - * @return true if worlds with no group should be considered part of the default group. - */ - public boolean isDefaultingUngroupedWorlds() { - return this.getBoolean(Path.DEFAULT_UNGROUPED_WORLDS); - } - - /** - * @param useDefaultGroup Set this to true to use the default group for ungrouped worlds. - */ - public void setDefaultingUngroupedWorlds(boolean useDefaultGroup) { - this.getConfig().set(Path.FIRST_RUN.getPath(), useDefaultGroup); - } - - /** - * @return True if using separate data for game modes. - */ - public boolean isUsingGameModeProfiles() { - return this.getBoolean(Path.USE_GAME_MODE_PROFILES); - } - - /** - * @param useGameModeProfile whether to use separate data for game modes. - */ - public void setUsingGameModeProfiles(boolean useGameModeProfile) { - this.getConfig().set(Path.USE_GAME_MODE_PROFILES.getPath(), useGameModeProfile); - } - - /** - * Whether Multiverse-Inventories will utilize optional shares in worlds that are not grouped. - * - * @return true if should utilize optional shares in worlds that are not grouped. - */ - public boolean usingOptionalsForUngrouped() { - return this.getBoolean(Path.USE_OPTIONALS_UNGROUPED); - } - - /** - * Sets whether Multiverse-Inventories will utilize optional shares in worlds that are not grouped. - * - * @param usingOptionalsForUngrouped true if should utilize optional shares in worlds that are not grouped. - */ - public void setUsingOptionalsForUngrouped(final boolean usingOptionalsForUngrouped) { - this.getConfig().set(Path.USE_OPTIONALS_UNGROUPED.getPath(), usingOptionalsForUngrouped); - } - - /** - * Saves the configuration file to disk. - */ - // TODO remove need for this method. - // TODO Figure out what I meant by the above todo message... - public void save() { - if (this.optionalSharables != null) { - this.getConfig().set(Path.OPTIONAL_SHARES.getPath(), this.optionalSharables.toStringList()); - } - this.config.save(); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/InventoriesListener.java b/src/main/java/com/onarandombox/multiverseinventories/InventoriesListener.java deleted file mode 100644 index 8e1d8e4d..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/InventoriesListener.java +++ /dev/null @@ -1,469 +0,0 @@ -package com.onarandombox.multiverseinventories; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.MultiverseCore.api.MultiverseWorld; -import com.onarandombox.MultiverseCore.event.MVConfigReloadEvent; -import com.onarandombox.MultiverseCore.event.MVVersionEvent; -import com.onarandombox.multiverseinventories.profile.GlobalProfile; -import com.onarandombox.multiverseinventories.profile.PlayerProfile; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainer; -import com.onarandombox.multiverseinventories.share.Sharables; -import me.drayshak.WorldInventories.WorldInventories; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Item; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.entity.EntityPortalEvent; -import org.bukkit.event.entity.PlayerDeathEvent; -import org.bukkit.event.player.AsyncPlayerPreLoginEvent; -import org.bukkit.event.player.AsyncPlayerPreLoginEvent.Result; -import org.bukkit.event.player.PlayerChangedWorldEvent; -import org.bukkit.event.player.PlayerGameModeChangeEvent; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.event.player.PlayerRespawnEvent; -import org.bukkit.event.player.PlayerTeleportEvent; -import org.bukkit.event.server.PluginDisableEvent; -import org.bukkit.event.server.PluginEnableEvent; -import org.bukkit.event.world.WorldUnloadEvent; -import org.bukkit.inventory.InventoryHolder; -import uk.co.tggl.pluckerpluck.multiinv.MultiInv; - -import java.io.File; -import java.io.IOException; -import java.util.List; -import java.util.stream.Collectors; - -/** - * PlayerListener for MultiverseInventories. - */ -public class InventoriesListener implements Listener { - - private MultiverseInventories inventories; - private List currentGroups; - private Location spawnLoc = null; - - public InventoriesListener(MultiverseInventories inventories) { - this.inventories = inventories; - } - - /** - * Adds Multiverse-Inventories version info to /mv version. - * - * @param event The MVVersionEvent that this plugin will listen for. - */ - @EventHandler - public void versionRequest(MVVersionEvent event) { - event.appendVersionInfo(this.inventories.getVersionInfo()); - File configFile = new File(this.inventories.getDataFolder(), "config.yml"); - File groupsFile = new File(this.inventories.getDataFolder(), "groups.yml"); - event.putDetailedVersionInfo("multiverse-inventories/config.yml", configFile); - event.putDetailedVersionInfo("multiverse-inventories/groups.yml", groupsFile); - } - - /** - * Hooks Multiverse-Inventories into the Multiverse reload command. - * - * @param event The MVConfigReloadEvent that this plugin will listen for. - */ - @EventHandler - public void configReload(MVConfigReloadEvent event) { - this.inventories.reloadConfig(); - event.addConfig("Multiverse-Inventories - config.yml"); - } - - /** - * Called when a plugin is enabled. - * - * @param event The plugin enable event. - */ - @EventHandler - public void pluginEnable(PluginEnableEvent event) { - try { - if (event.getPlugin() instanceof MultiInv) { - this.inventories.getImportManager().hookMultiInv((MultiInv) event.getPlugin()); - } else if (event.getPlugin() instanceof WorldInventories) { - this.inventories.getImportManager().hookWorldInventories((WorldInventories) event.getPlugin()); - } - } catch (NoClassDefFoundError ignore) { - } - } - - /** - * Called when a plugin is disabled. - * - * @param event The plugin disable event. - */ - @EventHandler - public void pluginDisable(PluginDisableEvent event) { - try { - if (event.getPlugin() instanceof MultiInv) { - this.inventories.getImportManager().unHookMultiInv(); - } else if (event.getPlugin() instanceof WorldInventories) { - this.inventories.getImportManager().unHookWorldInventories(); - } - } catch (NoClassDefFoundError ignore) { - } - } - - @EventHandler(priority = EventPriority.MONITOR) - public void playerPreLogin(AsyncPlayerPreLoginEvent event) { - if (event.getLoginResult() != Result.ALLOWED) { - return; - } - - Logging.finer("Loading global profile for Player{name:'%s', uuid:'%s'}.", - event.getName(), event.getUniqueId()); - - GlobalProfile globalProfile = inventories.getData().getGlobalProfile(event.getName(), event.getUniqueId()); - if (!globalProfile.getLastKnownName().equalsIgnoreCase(event.getName())) { - // Data must be migrated - Logging.info("Player %s changed name from '%s' to '%s'. Attempting to migrate playerdata...", - event.getUniqueId(), globalProfile.getLastKnownName(), event.getName()); - try { - inventories.getData().migratePlayerData(globalProfile.getLastKnownName(), event.getName(), - event.getUniqueId(), true); - } catch (IOException e) { - Logging.severe("An error occurred while trying to migrate playerdata."); - e.printStackTrace(); - } - - globalProfile.setLastKnownName(event.getName()); - inventories.getData().updateGlobalProfile(globalProfile); - Logging.info("Migration complete!"); - } - } - - /** - * Called when a player joins the server. - * - * @param event The player join event. - */ - @EventHandler - public void playerJoin(final PlayerJoinEvent event) { - final Player player = event.getPlayer(); - final GlobalProfile globalProfile = inventories.getData().getGlobalProfile(player.getName(), player.getUniqueId()); - final String world = globalProfile.getLastWorld(); - if (inventories.getMVIConfig().usingLoggingSaveLoad() && globalProfile.shouldLoadOnLogin()) { - ShareHandlingUpdater.updatePlayer(inventories, player, new DefaultPersistingProfile(Sharables.allOf(), - inventories.getWorldProfileContainerStore().getContainer(world).getPlayerData(player))); - } - inventories.getData().setLoadOnLogin(player.getName(), false); - verifyCorrectWorld(player, player.getWorld().getName(), globalProfile); - } - - /** - * Called when a player leaves the server. - * - * @param event The player quit event. - */ - @EventHandler - public void playerQuit(final PlayerQuitEvent event) { - final Player player = event.getPlayer(); - final String world = event.getPlayer().getWorld().getName(); - inventories.getData().updateLastWorld(player.getName(), world); - if (inventories.getMVIConfig().usingLoggingSaveLoad()) { - ShareHandlingUpdater.updateProfile(inventories, player, new DefaultPersistingProfile(Sharables.allOf(), - inventories.getWorldProfileContainerStore().getContainer(world).getPlayerData(player))); - inventories.getData().setLoadOnLogin(player.getName(), true); - } - } - - private void verifyCorrectWorld(Player player, String world, GlobalProfile globalProfile) { - if (globalProfile.getLastWorld() == null) { - inventories.getData().updateLastWorld(player.getName(), world); - } else { - if (!world.equals(globalProfile.getLastWorld())) { - Logging.fine("Player did not spawn in the world they were last reported to be in!"); - new WorldChangeShareHandler(this.inventories, player, - globalProfile.getLastWorld(), world).handleSharing(); - inventories.getData().updateLastWorld(player.getName(), world); - } - } - } - - /** - * Called when a player changes game modes. - * - * @param event The game mode change event. - */ - @EventHandler(priority = EventPriority.MONITOR) - public void playerGameModeChange(PlayerGameModeChangeEvent event) { - if (event.isCancelled() || !inventories.getMVIConfig().isUsingGameModeProfiles()) { - return; - } - Player player = event.getPlayer(); - new GameModeShareHandler(this.inventories, player, - player.getGameMode(), event.getNewGameMode()).handleSharing(); - } - - /** - * Called when a player changes worlds. - * - * @param event The world change event. - */ - @EventHandler(priority = EventPriority.LOW) - public void playerChangedWorld(PlayerChangedWorldEvent event) { - Player player = event.getPlayer(); - World fromWorld = event.getFrom(); - World toWorld = player.getWorld(); - - // A precaution.. Will this ever be true? - if (fromWorld.equals(toWorld)) { - Logging.fine("PlayerChangedWorldEvent fired when player travelling in same world."); - return; - } - // Warn if not managed by Multiverse-Core - if (this.inventories.getCore().getMVWorldManager().getMVWorld(toWorld) == null - || this.inventories.getCore().getMVWorldManager().getMVWorld(fromWorld) == null) { - Logging.fine("The from or to world is not managed by Multiverse-Core!"); - } - - new WorldChangeShareHandler(this.inventories, player, fromWorld.getName(), toWorld.getName()).handleSharing(); - inventories.getData().updateLastWorld(player.getName(), toWorld.getName()); - } - - /** - * Called when a player teleports. - * - * @param event The player teleport event. - */ - @EventHandler(priority = EventPriority.MONITOR) - public void playerTeleport(PlayerTeleportEvent event) { - if (event.isCancelled() - || event.getFrom().getWorld().equals(event.getTo().getWorld()) - || !this.inventories.getMVIConfig().getOptionalShares().contains(Sharables.LAST_LOCATION)) { - return; - } - - Player player = event.getPlayer(); - - String fromWorldName = event.getFrom().getWorld().getName(); - String toWorldName = event.getTo().getWorld().getName(); - - ProfileContainer fromWorldProfileContainer = this.inventories.getWorldProfileContainerStore().getContainer(fromWorldName); - PlayerProfile playerProfile = fromWorldProfileContainer.getPlayerData(player); - playerProfile.set(Sharables.LAST_LOCATION, event.getFrom()); - - List fromGroups = this.inventories.getGroupManager().getGroupsForWorld(fromWorldName); - for (WorldGroup fromGroup : fromGroups) { - playerProfile = fromGroup.getGroupProfileContainer().getPlayerData(event.getPlayer()); - - if (fromGroup.containsWorld(toWorldName)) { - if (!fromGroup.isSharing(Sharables.LAST_LOCATION)) { - playerProfile.set(Sharables.LAST_LOCATION, event.getFrom()); - } - } else { - playerProfile.set(Sharables.LAST_LOCATION, event.getFrom()); - } - } - - // Possibly prevents item duping exploit - player.closeInventory(); - } - - /** - * Called when a player dies. - * - * @param event The player death event. - */ - @EventHandler(priority = EventPriority.MONITOR) - public void playerDeath(PlayerDeathEvent event) { - Logging.finer("=== Handling PlayerDeathEvent for: " + event.getEntity().getName() + " ==="); - String deathWorld = event.getEntity().getWorld().getName(); - ProfileContainer worldProfileContainer = this.inventories.getWorldProfileContainerStore().getContainer(deathWorld); - PlayerProfile profile = worldProfileContainer.getPlayerData(event.getEntity()); - profile.set(Sharables.LEVEL, event.getNewLevel()); - profile.set(Sharables.EXPERIENCE, (float) event.getNewExp()); - profile.set(Sharables.TOTAL_EXPERIENCE, event.getNewTotalExp()); - this.inventories.getData().updatePlayerData(profile); - for (WorldGroup worldGroup : this.inventories.getGroupManager().getGroupsForWorld(deathWorld)) { - profile = worldGroup.getGroupProfileContainer().getPlayerData(event.getEntity()); - profile.set(Sharables.LEVEL, event.getNewLevel()); - profile.set(Sharables.EXPERIENCE, (float) event.getNewExp()); - profile.set(Sharables.TOTAL_EXPERIENCE, event.getNewTotalExp()); - this.inventories.getData().updatePlayerData(profile); - } - Logging.finer("=== Finished handling PlayerDeathEvent for: " + event.getEntity().getName() + "! ==="); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void playerRespawn(PlayerRespawnEvent event) { - Location respawnLoc = event.getRespawnLocation(); - if (respawnLoc == null) { - // This probably only happens if a naughty plugin sets the location to null... - return; - } - final Player player = event.getPlayer(); - Bukkit.getScheduler().scheduleSyncDelayedTask(inventories, new Runnable() { - public void run() { - verifyCorrectWorld(player, player.getWorld().getName(), - inventories.getData().getGlobalProfile(player.getName(), player.getUniqueId())); - } - }, 2L); - } - - /** - * Handles player respawns at the LOWEST priority. - * - * @param event The player respawn event. - */ - @EventHandler(priority = EventPriority.LOWEST) - public void lowestPriorityRespawn(PlayerRespawnEvent event) { - if (!event.isBedSpawn()) { - World world = event.getPlayer().getWorld(); - this.currentGroups = this.inventories.getGroupManager() - .getGroupsForWorld(world.getName()); - this.handleRespawn(event, EventPriority.LOWEST); - } - } - - /** - * Handles player respawns at the LOW priority. - * - * @param event The player respawn event. - */ - @EventHandler(priority = EventPriority.LOW) - public void lowPriorityRespawn(PlayerRespawnEvent event) { - if (!event.isBedSpawn()) { - this.handleRespawn(event, EventPriority.LOW); - } - } - - /** - * Handles player respawns at the NORMAL priority. - * - * @param event The player respawn event. - */ - @EventHandler(priority = EventPriority.NORMAL) - public void normalPriorityRespawn(PlayerRespawnEvent event) { - if (!event.isBedSpawn()) { - this.handleRespawn(event, EventPriority.NORMAL); - } - } - - /** - * Handles player respawns at the HIGH priority. - * - * @param event The player respawn event. - */ - @EventHandler(priority = EventPriority.HIGH) - public void highPriorityRespawn(PlayerRespawnEvent event) { - if (!event.isBedSpawn()) { - this.handleRespawn(event, EventPriority.HIGH); - } - } - - /** - * Handles player respawns at the HIGHEST priority. - * - * @param event The player respawn event. - */ - @EventHandler(priority = EventPriority.HIGHEST) - public void highestPriorityRespawn(PlayerRespawnEvent event) { - if (!event.isBedSpawn()) { - this.handleRespawn(event, EventPriority.HIGHEST); - } - } - - /** - * Handles player respawns at the MONITOR priority. - * - * @param event The player respawn event. - */ - @EventHandler(priority = EventPriority.MONITOR) - public void monitorPriorityRespawn(PlayerRespawnEvent event) { - if (!event.isBedSpawn()) { - this.handleRespawn(event, EventPriority.MONITOR); - this.updateCompass(event); - } - } - - private void handleRespawn(PlayerRespawnEvent event, EventPriority priority) { - for (WorldGroup group : this.currentGroups) { - if (group.getSpawnPriority().equals(priority)) { - String spawnWorldName = group.getSpawnWorld(); - if (spawnWorldName != null) { - MultiverseWorld mvWorld = this.inventories.getCore() - .getMVWorldManager().getMVWorld(spawnWorldName); - if (mvWorld != null) { - this.spawnLoc = mvWorld.getSpawnLocation(); - event.setRespawnLocation(this.spawnLoc); - break; - } - } - } - } - } - - private void updateCompass(PlayerRespawnEvent event) { - if (event.getRespawnLocation().equals(this.spawnLoc)) { - event.getPlayer().setCompassTarget(this.spawnLoc); - } - } - - @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - public void entityPortal(EntityPortalEvent event) { - Entity entity = event.getEntity(); - if (!(entity instanceof Item) && !(entity instanceof InventoryHolder)) { - return; - } - - World fromWorld = event.getFrom().getWorld(); - Location toLocation = event.getTo(); - if (toLocation == null) { - // Apparently this happens sometimes. - Logging.fine("Entity %s attempted to go to null location", entity); - return; - } - World toWorld = toLocation.getWorld(); - if (toWorld == null) { - // Apparently this also happens sometimes. - Logging.fine("Entity %s attempted to go to null world", entity); - return; - } - - if (fromWorld.equals(toWorld)) { - return; - } - - List fromGroups = inventories.getGroupManager().getGroupsForWorld(fromWorld.getName()); - List toGroups = inventories.getGroupManager().getGroupsForWorld(toWorld.getName()); - // We only care about the groups that have the inventory sharable - fromGroups = fromGroups.stream().filter(it -> it.isSharing(Sharables.INVENTORY)).collect(Collectors.toList()); - toGroups = toGroups.stream().filter(it -> it.isSharing(Sharables.INVENTORY)).collect(Collectors.toList()); - for (WorldGroup fromGroup : fromGroups) { - if (toGroups.contains(fromGroup)) { - Logging.finest("Allowing item or inventory holding %s to go from world %s to world %s", entity, - fromWorld.getName(), toWorld.getName()); - // The from and to destinations share at least one group that has the inventory sharable. - return; - } - } - - Logging.finest("Disallowing item or inventory holding %s to go from world %s to world %s since these" + - "worlds do not share inventories", entity, fromWorld.getName(), toWorld.getName()); - event.setCancelled(true); - } - - @EventHandler - public void worldUnload(WorldUnloadEvent event) { - String unloadWorldName = event.getWorld().getName(); - - Logging.finer("Clearing data for world/groups container with '%s' world.", unloadWorldName); - - ProfileContainer fromWorldProfileContainer = this.inventories.getWorldProfileContainerStore().getContainer(unloadWorldName); - fromWorldProfileContainer.clearContainer(); - - List fromGroups = this.inventories.getGroupManager().getGroupsForWorld(unloadWorldName); - for (WorldGroup fromGroup : fromGroups) { - fromGroup.getGroupProfileContainer().clearContainer(); - } - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java b/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java deleted file mode 100644 index d3d37d1c..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/MultiverseInventories.java +++ /dev/null @@ -1,471 +0,0 @@ -package com.onarandombox.multiverseinventories; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.MultiverseCore.MultiverseCore; -import com.onarandombox.MultiverseCore.api.MVPlugin; -import com.onarandombox.MultiverseCore.commands.HelpCommand; -import com.onarandombox.commandhandler.CommandHandler; -import com.onarandombox.multiverseinventories.profile.ProfileDataSource; -import com.onarandombox.multiverseinventories.profile.WorldGroupManager; -import com.onarandombox.multiverseinventories.profile.container.ContainerType; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainerStore; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.command.AddSharesCommand; -import com.onarandombox.multiverseinventories.command.AddWorldCommand; -import com.onarandombox.multiverseinventories.command.CreateGroupCommand; -import com.onarandombox.multiverseinventories.command.DeleteGroupCommand; -import com.onarandombox.multiverseinventories.command.GroupCommand; -import com.onarandombox.multiverseinventories.command.ImportCommand; -import com.onarandombox.multiverseinventories.command.InfoCommand; -import com.onarandombox.multiverseinventories.command.ListCommand; -import com.onarandombox.multiverseinventories.command.MigrateCommand; -import com.onarandombox.multiverseinventories.command.ReloadCommand; -import com.onarandombox.multiverseinventories.command.RemoveSharesCommand; -import com.onarandombox.multiverseinventories.command.RemoveWorldCommand; -import com.onarandombox.multiverseinventories.command.SpawnCommand; -import com.onarandombox.multiverseinventories.command.ToggleCommand; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.locale.Messager; -import com.onarandombox.multiverseinventories.locale.Messaging; -import com.onarandombox.multiverseinventories.migration.ImportManager; -import com.onarandombox.multiverseinventories.util.Perm; -import me.drayshak.WorldInventories.WorldInventories; -import org.bukkit.Bukkit; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.PluginDescriptionFile; -import org.bukkit.plugin.PluginManager; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.plugin.java.JavaPluginLoader; -import uk.co.tggl.pluckerpluck.multiinv.MultiInv; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Locale; -import java.util.logging.Level; - -/** - * Multiverse-Inventories plugin main class. - */ -public class MultiverseInventories extends JavaPlugin implements MVPlugin, Messaging { - - private static MultiverseInventories inventoriesPlugin; - - public static MultiverseInventories getPlugin() { - return inventoriesPlugin; - } - - private final int requiresProtocol = 22; - private final InventoriesListener inventoriesListener = new InventoriesListener(this); - private final AdventureListener adventureListener = new AdventureListener(this); - - private Messager messager = new DefaultMessager(this); - private WorldGroupManager worldGroupManager = null; - private ProfileContainerStore worldProfileContainerStore = null; - private ProfileContainerStore groupProfileContainerStore = null; - private ImportManager importManager = new ImportManager(this); - - private CommandHandler commandHandler = null; - private MultiverseCore core = null; - private InventoriesConfig config = null; - private FlatFileProfileDataSource data = null; - - private InventoriesDupingPatch dupingPatch; - - private File serverFolder = new File(System.getProperty("user.dir")); - - { - inventoriesPlugin = this; - } - - public MultiverseInventories() { - super(); - } - - /** - * This is for unit testing. - * @param loader The PluginLoader to use. - * @param description The Description file to use. - * @param dataFolder The folder that other datafiles can be found in. - * @param file The location of the plugin. - */ - public MultiverseInventories(JavaPluginLoader loader, PluginDescriptionFile description, File dataFolder, File file) { - super(loader, description, dataFolder, file); - } - - /** - * {@inheritDoc} - */ - @Override - public void onDisable() { - for (final Player player : getServer().getOnlinePlayers()) { - final String world = player.getWorld().getName(); - //getData().updateLastWorld(player.getName(), world); - if (getMVIConfig().usingLoggingSaveLoad()) { - ShareHandlingUpdater.updateProfile(this, player, new DefaultPersistingProfile(Sharables.allOf(), - getWorldProfileContainerStore().getContainer(world).getPlayerData(player))); - getData().setLoadOnLogin(player.getName(), true); - } - } - - this.dupingPatch.disable(); - - Logging.shutdown(); - } - - /** - * {@inheritDoc} - */ - @Override - public void onEnable() { - Logging.init(this); - Perm.register(this); - - MultiverseCore mvCore; - mvCore = (MultiverseCore) this.getServer().getPluginManager().getPlugin("Multiverse-Core"); - // Test if the Core was found, if not we'll disable this plugin. - if (mvCore == null) { - Logging.severe("Multiverse-Core not found, disabling..."); - this.getServer().getPluginManager().disablePlugin(this); - return; - } - this.setCore(mvCore); - - if (this.getCore().getProtocolVersion() < this.getRequiredProtocol()) { - Logging.severe("Your Multiverse-Core is OUT OF DATE"); - Logging.severe("This version of Multiverse-Inventories requires Protocol Level: " + this.getRequiredProtocol()); - Logging.severe("Your of Core Protocol Level is: " + this.getCore().getProtocolVersion()); - Logging.severe("Grab an updated copy at: "); - Logging.severe("http://bukkit.onarandombox.com/?dir=multiverse-core"); - this.getServer().getPluginManager().disablePlugin(this); - return; - } - - this.reloadConfig(); - - try { - this.getMessager().setLocale(new Locale(this.getMVIConfig().getLocale())); - } catch (IllegalArgumentException e) { - Logging.severe(e.getMessage()); - this.getServer().getPluginManager().disablePlugin(this); - return; - } - - // Initialize data class - //this.getWorldProfileContainerStore().setWorldProfiles(this.getData().getWorldProfiles()); - - Logging.setDebugLevel(getCore().getMVConfig().getGlobalDebug()); - - this.getCore().incrementPluginCount(); - - // Register Events - Bukkit.getPluginManager().registerEvents(inventoriesListener, this); - if (Bukkit.getPluginManager().getPlugin("Multiverse-Adventure") != null) { - Bukkit.getPluginManager().registerEvents(adventureListener, this); - } - if (getCore().getProtocolVersion() >= 24) { - new CoreDebugListener(this); - } - - // Register Commands - this.registerCommands(); - - // Hook plugins that can be imported from - this.hookImportables(); - - Sharables.init(this); - - this.dupingPatch = InventoriesDupingPatch.enableDupingPatch(this); - - // Display enable message/version info - Logging.log(true, Level.INFO, "enabled."); - } - - private void registerCommands() { - this.commandHandler = this.getCore().getCommandHandler(); - this.getCommandHandler().registerCommand(new InfoCommand(this)); - this.getCommandHandler().registerCommand(new ImportCommand(this)); - this.getCommandHandler().registerCommand(new ListCommand(this)); - this.getCommandHandler().registerCommand(new ReloadCommand(this)); - this.getCommandHandler().registerCommand(new AddWorldCommand(this)); - this.getCommandHandler().registerCommand(new RemoveWorldCommand(this)); - this.getCommandHandler().registerCommand(new AddSharesCommand(this)); - this.getCommandHandler().registerCommand(new RemoveSharesCommand(this)); - this.getCommandHandler().registerCommand(new CreateGroupCommand(this)); - this.getCommandHandler().registerCommand(new DeleteGroupCommand(this)); - this.getCommandHandler().registerCommand(new SpawnCommand(this)); - this.getCommandHandler().registerCommand(new GroupCommand(this)); - this.getCommandHandler().registerCommand(new ToggleCommand(this)); - this.getCommandHandler().registerCommand(new MigrateCommand(this)); - for (com.onarandombox.commandhandler.Command c : this.commandHandler.getAllCommands()) { - if (c instanceof HelpCommand) { - c.addKey("mvinv"); - } - } - } - - private void hookImportables() { - final PluginManager pm = Bukkit.getPluginManager(); - Plugin plugin = pm.getPlugin("MultiInv"); - if (plugin != null) { - this.getImportManager().hookMultiInv((MultiInv) plugin); - } - plugin = pm.getPlugin("WorldInventories"); - if (plugin != null) { - this.getImportManager().hookWorldInventories((WorldInventories) plugin); - } - } - - /** - * @return A class used for managing importing data from other similar plugins. - */ - public ImportManager getImportManager() { - return this.importManager; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean onCommand(CommandSender sender, Command command, String commandLabel, String[] args) { - if (!this.isEnabled()) { - sender.sendMessage("This plugin is Disabled!"); - return true; - } - ArrayList allArgs = new ArrayList(Arrays.asList(args)); - allArgs.add(0, command.getName()); - return this.getCommandHandler().locateAndRunCommand(sender, allArgs); - } - - private CommandHandler getCommandHandler() { - return this.commandHandler; - } - - /** - * {@inheritDoc} - */ - @Override - public void log(Level level, String msg) { - Logging.log(level, msg); - } - - /** - * {@inheritDoc} - */ - @Override - public MultiverseCore getCore() { - return this.core; - } - - /** - * {@inheritDoc} - */ - @Override - public void setCore(MultiverseCore core) { - this.core = core; - } - - /** - * {@inheritDoc} - */ - @Override - public int getProtocolVersion() { - return 1; - } - - /** - * {@inheritDoc} - */ - @Override - public String dumpVersionInfo(String buffer) { - buffer += logAndAddToPasteBinBuffer("=== Settings ==="); - buffer += logAndAddToPasteBinBuffer("First Run: " + this.getMVIConfig().isFirstRun()); - buffer += logAndAddToPasteBinBuffer("Using Bypass: " + this.getMVIConfig().isUsingBypass()); - buffer += logAndAddToPasteBinBuffer("Default Ungrouped Worlds: " + this.getMVIConfig().isDefaultingUngroupedWorlds()); - buffer += logAndAddToPasteBinBuffer("Save and Load on Log In and Out: " + this.getMVIConfig().usingLoggingSaveLoad()); - buffer += logAndAddToPasteBinBuffer("Using GameMode Profiles: " + this.getMVIConfig().isUsingGameModeProfiles()); - buffer += logAndAddToPasteBinBuffer("=== Shares ==="); - buffer += logAndAddToPasteBinBuffer("Optionals for Ungrouped Worlds: " + this.getMVIConfig().usingOptionalsForUngrouped()); - buffer += logAndAddToPasteBinBuffer("Enabled Optionals: " + this.getMVIConfig().getOptionalShares()); - buffer += logAndAddToPasteBinBuffer("=== Groups ==="); - for (WorldGroup group : this.getGroupManager().getGroups()) { - buffer += logAndAddToPasteBinBuffer(group.toString()); - } - return buffer; - } - - /** - * Builds a String containing Multiverse-Inventories' version info. - * - * @return The version info. - */ - public String getVersionInfo() { - StringBuilder versionInfo = new StringBuilder("[Multiverse-Inventories] Multiverse-Inventories Version: " + this.getDescription().getVersion() + '\n' - + "[Multiverse-Inventories] === Settings ===" + '\n' - + "[Multiverse-Inventories] First Run: " + this.getMVIConfig().isFirstRun() + '\n' - + "[Multiverse-Inventories] Using Bypass: " + this.getMVIConfig().isUsingBypass() + '\n' - + "[Multiverse-Inventories] Default Ungrouped Worlds: " + this.getMVIConfig().isDefaultingUngroupedWorlds() + '\n' - + "[Multiverse-Inventories] Save and Load on Log In and Out: " + this.getMVIConfig().usingLoggingSaveLoad() + '\n' - + "[Multiverse-Inventories] Using GameMode Profiles: " + this.getMVIConfig().isUsingGameModeProfiles() + '\n' - + "[Multiverse-Inventories] === Shares ===" + '\n' - + "[Multiverse-Inventories] Optionals for Ungrouped Worlds: " + this.getMVIConfig().usingOptionalsForUngrouped() + '\n' - + "[Multiverse-Inventories] Enabled Optionals: " + this.getMVIConfig().getOptionalShares() + '\n' - + "[Multiverse-Inventories] === Groups ===" + '\n'); - - for (WorldGroup group : this.getGroupManager().getGroups()) { - versionInfo.append("[Multiverse-Inventories] ").append(group.toString()).append('\n'); - } - - return versionInfo.toString(); - } - - private String logAndAddToPasteBinBuffer(String string) { - Logging.info(string); - return Logging.getPrefixedMessage(string + '\n', false); - } - - /** - * @return the Config object which contains settings for this plugin. - */ - public InventoriesConfig getMVIConfig() { - return this.config; - } - - /** - * Nulls the config object and reloads a new one, also resetting the world groups in memory. - */ - @Override - public void reloadConfig() { - try { - this.config = new InventoriesConfig(this); - this.worldGroupManager = new YamlWorldGroupManager(this, this.config.getConfig()); - this.worldProfileContainerStore = new WeakProfileContainerStore(this, ContainerType.WORLD); - this.groupProfileContainerStore = new WeakProfileContainerStore(this, ContainerType.GROUP); - - if (data != null) { - this.data.clearCache(); - } - - //this.data = null; - Logging.fine("Loaded config file!"); - } catch (IOException e) { // Catch errors loading the config file and exit out if found. - Logging.severe(this.getMessager().getMessage(Message.ERROR_CONFIG_LOAD)); - Logging.severe(e.getMessage()); - Bukkit.getPluginManager().disablePlugin(this); - return; - } - - this.getServer().getScheduler().scheduleSyncDelayedTask(this, new Runnable() { - @Override - public void run() { - // Create initial World Group for first run IF NO GROUPS EXIST - if (getMVIConfig().isFirstRun()) { - Logging.info("First run!"); - if (getGroupManager().getGroups().isEmpty()) { - getGroupManager().createDefaultGroup(); - } - - getMVIConfig().setFirstRun(false); - } - getGroupManager().checkForConflicts(null); - } - }, 1L); - } - - /** - * @return the PlayerData object which contains data for this plugin. - */ - public ProfileDataSource getData() { - if (this.data == null) { - // Loads the data - try { - this.data = new FlatFileProfileDataSource(this); - } catch (IOException e) { // Catch errors loading the language file and exit out if found. - Logging.severe(this.getMessager().getMessage(Message.ERROR_DATA_LOAD)); - Logging.severe(e.getMessage()); - Bukkit.getPluginManager().disablePlugin(this); - return null; - } - } - return this.data; - } - - /** - * {@inheritDoc} - */ - @Override - public Messager getMessager() { - return messager; - } - - /** - * {@inheritDoc} - */ - @Override - public void setMessager(Messager messager) { - if (messager == null) { - throw new IllegalArgumentException("The new messager can't be null!"); - } - this.messager = messager; - } - - /** - * @return The required protocol version of core. - */ - public int getRequiredProtocol() { - return this.requiresProtocol; - } - - /** - * @return The World Group manager for this plugin. - */ - public WorldGroupManager getGroupManager() { - return this.worldGroupManager; - } - - /** - * Returns the world profile container store for this plugin. - *

Player profiles for an individual world can be found here.

- * - * @return the world profile container store for this plugin. - */ - public ProfileContainerStore getWorldProfileContainerStore() { - return worldProfileContainerStore; - } - - /** - * Returns the group profile container store for this plugin. - *

Player profiles for a world group can be found here.

- * - * @return the group profile container store for this plugin. - */ - public ProfileContainerStore getGroupProfileContainerStore() { - return groupProfileContainerStore; - } - - /** - * Gets the server's root-folder as {@link File}. - * - * @return The server's root-folder - */ - public File getServerFolder() { - return serverFolder; - } - - /** - * Sets this server's root-folder. - * - * @param newServerFolder The new server-root - */ - public void setServerFolder(File newServerFolder) { - if (!newServerFolder.isDirectory()) { - throw new IllegalArgumentException("That's not a folder!"); - } - this.serverFolder = newServerFolder; - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/ShareHandler.java b/src/main/java/com/onarandombox/multiverseinventories/ShareHandler.java deleted file mode 100644 index 25008ab6..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/ShareHandler.java +++ /dev/null @@ -1,162 +0,0 @@ -package com.onarandombox.multiverseinventories; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.event.ShareHandlingEvent; -import com.onarandombox.multiverseinventories.profile.PlayerProfile; -import com.onarandombox.multiverseinventories.share.PersistingProfile; -import com.onarandombox.multiverseinventories.share.Shares; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import java.util.LinkedList; -import java.util.List; - -import static com.onarandombox.multiverseinventories.share.Sharables.allOf; - -/** - * Abstract class for handling sharing of data between worlds and game modes. - */ -public abstract class ShareHandler { - - protected final MultiverseInventories inventories; - protected final Player player; - final AffectedProfiles affectedProfiles; - - ShareHandler(MultiverseInventories inventories, Player player) { - this.inventories = inventories; - this.player = player; - this.affectedProfiles = new AffectedProfiles(); - } - - /** - * Finalizes the transfer from one world to another. This handles the switching - * inventories/stats for a player and persisting the changes. - */ - final void handleSharing() { - ShareHandlingEvent event = this.createEvent(); - - Bukkit.getPluginManager().callEvent(event); - if (!event.isCancelled()) { - this.completeSharing(event); - } - } - - protected final void setAlwaysWriteProfile(PlayerProfile profile) { - affectedProfiles.setAlwaysWriteProfile(profile); - } - - /** - * @param profile The player profile that will need data saved to. - * @param shares What from this group needs to be saved. - */ - protected final void addWriteProfile(PlayerProfile profile, Shares shares) { - affectedProfiles.addWriteProfile(profile, shares); - } - - /** - * Finalizes the transfer from one world to another. This handles the switching - * inventories/stats for a player and persisting the changes. - * - * @param profile The player profile that will need data loaded from. - * @param shares What from this group needs to be loaded. - */ - protected final void addReadProfile(PlayerProfile profile, Shares shares) { - affectedProfiles.addReadProfile(profile, shares); - } - - protected abstract ShareHandlingEvent createEvent(); - - protected void logBypass() { - Logging.fine(player.getName() + " has bypass permission for 1 or more world/groups!"); - } - - private void completeSharing(ShareHandlingEvent event) { - logAffectedProfilesCount(event); - saveAlwaysWriteProfile(event); - handleProfileChanges(event); - logHandlingComplete(event); - } - - private void logAffectedProfilesCount(ShareHandlingEvent event) { - PersistingProfile alwaysWriteProfile = event.getAlwaysWriteProfile(); - int writeProfiles = event.getWriteProfiles().size() + (alwaysWriteProfile != null ? 1 : 0); - - Logging.finer("Change affected by %d fromProfiles and %d toProfiles", writeProfiles, - event.getReadProfiles().size()); - } - - private void saveAlwaysWriteProfile(ShareHandlingEvent event) { - if (event.getAlwaysWriteProfile() != null) { - ShareHandlingUpdater.updateProfile(inventories, event.getPlayer(), event.getAlwaysWriteProfile()); - } else { - Logging.warning("No fromWorld to save to"); - } - } - - private void handleProfileChanges(ShareHandlingEvent event) { - if (event.getReadProfiles().isEmpty()) { - Logging.finest("No profiles to read from - nothing more to do."); - } else { - updateProfiles(event.getPlayer(), event.getWriteProfiles()); - updatePlayer(event.getPlayer(), event.getReadProfiles()); - } - } - - private void updateProfiles(Player player, List writeProfiles) { - for (PersistingProfile writeProfile : writeProfiles) { - ShareHandlingUpdater.updateProfile(inventories, player, writeProfile); - } - } - - private void updatePlayer(Player player, List readProfiles) { - for (PersistingProfile readProfile : readProfiles) { - ShareHandlingUpdater.updatePlayer(inventories, player, readProfile); - } - } - - private void logHandlingComplete(ShareHandlingEvent event) { - Logging.finer("=== %s complete for %s ===", event.getPlayer().getName(), event.getEventName()); - } - - public static class AffectedProfiles { - - private PersistingProfile alwaysWriteProfile; - private final List writeProfiles = new LinkedList<>(); - private final List readProfiles = new LinkedList<>(); - - AffectedProfiles() { } - - protected final void setAlwaysWriteProfile(PlayerProfile profile) { - alwaysWriteProfile = new DefaultPersistingProfile(allOf(), profile); - } - - /** - * @param profile The player profile that will need data saved to. - * @param shares What from this group needs to be saved. - */ - protected final void addWriteProfile(PlayerProfile profile, Shares shares) { - writeProfiles.add(new DefaultPersistingProfile(shares, profile)); - } - - /** - * @param profile The player profile that will need data loaded from. - * @param shares What from this group needs to be loaded. - */ - protected final void addReadProfile(PlayerProfile profile, Shares shares) { - readProfiles.add(new DefaultPersistingProfile(shares, profile)); - } - - public PersistingProfile getAlwaysWriteProfile() { - return alwaysWriteProfile; - } - - public List getWriteProfiles() { - return writeProfiles; - } - - public List getReadProfiles() { - return readProfiles; - } - } - -} diff --git a/src/main/java/com/onarandombox/multiverseinventories/ShareHandlingUpdater.java b/src/main/java/com/onarandombox/multiverseinventories/ShareHandlingUpdater.java deleted file mode 100644 index 5409a059..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/ShareHandlingUpdater.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.onarandombox.multiverseinventories; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.profile.container.ContainerType; -import com.onarandombox.multiverseinventories.share.PersistingProfile; -import com.onarandombox.multiverseinventories.share.Sharable; -import com.onarandombox.multiverseinventories.share.Sharables; -import org.apache.commons.lang.StringUtils; -import org.bukkit.entity.Player; - -import java.util.ArrayList; -import java.util.List; - -class ShareHandlingUpdater { - - static void updateProfile(final MultiverseInventories inventories, - final Player player, - final PersistingProfile profile) { - new ShareHandlingUpdater(inventories, player, profile).updateProfile(); - } - - static void updatePlayer(final MultiverseInventories inventories, - final Player player, - final PersistingProfile profile) { - new ShareHandlingUpdater(inventories, player, profile).updatePlayer(); - } - - private final MultiverseInventories inventories; - private final Player player; - private final PersistingProfile profile; - - private final List> saved = new ArrayList<>(Sharables.all().size()); - private final List> loaded = new ArrayList<>(Sharables.all().size()); - private final List> defaulted = new ArrayList<>(Sharables.all().size()); - - private ShareHandlingUpdater(MultiverseInventories inventories, Player player, PersistingProfile profile) { - this.inventories = inventories; - this.player = player; - this.profile = profile; - } - - private void updateProfile() { - for (Sharable sharable : profile.getShares()) { - if (isSharableUsed(sharable)) { - saved.add(sharable); - sharable.getHandler().updateProfile(profile.getProfile(), player); - } - } - if (saved.size() > 0) { - Logging.finer("Persisted: " + StringUtils.join(saved, ", ") + " to " - + profile.getProfile().getContainerType() + ":" + profile.getProfile().getContainerName() - + " (" + profile.getProfile().getProfileType() + ")" - + " for player " + profile.getProfile().getPlayer().getName()); - } - inventories.getData().updatePlayerData(profile.getProfile()); - } - - private void updatePlayer() { - player.closeInventory(); - for (Sharable sharable : profile.getShares()) { - if (isSharableUsed(sharable)) { - if (sharable.getHandler().updatePlayer(player, profile.getProfile())) { - loaded.add(sharable); - } else { - defaulted.add(sharable); - } - } - } - if (!loaded.isEmpty()) { - Logging.finer("Updated: " + loaded.toString() + " for " - + profile.getProfile().getPlayer().getName() + " for " - + profile.getProfile().getContainerType() + ":" + profile.getProfile().getContainerName() - + " (" + profile.getProfile().getProfileType() + ")"); - } - if (!defaulted.isEmpty()) { - Logging.finer("Defaulted: " + defaulted.toString() + " for " - + profile.getProfile().getPlayer().getName() + " for " - + profile.getProfile().getContainerType() + ":" + profile.getProfile().getContainerName() - + " (" + profile.getProfile().getProfileType() + ")"); - } - } - - private boolean isSharableUsed(Sharable sharable) { - if (sharable.isOptional()) { - if (!inventories.getMVIConfig().getOptionalShares().contains(sharable)) { - Logging.finest("Ignoring optional share: " + sharable.getNames()[0]); - return false; - } - if (profile.getProfile().getContainerType() == ContainerType.WORLD - && !inventories.getMVIConfig().usingOptionalsForUngrouped()) { - Logging.finest("Ignoring optional share '" + sharable.getNames()[0] + "' for ungrouped world!"); - return false; - } - } - return true; - } -} diff --git a/src/main/java/com/onarandombox/multiverseinventories/WeakProfileContainer.java b/src/main/java/com/onarandombox/multiverseinventories/WeakProfileContainer.java deleted file mode 100644 index eb34c970..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/WeakProfileContainer.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.onarandombox.multiverseinventories; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.profile.ProfileDataSource; -import com.onarandombox.multiverseinventories.profile.ProfileKey; -import com.onarandombox.multiverseinventories.profile.WorldGroupManager; -import com.onarandombox.multiverseinventories.profile.ProfileTypes; -import com.onarandombox.multiverseinventories.profile.container.ContainerType; -import com.onarandombox.multiverseinventories.profile.PlayerProfile; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainer; -import com.onarandombox.multiverseinventories.profile.ProfileType; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainerStore; -import org.bukkit.OfflinePlayer; -import org.bukkit.entity.Player; - -import java.util.HashMap; -import java.util.Map; -import java.util.WeakHashMap; - -/** - * Implementation of ProfileContainer using WeakHashMaps to keep memory usage to a minimum. - */ -final class WeakProfileContainer implements ProfileContainer { - - private Map> playerData = new WeakHashMap<>(); - private final MultiverseInventories inventories; - private final String name; - private final ContainerType type; - - WeakProfileContainer(MultiverseInventories inventories, String name, ContainerType type) { - this.inventories = inventories; - this.name = name; - this.type = type; - } - - /** - * Gets the stored profiles for this player, mapped by ProfileType. - * - * @param name The name of player to get profile map for. - * @return The profile map for the given player. - */ - protected Map getPlayerData(String name) { - return this.playerData.computeIfAbsent(name, k -> new HashMap<>()); - } - - protected ProfileDataSource getDataSource() { - return this.getInventories().getData(); - } - - protected WorldGroupManager getGroupManager() { - return this.getInventories().getGroupManager(); - } - - protected ProfileContainerStore getProfileManager() { - return this.getInventories().getWorldProfileContainerStore(); - } - - protected MultiverseInventories getInventories() { - return this.inventories; - } - - @Override - public PlayerProfile getPlayerData(Player player) { - ProfileType type; - if (inventories.getMVIConfig().isUsingGameModeProfiles()) { - type = ProfileTypes.forGameMode(player.getGameMode()); - } else { - type = ProfileTypes.SURVIVAL; - } - return getPlayerData(type, player); - } - - @Override - public PlayerProfile getPlayerData(ProfileType profileType, OfflinePlayer player) { - Map profileMap = this.getPlayerData(player.getName()); - PlayerProfile playerProfile = profileMap.get(profileType); - if (playerProfile == null) { - playerProfile = getDataSource().getPlayerData(getContainerType(), - getContainerName(), profileType, player.getUniqueId()); - Logging.finer("[%s - %s - %s - %s] not cached, loading from disk...", - profileType, getContainerType(), playerProfile.getContainerName(), player.getName()); - profileMap.put(profileType, playerProfile); - } - return playerProfile; - } - - @Override - public void addPlayerData(PlayerProfile playerProfile) { - this.getPlayerData(playerProfile.getPlayer().getName()).put(playerProfile.getProfileType(), playerProfile); - } - - @Override - public void removeAllPlayerData(OfflinePlayer player) { - this.getPlayerData(player.getName()).clear(); - this.getDataSource().removePlayerData(getContainerType(), getContainerName(), null, player.getName()); - } - - @Override - public void removePlayerData(ProfileType profileType, OfflinePlayer player) { - this.getPlayerData(player.getName()).remove(profileType); - this.getDataSource().removePlayerData(getContainerType(), getContainerName(), profileType, player.getName()); - } - - @Override - public String getContainerName() { - return name; - } - - @Override - public ContainerType getContainerType() { - return type; - } - - @Override - public void clearContainer() { - for (Map profiles : playerData.values()) { - for (PlayerProfile profile : profiles.values()) { - this.getDataSource().clearProfileCache(ProfileKey.createProfileKey(profile)); - } - } - this.playerData.clear(); - } -} diff --git a/src/main/java/com/onarandombox/multiverseinventories/WeakProfileContainerStore.java b/src/main/java/com/onarandombox/multiverseinventories/WeakProfileContainerStore.java deleted file mode 100644 index ad853c80..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/WeakProfileContainerStore.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.onarandombox.multiverseinventories; - -import com.onarandombox.multiverseinventories.profile.container.ContainerType; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainer; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainerStore; - -import java.util.Map; -import java.util.WeakHashMap; - -final class WeakProfileContainerStore implements ProfileContainerStore { - - private final Map containers = new WeakHashMap<>(); - - private final MultiverseInventories inventories; - private final ContainerType containerType; - - WeakProfileContainerStore(MultiverseInventories inventories, ContainerType containerType) { - this.inventories = inventories; - this.containerType = containerType; - } - - private MultiverseInventories getInventories() { - return this.inventories; - } - - @Override - public void addContainer(ProfileContainer container) { - this.containers.put(container.getContainerName().toLowerCase(), container); - } - - @Override - public ProfileContainer getContainer(String containerName) { - ProfileContainer container = this.containers.get(containerName.toLowerCase()); - if (container == null) { - container = new WeakProfileContainer(this.getInventories(), containerName, containerType); - addContainer(container); - } - return container; - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/WorldChangeShareHandler.java b/src/main/java/com/onarandombox/multiverseinventories/WorldChangeShareHandler.java deleted file mode 100644 index 33662872..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/WorldChangeShareHandler.java +++ /dev/null @@ -1,231 +0,0 @@ -package com.onarandombox.multiverseinventories; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.event.ShareHandlingEvent; -import com.onarandombox.multiverseinventories.event.WorldChangeShareHandlingEvent; -import com.onarandombox.multiverseinventories.profile.PlayerProfile; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainer; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.share.Shares; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.entity.Player; - -import java.util.List; - -/** - * WorldChange implementation of ShareHandler. - */ -final class WorldChangeShareHandler extends ShareHandler { - - private final String fromWorld; - private final String toWorld; - private final List fromWorldGroups; - private final List toWorldGroups; - - WorldChangeShareHandler(MultiverseInventories inventories, Player player, String fromWorld, String toWorld) { - super(inventories, player); - this.fromWorld = fromWorld; - this.toWorld = toWorld; - - // Get any groups we may need to save stuff to. - this.fromWorldGroups = getAffectedWorldGroups(fromWorld); - // Get any groups we may need to load stuff from. - this.toWorldGroups = getAffectedWorldGroups(toWorld); - - prepareProfiles(); - } - - private List getAffectedWorldGroups(String world) { - return this.inventories.getGroupManager().getGroupsForWorld(world); - } - - @Override - protected ShareHandlingEvent createEvent() { - return new WorldChangeShareHandlingEvent(player, affectedProfiles, fromWorld, toWorld); - } - - private void prepareProfiles() { - Logging.finer("=== %s traveling from world: %s to world: %s ===", player.getName(), fromWorld, toWorld); - - setAlwaysWriteWorldProfile(); - - if (isPlayerAffectedByChange()) { - addProfiles(); - } - } - - private void setAlwaysWriteWorldProfile() { - // We will always save everything to the world they come from. - PlayerProfile fromWorldProfile = getWorldPlayerProfile(fromWorld, player); - setAlwaysWriteProfile(fromWorldProfile); - } - - private PlayerProfile getWorldPlayerProfile(String world, Player player) { - return getWorldProfile(world).getPlayerData(player); - } - - private ProfileContainer getWorldProfile(String world) { - return inventories.getWorldProfileContainerStore().getContainer(world); - } - - private boolean isPlayerAffectedByChange() { - if (isPlayerBypassingChange()) { - logBypass(); - return false; - } - return true; - } - - private boolean isPlayerBypassingChange() { - return Perm.BYPASS_WORLD.hasBypass(player, fromWorld); - } - - private void addProfiles() { - addWriteProfiles(); - new ReadProfilesAggregator().addReadProfiles(); - } - - private void addWriteProfiles() { - if (hasFromWorldGroups()) { - fromWorldGroups.forEach(wg -> new WorldGroupWrapper(wg).conditionallyAddWriteProfiles()); - } else { - Logging.finer("No groups for fromWorld."); - } - } - - private boolean hasFromWorldGroups() { - return !fromWorldGroups.isEmpty(); - } - - private class ReadProfilesAggregator { - - private Shares sharesToRead; - - private void addReadProfiles() { - sharesToRead = Sharables.noneOf(); - addReadProfilesFromToWorldGroups(); - useToWorldForMissingShares(); - } - - private void addReadProfilesFromToWorldGroups() { - if (hasToWorldGroups()) { - toWorldGroups.forEach(this::conditionallyAddReadProfileForWorldGroup); - } else { - Logging.finer("No groups for toWorld."); - } - } - - private boolean hasToWorldGroups() { - return !toWorldGroups.isEmpty(); - } - - private void conditionallyAddReadProfileForWorldGroup(WorldGroup worldGroup) { - if (isPlayerAffectedByChange(worldGroup)) { - if (isFromWorldNotInToWorldGroup(worldGroup)) { - addReadProfileForWorldGroup(worldGroup); - } else { - sharesToRead.addAll(worldGroup.getShares()); - } - } - } - - private boolean isPlayerAffectedByChange(WorldGroup worldGroup) { - if (isPlayerBypassingChange(worldGroup)) { - logBypass(); - return false; - } - return true; - } - - private boolean isPlayerBypassingChange(WorldGroup worldGroup) { - return Perm.BYPASS_GROUP.hasBypass(player, worldGroup.getName()); - } - - private boolean isFromWorldNotInToWorldGroup(WorldGroup worldGroup) { - return !worldGroup.containsWorld(fromWorld); - } - - private void addReadProfileForWorldGroup(WorldGroup worldGroup) { - PlayerProfile playerProfile = getWorldGroupPlayerData(worldGroup); - Shares sharesToAdd = getWorldGroupShares(worldGroup); - - addReadProfile(playerProfile, sharesToAdd); - sharesToRead.addAll(sharesToAdd); - } - - private PlayerProfile getWorldGroupPlayerData(WorldGroup worldGroup) { - return getWorldGroupProfileContainer(worldGroup).getPlayerData(player); - } - - private ProfileContainer getWorldGroupProfileContainer(WorldGroup worldGroup) { - return worldGroup.getGroupProfileContainer(); - } - - private Shares getWorldGroupShares(WorldGroup worldGroup) { - return Sharables.fromShares(worldGroup.getShares()); - } - - private void useToWorldForMissingShares() { - // We need to fill in any sharables that are not going to be transferred with what's saved in the world file. - if (hasUnhandledShares()) { - addUnhandledSharesFromToWorld(); - } - } - - private boolean hasUnhandledShares() { - return !sharesToRead.isSharing(Sharables.all()); - } - - private void addUnhandledSharesFromToWorld() { - Shares unhandledShares = Sharables.complimentOf(sharesToRead); - - Logging.finer("%s are left unhandled, defaulting to toWorld", unhandledShares); - - addReadProfile(getToWorldPlayerData(), unhandledShares); - } - - private PlayerProfile getToWorldPlayerData() { - return getToWorldProfileContainer().getPlayerData(player); - } - - private ProfileContainer getToWorldProfileContainer() { - return inventories.getWorldProfileContainerStore().getContainer(toWorld); - } - } - - private class WorldGroupWrapper { - private final WorldGroup worldGroup; - - public WorldGroupWrapper(WorldGroup worldGroup) { - this.worldGroup = worldGroup; - } - - private void conditionallyAddWriteProfiles() { - if (isEligibleForWrite()) { - addWriteProfiles(); - } - } - - boolean isEligibleForWrite() { - return groupDoesNotContainWorld(toWorld) || isNotSharingAll(); - } - - private boolean groupDoesNotContainWorld(String world) { - return !worldGroup.containsWorld(world); - } - - private boolean isNotSharingAll() { - return !worldGroup.getShares().isSharing(Sharables.all()); - } - - void addWriteProfiles() { - ProfileContainer container = worldGroup.getGroupProfileContainer(); - affectedProfiles.addWriteProfile(container.getPlayerData(player), getWorldGroupShares()); - } - - private Shares getWorldGroupShares() { - return Sharables.fromShares(worldGroup.getShares()); - } - } - -} diff --git a/src/main/java/com/onarandombox/multiverseinventories/blacklist/ItemBlacklist.java b/src/main/java/com/onarandombox/multiverseinventories/blacklist/ItemBlacklist.java deleted file mode 100644 index 049ca185..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/blacklist/ItemBlacklist.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.onarandombox.multiverseinventories.blacklist; - -/** - * Placeholder. - */ -public interface ItemBlacklist { - - -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/blacklist/SimpleItemBlacklist.java b/src/main/java/com/onarandombox/multiverseinventories/blacklist/SimpleItemBlacklist.java deleted file mode 100644 index e0395f3b..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/blacklist/SimpleItemBlacklist.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.onarandombox.multiverseinventories.blacklist; - -/** - * Simple Implementation of ItemBlacklist. - */ -public class SimpleItemBlacklist implements ItemBlacklist { - - -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/blacklist/package-info.java b/src/main/java/com/onarandombox/multiverseinventories/blacklist/package-info.java deleted file mode 100644 index bf4177e5..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/blacklist/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * This package contains classes related to Item Blacklisting per world group/world. - * It is also very unfinished and likely to be refactored later. - */ -package com.onarandombox.multiverseinventories.blacklist; - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/AddSharesCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/AddSharesCommand.java deleted file mode 100644 index e3e98458..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/AddSharesCommand.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.share.Shares; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.command.CommandSender; - -import java.util.List; - -/** - * The /mvinv addshares Command. - * @deprecated Deprecated in favor of /mvinv group. - */ -@Deprecated -public class AddSharesCommand extends InventoriesCommand { - - public AddSharesCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("Adds share(s) to a World Group."); - this.setCommandUsage("/mvinv addshares {SHARE[,EXTRA]} {GROUP}"); - this.setArgRange(2, 2); - this.addKey("mvinv addshares"); - this.addKey("mvinv addshare"); - this.addKey("mvinv adds"); - this.addKey("mvinvas"); - this.addKey("mvinvadds"); - this.addKey("mvinvaddshares"); - this.addKey("mvinvaddshare"); - this.setPermission(Perm.COMMAND_ADDSHARES.getPermission()); - } - - @Override - public void runCommand(CommandSender sender, List args) { - Shares newShares; - Shares negativeShares; - if (args.get(0).contains("all") || args.get(0).contains("everything") || args.get(0).contains("*")) { - newShares = Sharables.allOf(); - negativeShares = Sharables.noneOf(); - } else if (args.get(0).contains("-all") || args.get(0).contains("-everything") || args.get(0).contains("-*")) { - negativeShares = Sharables.allOf(); - newShares = Sharables.noneOf(); - } else { - negativeShares = Sharables.noneOf(); - newShares = Sharables.noneOf(); - String[] sharesString = args.get(0).split(","); - for (String shareString : sharesString) { - if (shareString.startsWith("-") && shareString.length() > 1) { - Shares shares = Sharables.lookup(shareString.substring(1)); - if (shares == null) { - continue; - } - negativeShares.setSharing(shares, true); - } else { - Shares shares = Sharables.lookup(shareString); - if (shares == null) { - continue; - } - newShares.setSharing(shares, true); - } - } - } - if (newShares.isEmpty() && negativeShares.isEmpty()) { - this.messager.normal(Message.ERROR_NO_SHARES_SPECIFIED, sender, args.get(0)); - return; - } - WorldGroup worldGroup = this.plugin.getGroupManager().getGroup(args.get(1)); - if (worldGroup == null) { - this.messager.normal(Message.ERROR_NO_GROUP, sender, args.get(1)); - return; - } - worldGroup.getShares().mergeShares(newShares); - worldGroup.getShares().removeAll(negativeShares); - this.plugin.getGroupManager().updateGroup(worldGroup); - this.plugin.getMVIConfig().save(); - this.messager.normal(Message.NOW_SHARING, sender, worldGroup.getName(), - worldGroup.getShares().toString()); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/AddWorldCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/AddWorldCommand.java deleted file mode 100644 index e4812a0a..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/AddWorldCommand.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.command.CommandSender; - -import java.util.List; - -/** - * The /mvinv addworld Command. - * @deprecated Deprecated in favor of /mvinv group. - */ -@Deprecated -public class AddWorldCommand extends InventoriesCommand { - - public AddWorldCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("Adds a World to a World Group."); - this.setCommandUsage("/mvinv addworld {WORLD} {GROUP}"); - this.setArgRange(2, 2); - this.addKey("mvinv addworld"); - this.addKey("mvinv addw"); - this.addKey("mvinvaw"); - this.addKey("mvinvaddw"); - this.addKey("mvinvaddworld"); - this.setPermission(Perm.COMMAND_ADDWORLD.getPermission()); - } - - @Override - public void runCommand(CommandSender sender, List args) { - World world = Bukkit.getWorld(args.get(0)); - if (world == null) { - this.messager.normal(Message.ERROR_NO_WORLD, sender, args.get(0)); - return; - } - WorldGroup worldGroup = this.plugin.getGroupManager().getGroup(args.get(1)); - if (worldGroup == null) { - this.messager.normal(Message.ERROR_NO_GROUP, sender, args.get(1)); - return; - } - if (worldGroup.containsWorld(world.getName())) { - this.messager.normal(Message.WORLD_ALREADY_EXISTS, sender, world.getName(), - worldGroup.getName()); - return; - } - worldGroup.addWorld(world); - this.plugin.getGroupManager().updateGroup(worldGroup); - this.plugin.getMVIConfig().save(); - this.messager.normal(Message.WORLD_ADDED, sender, world.getName(), - worldGroup.getName()); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/CreateGroupCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/CreateGroupCommand.java deleted file mode 100644 index d9da3a99..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/CreateGroupCommand.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.command.CommandSender; - -import java.util.List; - -/** - * The /mvinv creategroup Command. - */ -public class CreateGroupCommand extends InventoriesCommand { - - public CreateGroupCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("Creates a new World Group with no worlds and no shares."); - this.setCommandUsage("/mvinv creategroup {NAME}"); - this.setArgRange(1, 1); - this.addKey("mvinv creategroup"); - this.addKey("mvinv createg"); - this.addKey("mvinv cg"); - this.addKey("mvinvcreategroup"); - this.addKey("mvinvcreateg"); - this.addKey("mvinvcg"); - this.setPermission(Perm.COMMAND_CREATEGROUP.getPermission()); - } - - @Override - public void runCommand(CommandSender sender, List args) { - WorldGroup worldGroup = this.plugin.getGroupManager().getGroup(args.get(0)); - if (worldGroup != null) { - this.messager.normal(Message.GROUP_EXISTS, sender, args.get(0)); - return; - } - - worldGroup = this.plugin.getGroupManager().newEmptyGroup(args.get(0)); - this.plugin.getGroupManager().updateGroup(worldGroup); - this.messager.normal(Message.GROUP_CREATION_COMPLETE, sender); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/DeleteGroupCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/DeleteGroupCommand.java deleted file mode 100644 index dd8c903a..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/DeleteGroupCommand.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.command.CommandSender; - -import java.util.List; - -/** - * The /mvinv deletegroup Command. - */ -public class DeleteGroupCommand extends InventoriesCommand { - - public DeleteGroupCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("Deletes a World Group."); - this.setCommandUsage("/mvinv deletegroup {NAME}"); - this.setArgRange(1, 1); - this.addKey("mvinv deletegroup"); - this.addKey("mvinv deleteg"); - this.addKey("mvinv dg"); - this.addKey("mvinvdeletegroup"); - this.addKey("mvinvdeleteg"); - this.addKey("mvinvdg"); - this.setPermission(Perm.COMMAND_DELETEGROUP.getPermission()); - } - - @Override - public void runCommand(CommandSender sender, List args) { - WorldGroup worldGroup = this.plugin.getGroupManager().getGroup(args.get(0)); - if (worldGroup == null) { - this.messager.normal(Message.ERROR_NO_GROUP, sender, args.get(0)); - return; - } - - this.plugin.getGroupManager().removeGroup(worldGroup); - this.messager.normal(Message.GROUP_REMOVED, sender, args.get(0)); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/GroupCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/GroupCommand.java deleted file mode 100644 index e4baefc6..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/GroupCommand.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.command.prompts.GroupControlPrompt; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.command.CommandSender; -import org.bukkit.conversations.Conversable; -import org.bukkit.conversations.Conversation; -import org.bukkit.conversations.ConversationFactory; - -import java.util.List; - -/** - * The /mvi info Command. - */ -public class GroupCommand extends InventoriesCommand { - - public GroupCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("Creates a world group."); - this.setCommandUsage("/mvinv group"); - this.setArgRange(0, 0); - this.addKey("mvinv group"); - this.addKey("mvinv g"); - this.addKey("mvinvgroup"); - this.addKey("mvinvg"); - this.setPermission(Perm.COMMAND_GROUP.getPermission()); - } - - @Override - public void runCommand(CommandSender sender, List args) { - if (!(sender instanceof Conversable)) { - this.messager.normal(Message.NON_CONVERSABLE, sender); - return; - } - - Conversable conversable = (Conversable) sender; - Conversation conversation = new ConversationFactory(this.plugin) - .withFirstPrompt(new GroupControlPrompt(plugin, sender)) - .withEscapeSequence("##") - .withModality(false).buildConversation(conversable); - conversation.begin(); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/ImportCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/ImportCommand.java deleted file mode 100644 index 1831351b..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/ImportCommand.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.migration.DataImporter; -import com.onarandombox.multiverseinventories.migration.MigrationException; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; - -import java.util.List; - -/** - * The /mvi info Command. - */ -public class ImportCommand extends InventoriesCommand { - - public ImportCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("Import from MultiInv/WorldInventories"); - this.setCommandUsage("/mvinv import " + ChatColor.GREEN + "{MultiInv|WorldInventories}"); - this.setArgRange(1, 1); - this.addKey("mvinv import"); - this.addKey("mvinvim"); - this.addKey("mvinvimport"); - this.setPermission(Perm.COMMAND_IMPORT.getPermission()); - } - - @Override - public void runCommand(CommandSender sender, List args) { - DataImporter importer = null; - if (args.get(0).equalsIgnoreCase("MultiInv")) { - importer = this.plugin.getImportManager().getMultiInvImporter(); - } else if (args.get(0).equalsIgnoreCase("WorldInventories")) { - importer = this.plugin.getImportManager().getWorldInventoriesImporter(); - } else { - this.messager.bad(Message.ERROR_PLUGIN_NOT_ENABLED, - sender, args.get(0)); - return; - } - if (importer == null) { - this.messager.bad(Message.ERROR_PLUGIN_NOT_ENABLED, - sender, args.get(0)); - } else { - try { - importer.importData(); - } catch (MigrationException e) { - Logging.severe(e.getMessage()); - Logging.severe("Cause: " + e.getCauseException().getMessage()); - } - } - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/InfoCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/InfoCommand.java deleted file mode 100644 index 8567b3a6..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/InfoCommand.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainer; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - -import java.util.List; -import java.util.Set; - -/** - * The /mvi info Command. - */ -public class InfoCommand extends InventoriesCommand { - - public InfoCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("World and Group Information"); - this.setCommandUsage("/mvinv info " + ChatColor.GREEN + "[WORLD|GROUP]"); - this.setArgRange(0, 1); - this.addKey("mvinv info"); - this.addKey("mvinvi"); - this.addKey("mvinvinfo"); - this.setPermission(Perm.COMMAND_INFO.getPermission()); - } - - @Override - public void runCommand(CommandSender sender, List args) { - String name; - if (args.isEmpty()) { - if (!(sender instanceof Player)) { - this.messager.normal(Message.INFO_ZERO_ARG, sender); - return; - } - name = ((Player) sender).getWorld().getName(); - } else { - name = args.get(0); - } - - ProfileContainer worldProfileContainer = this.plugin.getWorldProfileContainerStore().getContainer(name); - messager.normal(Message.INFO_WORLD, sender, name); - if (worldProfileContainer != null && Bukkit.getWorld(worldProfileContainer.getContainerName()) != null) { - worldInfo(sender, worldProfileContainer); - } else { - messager.normal(Message.ERROR_NO_WORLD_PROFILE, sender, name); - } - WorldGroup worldGroup = this.plugin.getGroupManager().getGroup(name); - this.messager.normal(Message.INFO_GROUP, sender, name); - if (worldGroup != null) { - this.groupInfo(sender, worldGroup); - } else { - this.messager.normal(Message.ERROR_NO_GROUP, sender, name); - } - } - - private void groupInfo(CommandSender sender, WorldGroup worldGroup) { - StringBuilder worldsString = new StringBuilder(); - Set worlds = worldGroup.getWorlds(); - if (worlds.isEmpty()) { - worldsString.append("N/A"); - } else { - for (String world : worlds) { - if (!worldsString.toString().isEmpty()) { - worldsString.append(", "); - } - worldsString.append(world); - } - } - this.messager.normal(Message.INFO_GROUPS_INFO, - sender, worldsString, worldGroup.getShares().toString()); - } - - private void worldInfo(CommandSender sender, ProfileContainer worldProfileContainer) { - StringBuilder groupsString = new StringBuilder(); - List worldGroups = this.plugin.getGroupManager() - .getGroupsForWorld(worldProfileContainer.getContainerName()); - - if (worldGroups.isEmpty()) { - groupsString.append("N/A"); - } else { - for (WorldGroup worldGroup : worldGroups) { - if (!groupsString.toString().isEmpty()) { - groupsString.append(", "); - } - groupsString.append(worldGroup.getName()); - } - } - - this.messager.normal(Message.INFO_WORLD_INFO, - sender, groupsString.toString()); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/InventoriesCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/InventoriesCommand.java deleted file mode 100644 index c06378d7..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/InventoriesCommand.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.onarandombox.commandhandler.Command; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.locale.Messager; -import org.bukkit.command.CommandSender; - -import java.util.List; - -/** - * A base command class to easily retrieve the plugin associated. - */ -public abstract class InventoriesCommand extends Command { - - /** - * Instance of MultiverseInventories. - */ - protected MultiverseInventories plugin; - /** - * Instance of messager used for Inventories. - */ - protected Messager messager; - - public InventoriesCommand(MultiverseInventories plugin) { - super(plugin); - this.plugin = plugin; - this.messager = plugin.getMessager(); - } - - @Override - public abstract void runCommand(CommandSender sender, List args); -} - - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/ListCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/ListCommand.java deleted file mode 100644 index aacb289e..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/ListCommand.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.command.CommandSender; - -import java.util.Collection; -import java.util.List; - -/** - * The /mvi info Command. - */ -public class ListCommand extends InventoriesCommand { - - public ListCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("World and Group Information"); - this.setCommandUsage("/mvinv list"); - this.setArgRange(0, 0); - this.addKey("mvinv list"); - this.addKey("mvinvl"); - this.addKey("mvinvlist"); - this.setPermission(Perm.COMMAND_LIST.getPermission()); - } - - @Override - public void runCommand(CommandSender sender, List args) { - Collection groups = this.plugin.getGroupManager().getGroups(); - String groupsString = "N/A"; - if (!groups.isEmpty()) { - StringBuilder builder = new StringBuilder(); - for (WorldGroup group : groups) { - if (!builder.toString().isEmpty()) { - builder.append(", "); - } - builder.append(group.getName()); - } - groupsString = builder.toString(); - } - this.messager.normal(Message.LIST_GROUPS, sender, groupsString); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/MigrateCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/MigrateCommand.java deleted file mode 100644 index 70feb105..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/MigrateCommand.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainer; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - -import java.io.IOException; -import java.util.List; -import java.util.Set; - -/** - * The /mvi info Command. - */ -public class MigrateCommand extends InventoriesCommand { - - public MigrateCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("Migrate player data from one name to another"); - this.setCommandUsage("/mvinv migrate " + ChatColor.GREEN + "{OLDNAME} {NEWNAME} [saveold]"); - this.setArgRange(2, 3); - this.addKey("mvinv migrate"); - this.addKey("mvinvmigrate"); - this.setPermission(Perm.COMMAND_INFO.getPermission()); - } - - @Override - public void runCommand(CommandSender sender, List args) { - String oldName = args.get(0); - String newName = args.get(1); - boolean deleteOld = true; - if (args.size() > 2) { - if (args.get(2).equalsIgnoreCase("saveold")) { - deleteOld = false; - } - } - try { - plugin.getData().migratePlayerData(oldName, newName, Bukkit.getOfflinePlayer(newName).getUniqueId(), deleteOld); - messager.good(Message.MIGRATE_SUCCESSFUL, sender, oldName, newName); - } catch (IOException e) { - Logging.severe("Could not migrate data from name " + oldName + " to " + newName); - e.printStackTrace(); - messager.bad(Message.MIGRATE_FAILED, sender, oldName, newName); - } - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/ReloadCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/ReloadCommand.java deleted file mode 100644 index 45596191..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/ReloadCommand.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.onarandombox.multiverseinventories.util.Perm; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.locale.Message; -import org.bukkit.command.CommandSender; - -import java.util.List; - -/** - * The /mvi info Command. - */ -public class ReloadCommand extends InventoriesCommand { - - public ReloadCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("Reloads config file"); - this.setCommandUsage("/mvinv reload"); - this.setArgRange(0, 0); - this.addKey("mvinv reload"); - this.addKey("mvinvreload"); - this.setPermission(Perm.COMMAND_RELOAD.getPermission()); - } - - @Override - public void runCommand(CommandSender sender, List args) { - this.plugin.reloadConfig(); - this.messager.normal(Message.RELOAD_COMPLETE, sender); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/RemoveSharesCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/RemoveSharesCommand.java deleted file mode 100644 index 72c08ea1..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/RemoveSharesCommand.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.share.Sharable; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.share.Shares; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.command.CommandSender; - -import java.util.List; - -/** - * The /mvinv rmshares Command. - * @deprecated Deprecated in favor of /mvinv group. - */ -@Deprecated -public class RemoveSharesCommand extends InventoriesCommand { - - public RemoveSharesCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("Removes share(s) from a World Group."); - this.setCommandUsage("/mvinv removeshares {SHARE[,EXTRA]} {GROUP}"); - this.setArgRange(2, 2); - this.addKey("mvinv removeshares"); - this.addKey("mvinv rmshares"); - this.addKey("mvinv removeshare"); - this.addKey("mvinv rmshare"); - this.addKey("mvinv removes"); - this.addKey("mvinv rms"); - this.addKey("mvinvrs"); - this.addKey("mvinvrms"); - this.addKey("mvinvremoves"); - this.addKey("mvinvremoveshares"); - this.addKey("mvinvrmshares"); - this.addKey("mvinvremoveshare"); - this.addKey("mvinvrmshare"); - this.setPermission(Perm.COMMAND_RMSHARES.getPermission()); - } - - @Override - public void runCommand(CommandSender sender, List args) { - Shares newShares; - if (args.get(0).contains("all") || args.get(0).contains("everything") || args.get(0).contains("*")) { - newShares = Sharables.allOf(); - } else { - newShares = Sharables.noneOf(); - String[] sharesString = args.get(0).split(","); - for (String shareString : sharesString) { - Shares shares = Sharables.lookup(shareString); - if (shares == null) { - continue; - } - newShares.setSharing(shares, true); - } - } - if (newShares.isEmpty()) { - this.messager.normal(Message.ERROR_NO_SHARES_SPECIFIED, sender, args.get(0)); - return; - } - WorldGroup worldGroup = this.plugin.getGroupManager().getGroup(args.get(1)); - if (worldGroup == null) { - this.messager.normal(Message.ERROR_NO_GROUP, sender, args.get(1)); - return; - } - for (Sharable sharable : newShares) { - worldGroup.getShares().setSharing(sharable, false); - } - this.plugin.getGroupManager().updateGroup(worldGroup); - this.plugin.getMVIConfig().save(); - this.messager.normal(Message.NOW_SHARING, sender, worldGroup.getName(), - worldGroup.getShares().toString()); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/RemoveWorldCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/RemoveWorldCommand.java deleted file mode 100644 index f715295a..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/RemoveWorldCommand.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.command.CommandSender; - -import java.util.List; - -/** - * The /mvinv rmworld Command. - * @deprecated Deprecated in favor of /mvinv group. - */ -@Deprecated -public class RemoveWorldCommand extends InventoriesCommand { - - public RemoveWorldCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("Removes a World from a World Group."); - this.setCommandUsage("/mvinv removeworld {WORLD} {GROUP}"); - this.setArgRange(2, 2); - this.addKey("mvinv removeworld"); - this.addKey("mvinv rmworld"); - this.addKey("mvinv removew"); - this.addKey("mvinv rmw"); - this.addKey("mvinvrw"); - this.addKey("mvinvrmw"); - this.addKey("mvinvremovew"); - this.addKey("mvinvremoveworld"); - this.addKey("mvinvrmworld"); - this.setPermission(Perm.COMMAND_RMWORLD.getPermission()); - } - - @Override - public void runCommand(CommandSender sender, List args) { - World world = Bukkit.getWorld(args.get(0)); - if (world == null) { - this.messager.normal(Message.ERROR_NO_WORLD, sender, args.get(0)); - return; - } - WorldGroup worldGroup = this.plugin.getGroupManager().getGroup(args.get(1)); - if (worldGroup == null) { - this.messager.normal(Message.ERROR_NO_GROUP, sender, args.get(1)); - return; - } - if (!worldGroup.containsWorld(world.getName())) { - this.messager.normal(Message.WORLD_NOT_IN_GROUP, sender, world.getName(), - worldGroup.getName()); - return; - } - worldGroup.removeWorld(world); - this.plugin.getGroupManager().updateGroup(worldGroup); - this.plugin.getMVIConfig().save(); - this.messager.normal(Message.WORLD_REMOVED, sender, world.getName(), - worldGroup.getName()); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/SpawnCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/SpawnCommand.java deleted file mode 100644 index c7869ae1..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/SpawnCommand.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.onarandombox.MultiverseCore.api.MultiverseWorld; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.util.Perm; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.locale.Message; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - -import java.util.List; - -/** - * The /mvi info Command. - */ -public class SpawnCommand extends InventoriesCommand { - - public SpawnCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("Spawn"); - this.setCommandUsage("/mvinv spawn" + ChatColor.GOLD + " [PLAYER]"); - this.setArgRange(0, 1); - this.addKey("mvinv spawn"); - this.addKey("mvinvspawn"); - this.addKey("mvinvs"); - this.addKey("gspawn"); - this.addKey("ispawn"); - this.setPermission(Perm.COMMAND_SPAWN.getPermission()); - this.addAdditonalPermission(Perm.COMMAND_SPAWN_OTHER.getPermission()); - - } - - @Override - public void runCommand(CommandSender sender, List args) { - Player player = null; - if (sender instanceof Player) { - player = (Player) sender; - } - // If a persons name was passed in, you must be A. the console, or B have permissions - if (args.size() == 1) { - Perm perm = Perm.COMMAND_SPAWN_OTHER; - if (player != null && !perm.has(player)) { - this.messager.normal(Message.GENERIC_COMMAND_NO_PERMISSION, player, - perm.getPermission().getDescription(), perm.getPermission().getName()); - return; - } - Player target = Bukkit.getPlayerExact(args.get(0)); - if (target != null) { - this.messager.normal(Message.TELEPORTING, target); - spawnAccurately(target); - - if (player != null) { - this.messager.normal(Message.TELEPORTED_BY, target, - ChatColor.YELLOW + player.getName()); - } else { - this.messager.normal(Message.TELEPORTED_BY, target, - ChatColor.LIGHT_PURPLE + this.messager - .getMessage(Message.GENERIC_THE_CONSOLE)); - } - } else { - this.messager.normal(Message.GENERIC_NOT_LOGGED_IN, sender, args.get(0)); - } - } else { - Perm perm = Perm.COMMAND_SPAWN; - if (player != null && !perm.has(player)) { - this.messager.normal(Message.GENERIC_COMMAND_NO_PERMISSION, player, - perm.getPermission().getDescription(), perm.getPermission().getName()); - return; - } - if (player != null) { - this.messager.normal(Message.TELEPORTING, player); - spawnAccurately(player); - } else { - this.messager.normal(Message.TELEPORT_CONSOLE_ERROR, sender); - } - } - } - - private void spawnAccurately(Player player) { - World world = null; - for (WorldGroup group : this.plugin.getGroupManager().getGroupsForWorld(player.getWorld().getName())) { - if (group.getSpawnWorld() != null) { - world = Bukkit.getWorld(group.getSpawnWorld()); - if (world != null) { - break; - } - } - } - if (world == null) { - world = player.getWorld(); - } - MultiverseWorld mvWorld = this.plugin.getCore() - .getMVWorldManager().getMVWorld(world); - Location spawnLocation; - if (mvWorld != null) { - spawnLocation = mvWorld.getSpawnLocation(); - } else { - spawnLocation = world.getSpawnLocation(); - } - this.plugin.getCore().getSafeTTeleporter().safelyTeleport(player, player, spawnLocation, false); - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/ToggleCommand.java b/src/main/java/com/onarandombox/multiverseinventories/command/ToggleCommand.java deleted file mode 100644 index f30e9401..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/ToggleCommand.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.onarandombox.multiverseinventories.command; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.share.Sharable; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.share.Shares; -import com.onarandombox.multiverseinventories.locale.Message; -import com.onarandombox.multiverseinventories.util.Perm; -import org.bukkit.command.CommandSender; - -import java.util.List; - -/** - * The /mvi info Command. - */ -public class ToggleCommand extends InventoriesCommand { - - public ToggleCommand(MultiverseInventories plugin) { - super(plugin); - this.setName("Toggles the usage of optional sharables"); - this.setCommandUsage("/mvinv toggle {SHARE}"); - this.setArgRange(1, 1); - this.addKey("mvinv toggle"); - this.addKey("mvinv t"); - this.setPermission(Perm.COMMAND_ADDSHARES.getPermission()); - } - - @Override - public void runCommand(CommandSender sender, List args) { - Shares shares = Sharables.lookup(args.get(0).toLowerCase()); - if (shares == null) { - this.messager.normal(Message.ERROR_NO_SHARES_SPECIFIED, sender); - return; - } - boolean foundOpt = false; - for (Sharable sharable : shares) { - if (sharable.isOptional()) { - foundOpt = true; - if (this.plugin.getMVIConfig().getOptionalShares().contains(sharable)) { - this.plugin.getMVIConfig().getOptionalShares().remove(sharable); - this.messager.normal(Message.NOW_NOT_USING_OPTIONAL, sender, sharable.getNames()[0]); - } else { - this.plugin.getMVIConfig().getOptionalShares().add(sharable); - this.messager.normal(Message.NOW_USING_OPTIONAL, sender, sharable.getNames()[0]); - } - } - } - if (foundOpt) { - this.plugin.getMVIConfig().save(); - } else { - this.messager.normal(Message.NO_OPTIONAL_SHARES, sender, args.get(0)); - } - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/package-info.java b/src/main/java/com/onarandombox/multiverseinventories/command/package-info.java deleted file mode 100644 index 988881fa..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * This package contains all Commands. - */ -package com.onarandombox.multiverseinventories.command; - diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupControlPrompt.java b/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupControlPrompt.java deleted file mode 100644 index fa517717..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupControlPrompt.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.onarandombox.multiverseinventories.command.prompts; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.locale.Message; -import org.bukkit.command.CommandSender; -import org.bukkit.conversations.ConversationContext; -import org.bukkit.conversations.Prompt; - -public class GroupControlPrompt extends InventoriesPrompt { - - public GroupControlPrompt(final MultiverseInventories plugin, final CommandSender sender) { - super(plugin, sender); - } - - @Override - public String getPromptText(final ConversationContext conversationContext) { - return messager.getMessage(Message.GROUP_COMMAND_PROMPT); - } - - @Override - public Prompt acceptInput(final ConversationContext conversationContext, final String s) { - if (s.equalsIgnoreCase("delete")) { - return new GroupDeletePrompt(plugin, sender); - } else if (s.equalsIgnoreCase("create")) { - return new GroupCreatePrompt(plugin, sender); - } else if (s.equalsIgnoreCase("edit")) { - return new GroupEditPrompt(plugin, sender); - } else { - messager.normal(Message.INVALID_PROMPT_OPTION, sender); - return this; - } - } -} diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupCreatePrompt.java b/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupCreatePrompt.java deleted file mode 100644 index ea03e3c1..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupCreatePrompt.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.onarandombox.multiverseinventories.command.prompts; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; -import org.bukkit.command.CommandSender; -import org.bukkit.conversations.ConversationContext; -import org.bukkit.conversations.Prompt; - -class GroupCreatePrompt extends InventoriesPrompt { - - public GroupCreatePrompt(final MultiverseInventories plugin, final CommandSender sender) { - super(plugin, sender); - } - - @Override - public String getPromptText(final ConversationContext conversationContext) { - return messager.getMessage(Message.GROUP_CREATE_PROMPT); - } - - @Override - public Prompt acceptInput(final ConversationContext conversationContext, final String s) { - final WorldGroup group = plugin.getGroupManager().getGroup(s); - if (group == null) { - if (s.isEmpty() || !s.matches("^[a-zA-Z0-9][a-zA-Z0-9_]*$")) { - messager.normal(Message.GROUP_INVALID_NAME, sender); - return this; - } - final WorldGroup newGroup = plugin.getGroupManager().newEmptyGroup(s); - return new GroupWorldsPrompt(plugin, sender, newGroup, - new GroupSharesPrompt(plugin, sender, newGroup, Prompt.END_OF_CONVERSATION, true), true); - } else { - messager.normal(Message.GROUP_EXISTS, sender, s); - } - return Prompt.END_OF_CONVERSATION; - } -} diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupDeletePrompt.java b/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupDeletePrompt.java deleted file mode 100644 index 12617b81..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupDeletePrompt.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.onarandombox.multiverseinventories.command.prompts; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; -import org.bukkit.conversations.ConversationContext; -import org.bukkit.conversations.Prompt; - -class GroupDeletePrompt extends InventoriesPrompt { - - public GroupDeletePrompt(final MultiverseInventories plugin, final CommandSender sender) { - super(plugin, sender); - } - - @Override - public String getPromptText(final ConversationContext conversationContext) { - final StringBuilder builder = new StringBuilder(); - for (WorldGroup group : plugin.getGroupManager().getGroups()) { - if (builder.length() == 0) { - builder.append(ChatColor.WHITE); - } else { - builder.append(ChatColor.GOLD).append(", ").append(ChatColor.WHITE); - } - builder.append(group.getName()); - } - return messager.getMessage(Message.GROUP_DELETE_PROMPT, builder.toString()); - } - - @Override - public Prompt acceptInput(final ConversationContext conversationContext, final String s) { - final WorldGroup group = plugin.getGroupManager().getGroup(s); - if (group == null) { - messager.normal(Message.ERROR_NO_GROUP, sender, s); - } else { - plugin.getGroupManager().removeGroup(group); - messager.normal(Message.GROUP_REMOVED, sender, s); - } - return Prompt.END_OF_CONVERSATION; - } -} diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupEditPrompt.java b/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupEditPrompt.java deleted file mode 100644 index 395e99f3..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupEditPrompt.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.onarandombox.multiverseinventories.command.prompts; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; -import org.bukkit.conversations.ConversationContext; -import org.bukkit.conversations.Prompt; - -class GroupEditPrompt extends InventoriesPrompt { - - public GroupEditPrompt(final MultiverseInventories plugin, final CommandSender sender) { - super(plugin, sender); - } - - @Override - public String getPromptText(final ConversationContext conversationContext) { - final StringBuilder builder = new StringBuilder(); - for (WorldGroup group : plugin.getGroupManager().getGroups()) { - if (builder.length() == 0) { - builder.append(ChatColor.WHITE); - } else { - builder.append(ChatColor.GOLD).append(", ").append(ChatColor.WHITE); - } - builder.append(group.getName()); - } - return messager.getMessage(Message.GROUP_EDIT_PROMPT, builder.toString()); - } - - @Override - public Prompt acceptInput(final ConversationContext conversationContext, final String s) { - final WorldGroup group = plugin.getGroupManager().getGroup(s); - if (group == null) { - messager.normal(Message.ERROR_NO_GROUP, sender, s); - } else { - return new GroupModifyPrompt(plugin, sender, group); - } - return Prompt.END_OF_CONVERSATION; - } -} diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupModifyPrompt.java b/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupModifyPrompt.java deleted file mode 100644 index 87a4abd4..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupModifyPrompt.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.onarandombox.multiverseinventories.command.prompts; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; -import org.bukkit.command.CommandSender; -import org.bukkit.conversations.ConversationContext; -import org.bukkit.conversations.Prompt; - -class GroupModifyPrompt extends InventoriesPrompt { - - protected final WorldGroup group; - - public GroupModifyPrompt(final MultiverseInventories plugin, final CommandSender sender, - final WorldGroup group) { - super(plugin, sender); - this.group = group; - } - - @Override - public String getPromptText(final ConversationContext conversationContext) { - return messager.getMessage(Message.GROUP_MODIFY_PROMPT, group.getName()); - } - - @Override - public Prompt acceptInput(final ConversationContext conversationContext, final String s) { - if (s.equalsIgnoreCase("worlds")) { - return new GroupWorldsPrompt(plugin, sender, group, this, false); - } else if (s.equalsIgnoreCase("shares")) { - return new GroupSharesPrompt(plugin, sender, group, this, false); - } else { - messager.normal(Message.INVALID_PROMPT_OPTION, sender); - return this; - } - } -} diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupSharesPrompt.java b/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupSharesPrompt.java deleted file mode 100644 index f2071f52..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupSharesPrompt.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.onarandombox.multiverseinventories.command.prompts; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.share.Sharable; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.share.Shares; -import com.onarandombox.multiverseinventories.locale.Message; -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; -import org.bukkit.conversations.ConversationContext; -import org.bukkit.conversations.Prompt; - -class GroupSharesPrompt extends InventoriesPrompt { - - protected final WorldGroup group; - protected final Prompt nextPrompt; - protected final boolean isCreating; - protected final Shares shares; - - public GroupSharesPrompt(final MultiverseInventories plugin, final CommandSender sender, - final WorldGroup group, final Prompt nextPrompt, - final boolean creatingGroup) { - super(plugin, sender); - this.group = group; - this.nextPrompt = nextPrompt; - this.isCreating = creatingGroup; - this.shares = Sharables.fromShares(group.getShares()); - } - - @Override - public String getPromptText(final ConversationContext conversationContext) { - final StringBuilder builder = new StringBuilder(); - for (final Sharable sharable : shares) { - if (builder.length() == 0) { - builder.append(ChatColor.WHITE); - } else { - builder.append(ChatColor.GOLD).append(", ").append(ChatColor.WHITE); - } - builder.append(sharable.toString()); - } - return messager.getMessage(Message.GROUP_SHARES_PROMPT, group.getName(), builder.toString()); - } - - @Override - public Prompt acceptInput(final ConversationContext conversationContext, final String s) { - if (s.equals("@")) { - group.getShares().clear(); - group.getShares().addAll(this.shares); - plugin.getGroupManager().updateGroup(group); - if (isCreating) { - messager.normal(Message.GROUP_CREATION_COMPLETE, sender); - } else { - messager.normal(Message.GROUP_UPDATED, sender); - } - messager.normal(Message.INFO_GROUP, sender, group.getName()); - messager.normal(Message.INFO_GROUPS_INFO, sender, group.getWorlds(), group.getShares()); - plugin.getGroupManager().checkForConflicts(sender); - return nextPrompt; - } - - boolean negative = false; - Shares shares = Sharables.lookup(s.toLowerCase()); - if (shares == null && s.startsWith("-") && s.length() > 1) { - negative = true; - shares = Sharables.lookup(s.toLowerCase().substring(1)); - } - - if (shares == null) { - messager.normal(Message.ERROR_NO_SHARES_SPECIFIED, sender); - return this; - } - if (negative) { - this.shares.removeAll(shares); - return this; - } - this.shares.addAll(shares); - return this; - } -} diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupWorldsPrompt.java b/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupWorldsPrompt.java deleted file mode 100644 index 6d428863..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/GroupWorldsPrompt.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.onarandombox.multiverseinventories.command.prompts; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.locale.Message; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.World; -import org.bukkit.command.CommandSender; -import org.bukkit.conversations.ConversationContext; -import org.bukkit.conversations.Prompt; - -import java.util.HashSet; -import java.util.Set; - -class GroupWorldsPrompt extends InventoriesPrompt { - - protected final WorldGroup group; - protected final Prompt nextPrompt; - protected final boolean isCreating; - protected final Set worlds; - - public GroupWorldsPrompt(final MultiverseInventories plugin, final CommandSender sender, - final WorldGroup group, final Prompt nextPrompt, - final boolean creatingGroup) { - super(plugin, sender); - this.group = group; - this.nextPrompt = nextPrompt; - this.isCreating = creatingGroup; - this.worlds = new HashSet(group.getWorlds()); - } - - @Override - public String getPromptText(final ConversationContext conversationContext) { - final StringBuilder builder = new StringBuilder(); - for (final String world : worlds) { - if (builder.length() == 0) { - builder.append(ChatColor.WHITE); - } else { - builder.append(ChatColor.GOLD).append(", ").append(ChatColor.WHITE); - } - builder.append(world); - } - return messager.getMessage(Message.GROUP_WORLDS_PROMPT, group.getName(), builder.toString()); - } - - @Override - public Prompt acceptInput(final ConversationContext conversationContext, final String s) { - if (s.equals("@")) { - if (worlds.isEmpty()) { - messager.normal(Message.GROUP_WORLDS_EMPTY, sender); - return this; - } - group.removeAllWorlds(false); - group.addWorlds(worlds, false); - if (!isCreating) { - plugin.getGroupManager().updateGroup(group); - messager.normal(Message.GROUP_UPDATED, sender); - messager.normal(Message.INFO_GROUP, sender, group.getName()); - messager.normal(Message.INFO_GROUPS_INFO, sender, group.getWorlds(), group.getShares()); - } - return nextPrompt; - } - - boolean negative = false; - World world = Bukkit.getWorld(s); - if (world == null && s.startsWith("-") && s.length() > 1) { - negative = true; - world = Bukkit.getWorld(s.substring(1)); - } - - if (world == null) { - messager.normal(Message.ERROR_NO_WORLD, sender, s); - return this; - } - if (negative) { - if (!worlds.contains(world.getName())) { - messager.normal(Message.WORLD_NOT_IN_GROUP, sender, world.getName(), group.getName()); - return this; - } - worlds.remove(world.getName()); - return this; - } - worlds.add(world.getName()); - return this; - } -} diff --git a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/InventoriesPrompt.java b/src/main/java/com/onarandombox/multiverseinventories/command/prompts/InventoriesPrompt.java deleted file mode 100644 index 7ec9f212..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/command/prompts/InventoriesPrompt.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.onarandombox.multiverseinventories.command.prompts; - -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.locale.Messager; -import org.bukkit.command.CommandSender; -import org.bukkit.conversations.ConversationContext; -import org.bukkit.conversations.Prompt; - -abstract class InventoriesPrompt implements Prompt { - - protected final MultiverseInventories plugin; - protected final Messager messager; - protected final CommandSender sender; - - InventoriesPrompt(final MultiverseInventories plugin, final CommandSender sender) { - this.plugin = plugin; - this.messager = plugin.getMessager(); - this.sender = sender; - } - - @Override - public boolean blocksForInput(final ConversationContext conversationContext) { - return true; - } -} diff --git a/src/main/java/com/onarandombox/multiverseinventories/locale/LazyLocaleMessageProvider.java b/src/main/java/com/onarandombox/multiverseinventories/locale/LazyLocaleMessageProvider.java deleted file mode 100644 index baf9c688..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/locale/LazyLocaleMessageProvider.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.onarandombox.multiverseinventories.locale; - -import java.util.Locale; -import java.util.Set; - -/** - *

Multiverse 2 LazyMessageProvider

- * - * This interface describes a Multiverse-MessageProvider that only loads locales when they're needed. - */ -public interface LazyLocaleMessageProvider extends MessageProvider { - - /** - *

Loads a localization for a specified {@link Locale}.

- * - * If that localization is already loaded, this method will reload it. - * - * @param locale The desired {@link Locale}. - * @throws LocalizationLoadingException When an error occurs while trying to load the specified localization. - * @throws NoSuchLocalizationException When no localization was found for the desired locale. - */ - void loadLocale(Locale locale) throws NoSuchLocalizationException, // SUPPRESS CHECKSTYLE: Redundant - LocalizationLoadingException; // SUPPRESS CHECKSTYLE: Redundant - - /** - * Retrieves all loaded localizations. - * - * @return A {@link Set} of {@link Locale}s whose localizations are currently loaded. - */ - Set getLoadedLocales(); - - /** - * Detects if a localization is loaded for the specified {@link Locale}. - * - * @param locale The {@link Locale}. - * @return Whether a localization is loaded for the specified {@link Locale}. - */ - boolean isLocaleLoaded(Locale locale); - -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/locale/LocalizationLoadingException.java b/src/main/java/com/onarandombox/multiverseinventories/locale/LocalizationLoadingException.java deleted file mode 100644 index f5bfea63..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/locale/LocalizationLoadingException.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.onarandombox.multiverseinventories.locale; - -import java.io.IOException; -import java.util.Locale; - -/** - * Thrown when an error occurs while a localization is loaded. - */ -public class LocalizationLoadingException extends IOException { - private final Locale locale; - - public LocalizationLoadingException(Locale locale) { - this.locale = locale; - } - - public LocalizationLoadingException(String message, Locale locale) { - super(message); - this.locale = locale; - } - - public LocalizationLoadingException(Throwable cause, Locale locale) { - super(cause); - this.locale = locale; - } - - public LocalizationLoadingException(String message, Throwable cause, Locale locale) { - super(message, cause); - this.locale = locale; - } - - /** - * Retrieves the locale that was attempting to be loaded. - * - * @return The locale that was attempting to be loaded. - */ - public Locale getLocale() { - return locale; - } - - @Override - public String getMessage() { - return super.getMessage() + " (While trying to load localization for locale " + getLocale() + ")"; - } -} diff --git a/src/main/java/com/onarandombox/multiverseinventories/locale/Message.java b/src/main/java/com/onarandombox/multiverseinventories/locale/Message.java deleted file mode 100644 index badd749d..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/locale/Message.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.onarandombox.multiverseinventories.locale; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * An enum containing all messages/strings used by Multiverse. - */ -public enum Message { - // BEGIN CHECKSTYLE-SUPPRESSION: Javadoc - TEST_STRING("a test-string from the enum"), - - // Generic Strings - GENERIC_SORRY("Sorry..."), - GENERIC_PAGE("Page"), - GENERIC_OF("of"), - GENERIC_UNLOADED("UNLOADED"), - GENERIC_PLUGIN_DISABLED("This plugin is Disabled!"), - GENERIC_ERROR("[Error]"), - GENERIC_SUCCESS("[Success]"), - GENERIC_INFO("[Info]"), - GENERIC_HELP("[Help]"), - GENERIC_COMMAND_NO_PERMISSION("You do not have permission to %1. (%2)"), - GENERIC_THE_CONSOLE("the console"), - GENERIC_NOT_LOGGED_IN("%1 is not logged on right now!"), - GENERIC_OFF("OFF"), - - // Errors - ERROR_CONFIG_LOAD("Encountered an error while loading the configuration file. Disabling..."), - ERROR_DATA_LOAD("Encountered an error while loading the data file. Disabling..."), - ERROR_NO_GROUP("&6There is no group with the name: &f%1"), - ERROR_NO_WORLD("&6There is no world with the name: &f%1"), - ERROR_NO_WORLD_PROFILE("&6There is no profile container for the world: &f%1"), - ERROR_PLUGIN_NOT_ENABLED("&f%1 &6is not enabled so you may not import data from it!"), - ERROR_UNSUPPORTED_IMPORT("&6Sorry, ''&f%1&6'' is not supported for importing."), - ERROR_NO_SHARES_SPECIFIED("&cYou did not specify any valid shares!"), - - // Group Conflicts - CONFLICT_RESULTS("Conflict found for groups: '%1' and '%2' because they both share: '%3' for the world(s): '%4'"), - CONFLICT_CHECKING("Checking for conflicts in groups..."), - CONFLICT_FOUND("Conflicts have been found... If these are not resolved, you may experience problems with your data."), - CONFLICT_NOT_FOUND("No group conflicts found!"), - - //// Commands - NON_CONVERSABLE("You are not allowed to access conversations (remote console?)"), - INVALID_PROMPT_OPTION("&cThat is not a valid option! Type &f##&c to stop working on groups."), - // Info Command - INFO_ZERO_ARG("You may only use the no argument version of this command in game!"), - INFO_WORLD("&b===[ Info for world: &6%1&b ]==="), - INFO_WORLD_INFO("&6Groups:&f %1"), - INFO_GROUP("&b===[ Info for group: &6%1&b ]==="), - INFO_GROUPS_INFO("&6Worlds:&f %1", "&bShares:&f %2"), - // Group Command - GROUP_COMMAND_PROMPT("&6What would you like to do? &fCreate&6, &fEdit &6or &fDelete&6. Enter &f##&6 at any time to cancel."), - GROUP_CREATE_PROMPT("&6Please name your new group: "), - GROUP_EDIT_PROMPT("&6Edit which group? %1"), - GROUP_DELETE_PROMPT("&6Delete which group? %1"), - GROUP_MODIFY_PROMPT("&6Which would you like to change for &e%1&6? &fWorlds &6or &fShares&6. Enter &f##&6 to finish."), - GROUP_WORLDS_PROMPT("&6Enter the name of a world to add to group &f%1&6 or enter &f@&6 to continue. To remove a world, precede the name with the minus symbol. (ex: &f-worldname&6). Current worlds: %2"), - GROUP_SHARES_PROMPT("&6Enter &fall&6 or a specific share to add to group &f%1&6 or enter &f@&6 to continue. To remove shares, precede the name with the minus symbol (ex: &f-inventory&6). Current shares: %2"), - GROUP_INVALID_NAME("&cThat name is not valid! May only contain letters, numbers, and underscores."), - GROUP_EXISTS("&cThat group already exists! (&f%1&c)"), - GROUP_REMOVED("&2Removed group: &f%1"), - GROUP_WORLDS_EMPTY("&cYou may not have a group with no worlds, please add worlds or type &f##&c to cancel."), - GROUP_CREATION_COMPLETE("&2You created a new group!"), - GROUP_UPDATED("&2Group has been updated!"), - // List Command - LIST_GROUPS("&b===[ Group List ]===", "&6Groups:&f %1"), - // Reload Command - RELOAD_COMPLETE("&b===[ Reload Complete! ]==="), - // AddWorld Command - WORLD_ADDED("&6World:&f %1 &6added to Group: &f%2"), - WORLD_ALREADY_EXISTS("&6World:&f %1 &6already part of Group: &f%2"), - // RemoveWorld Command - WORLD_REMOVED("&6World:&f %1 &6removed from Group: &f%2"), - WORLD_NOT_IN_GROUP("&6World:&f %1 &6is not part of Group: &f%2"), - // AddShares Command - NOW_SHARING("&6Group: &f%1 &6is now sharing: &f%2"), - // Spawn Command - TELEPORTING("Teleporting to this world group's spawn..."), - TELEPORTED_BY("You were teleported by: %1"), - TELEPORT_CONSOLE_ERROR("From the console, you must provide a PLAYER"), - // DebugCommand - INVALID_DEBUG("&fInvalid debug level. Please use number 0-3. &b(3 being many many messages!)"), - DEBUG_SET("Debug mode is %1"), - // Toggle Command - NOW_USING_OPTIONAL("&f%1 &6will now be considered when player's change world."), - NOW_NOT_USING_OPTIONAL("&f%1 &6will no longer be considered when player's change world."), - NO_OPTIONAL_SHARES("&f%1 &6is not an optional share!"), - // Migrate Command - MIGRATE_FAILED("Failed to migrate data from %1 to %2! Check logs for error details."), - MIGRATE_SUCCESSFUL("Migrated data from %1 to %2!"); - - // BEGIN CHECKSTYLE-SUPPRESSION: Javadoc - - private final List def; - - Message(String def, String... extra) { - this.def = new ArrayList(); - this.def.add(def); - this.def.addAll(Arrays.asList(extra)); - } - - /** - * @return This {@link Message}'s default-message - */ - public List getDefault() { - return def; - } - -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/locale/MessageProvider.java b/src/main/java/com/onarandombox/multiverseinventories/locale/MessageProvider.java deleted file mode 100644 index 815a8311..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/locale/MessageProvider.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.onarandombox.multiverseinventories.locale; - -import java.util.List; -import java.util.Locale; - -/** - *

Multiverse 2 MessageProvider.

- * - * This interface describes a Multiverse-MessageProvider. - */ -public interface MessageProvider { - /** - * The default locale. - */ - Locale DEFAULT_LOCALE = Locale.ENGLISH; - - /** - * Returns a message (as {@link String}) for the specified key (as {@link Message}). - * - * @param key The key - * @param args Args for String.format() - * @return The message - */ - String getMessage(Message key, Object... args); - - /** - * Returns a message (as {@link String}) in a specified {@link Locale} for the specified key (as {@link Message}). - * - * @param key The Key - * @param locale The {@link Locale} - * @param args Args for String.format() - * @return The message - */ - String getMessage(Message key, Locale locale, Object... args); - - /** - * Returns a message (as {@link List}) of Strings for the specified key (as {@link Message}). - * - * @param key The key - * @param args Args for String.format() - * @return The messages - */ - List getMessages(Message key, Object... args); - - /** - * Returns a message (as {@link List}) of Strings in a specified {@link Locale} for the specified key (as {@link Message}). - * - * @param key The key - * @param locale The {@link Locale} - * @param args Args for String.format() - * @return The messages - */ - List getMessages(Message key, Locale locale, Object... args); - - /** - * Returns the Locale this MessageProvider is currently using. - * - * @return The locale this MessageProvider is currently using. - */ - Locale getLocale(); - - /** - * Sets the locale for this MessageProvider. - * - * @param locale The new {@link Locale}. - */ - void setLocale(Locale locale); -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/locale/Messager.java b/src/main/java/com/onarandombox/multiverseinventories/locale/Messager.java deleted file mode 100644 index 86ed0110..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/locale/Messager.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.onarandombox.multiverseinventories.locale; - -import org.bukkit.command.CommandSender; - -import java.util.List; - -/** - * This interface describes a Messager which sends messages to CommandSenders. - */ -public interface Messager extends MessageProvider { - - /** - * Sends a message to the specified player with the generic ERROR prefix. - * - * @param message The message to send. - * @param sender The entity to send the messages to. - * @param args arguments for String.format(). - */ - void bad(Message message, CommandSender sender, Object... args); - - /** - * Sends a message to the specified player with NO special prefix. - * - * @param message The message to send. - * @param sender The entity to send the messages to. - * @param args arguments for String.format(). - */ - void normal(Message message, CommandSender sender, Object... args); - - /** - * Sends a message to the specified player with the generic SUCCESS prefix. - * - * @param message The message to send. - * @param sender The entity to send the messages to. - * @param args arguments for String.format(). - */ - void good(Message message, CommandSender sender, Object... args); - - /** - * Sends a message to the specified player with the generic INFO prefix. - * - * @param message The message to send. - * @param sender The entity to send the messages to. - * @param args arguments for String.format(). - */ - void info(Message message, CommandSender sender, Object... args); - - /** - * Sends a message to the specified player with the generic HELP prefix. - * - * @param message The message to send. - * @param sender The entity to send the messages to. - * @param args arguments for String.format(). - */ - void help(Message message, CommandSender sender, Object... args); - - /** - * Sends a message to a player that automatically takes words that are too long and puts them on a new line. - * - * @param player Player to send message to. - * @param message Message to send. - */ - void sendMessage(CommandSender player, String message); - - /** - * Sends a message to a player that automatically takes words that are too long and puts them on a new line. - * - * @param player Player to send message to. - * @param messages Messages to send. - */ - void sendMessages(CommandSender player, List messages); -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/locale/Messaging.java b/src/main/java/com/onarandombox/multiverseinventories/locale/Messaging.java deleted file mode 100644 index 971b4978..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/locale/Messaging.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.onarandombox.multiverseinventories.locale; - -/** - * This interface is implemented by classes that use a {@link Messager}. - */ -public interface Messaging { - - /** - * @return The {@link Messager} used by the Plugin. - */ - Messager getMessager(); - - /** - * Sets the {@link Messager} used by the Plugin. - * - * @param messager The new {@link Messager}. Must not be null! - */ - void setMessager(Messager messager); -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/locale/NoSuchLocalizationException.java b/src/main/java/com/onarandombox/multiverseinventories/locale/NoSuchLocalizationException.java deleted file mode 100644 index 31dd92ae..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/locale/NoSuchLocalizationException.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.onarandombox.multiverseinventories.locale; - -import java.util.Locale; - -/** - * Thrown when the requested localization is not found. - */ -public class NoSuchLocalizationException extends LocalizationLoadingException { - - public NoSuchLocalizationException(Locale locale) { - super(locale); - } - - public NoSuchLocalizationException(String message, Locale locale) { - super(message, locale); - } - - public NoSuchLocalizationException(String message, Throwable cause, Locale locale) { - super(message, cause, locale); - } - - public NoSuchLocalizationException(Throwable cause, Locale locale) { - super(cause, locale); - } - -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/locale/package-info.java b/src/main/java/com/onarandombox/multiverseinventories/locale/package-info.java deleted file mode 100644 index 6279ccea..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/locale/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * This package contains Multiverse-Inventories localization features. - */ -package com.onarandombox.multiverseinventories.locale; - diff --git a/src/main/java/com/onarandombox/multiverseinventories/migration/DataImporter.java b/src/main/java/com/onarandombox/multiverseinventories/migration/DataImporter.java deleted file mode 100644 index e97a5fbd..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/migration/DataImporter.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.onarandombox.multiverseinventories.migration; - -import org.bukkit.plugin.Plugin; - -/** - * Interface for data migration importers. - */ -public interface DataImporter { - - /** - * Imports the data from another plugin. - * - * @throws MigrationException If there was any MAJOR issue loading the data. - */ - void importData() throws MigrationException; - - /** - * @return The plugin associated with this Importer. - */ - Plugin getPlugin(); -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/migration/ImportManager.java b/src/main/java/com/onarandombox/multiverseinventories/migration/ImportManager.java deleted file mode 100644 index b1eefaeb..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/migration/ImportManager.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.onarandombox.multiverseinventories.migration; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.migration.multiinv.MultiInvImporter; -import com.onarandombox.multiverseinventories.migration.worldinventories.WorldInventoriesImporter; -import me.drayshak.WorldInventories.WorldInventories; -import uk.co.tggl.pluckerpluck.multiinv.MultiInv; - -/** - * Manages the import heplers for other similar plugins. - */ -public class ImportManager { - - private MultiInvImporter multiInvImporter = null; - private WorldInventoriesImporter worldInventoriesImporter = null; - private MultiverseInventories inventories; - - public ImportManager(MultiverseInventories inventories) { - this.inventories = inventories; - } - - /** - * Hooks MultiInv for importing it's data. - * - * @param plugin Instance of MultiInv. - */ - public void hookMultiInv(MultiInv plugin) { - this.multiInvImporter = new MultiInvImporter(this.inventories, plugin); - Logging.info("Hooked MultiInv for importing!"); - } - - /** - * Hooks WorldInventories for importing it's data. - * - * @param plugin Instance of WorldInventories. - */ - public void hookWorldInventories(WorldInventories plugin) { - this.worldInventoriesImporter = new WorldInventoriesImporter(this.inventories, plugin); - Logging.info("Hooked WorldInventories for importing!"); - } - - /** - * @return The MultiInv importer class or null if not hooked. - */ - public MultiInvImporter getMultiInvImporter() { - return this.multiInvImporter; - } - - /** - * @return The WorldInventories importer class or null if not hooked. - */ - public WorldInventoriesImporter getWorldInventoriesImporter() { - return this.worldInventoriesImporter; - } - - /** - * Un-hooks MultiInv so we're not able to import from it anymore. - */ - public void unHookMultiInv() { - this.multiInvImporter = null; - } - - /** - * Un-hooks WorldInventories so we're not able to import from it anymore. - */ - public void unHookWorldInventories() { - this.worldInventoriesImporter = null; - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/migration/MigrationException.java b/src/main/java/com/onarandombox/multiverseinventories/migration/MigrationException.java deleted file mode 100644 index c169b378..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/migration/MigrationException.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.onarandombox.multiverseinventories.migration; - -/** - * Exception thrown when migration doesn't go well. - */ -public class MigrationException extends Exception { - - private Exception causeException = null; - - public MigrationException(String message) { - super(message); - } - - /** - * Sets what the causing exception was, if any. - * - * @param exception The cause exception. - * @return This exception for easy chainability. - */ - public MigrationException setCauseException(Exception exception) { - this.causeException = exception; - return this; - } - - /** - * @return The causing exception or null if none. - */ - public Exception getCauseException() { - return this.causeException; - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/migration/package-info.java b/src/main/java/com/onarandombox/multiverseinventories/migration/package-info.java deleted file mode 100644 index 139a8dbc..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/migration/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * This package contains thigns to help with importing player stats/inventory data from - * other similar plugins. - */ -package com.onarandombox.multiverseinventories.migration; - diff --git a/src/main/java/com/onarandombox/multiverseinventories/profile/GlobalProfile.java b/src/main/java/com/onarandombox/multiverseinventories/profile/GlobalProfile.java deleted file mode 100644 index 77c8c58b..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/profile/GlobalProfile.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.onarandombox.multiverseinventories.profile; - -import org.bukkit.Bukkit; - -import java.util.UUID; - -/** - * The global profile for a player which contains meta-data for the player. - */ -public final class GlobalProfile { - - /** - * Creates a global profile object for the given player with default values. - * - * @param playerName the player to create the profile object for. - * @return a new GlobalProfile for the given player. - * @deprecated Needs to use UUID. - */ - @Deprecated - public static GlobalProfile createGlobalProfile(String playerName) { - return new GlobalProfile(playerName, Bukkit.getOfflinePlayer(playerName).getUniqueId()); - } - - /** - * Creates a global profile object for the given player with default values. - * - * @param playerName the player to create the profile object for. - * @param playerUUID the UUID of the player to create the profile for. - * @return a new GlobalProfile for the given player. - */ - public static GlobalProfile createGlobalProfile(String playerName, UUID playerUUID) { - return new GlobalProfile(playerName, playerUUID); - } - - private final UUID uuid; - private String lastWorld = null; - private String lastKnownName; - private boolean loadOnLogin = false; - - private GlobalProfile(String name, UUID uuid) { - this.uuid = uuid; - this.lastKnownName = name; - } - - /** - * Returns the name of the player. - * - * @return The name of the player. - * @deprecated Use {@link #getPlayerUUID()} to uniquely identify a player. - * If you need player name, use {@link #getLastKnownName()}. - */ - @Deprecated - public String getPlayerName() { - return this.lastKnownName; - } - - /** - * Returns the UUID of the player. - * - * @return the UUID of the player. - */ - public UUID getPlayerUUID() { - return uuid; - } - - /** - * Returns the last name the player was known to have. - * - * @return the last name the player was known to have. - */ - public String getLastKnownName() { - return lastKnownName; - } - - /** - * Sets the last name that the player was seen having. - *

This should be updated when a player's name is changed through Mojang but only after their data has been - * migrated to the new name.

- * - * @param lastKnownName the last known name for the player. - */ - public void setLastKnownName(String lastKnownName) { - this.lastKnownName = lastKnownName; - } - - /** - * Returns the name of last world the player was in. - * - * @return The last world the player was in or null if not set. - */ - public String getLastWorld() { - return this.lastWorld; - } - - /** - * Says whether the player data for the player's logout world should be loaded when the player logs in. - * The default value is false. - * - * @return true if player data should be loaded when they log in. - */ - public boolean shouldLoadOnLogin() { - return loadOnLogin; - } - - /** - * Sets whether the player data for the player's logout world should be loaded when the player logs in. - * - * @param loadOnLogin true if player data should be loaded when they log in. - */ - public void setLoadOnLogin(boolean loadOnLogin) { - this.loadOnLogin = loadOnLogin; - } - - /** - * Sets the last world the player was known to be in. This is done automatically on world change. - * - * @param world The world the player is in. - */ - public void setLastWorld(String world) { - this.lastWorld = world; - } - - @Override - public String toString() { - return "GlobalProfile{" + - "uuid=" + uuid + - ", lastWorld='" + lastWorld + '\'' + - ", lastKnownName='" + lastKnownName + '\'' + - ", loadOnLogin=" + loadOnLogin + - '}'; - } -} diff --git a/src/main/java/com/onarandombox/multiverseinventories/profile/PlayerProfile.java b/src/main/java/com/onarandombox/multiverseinventories/profile/PlayerProfile.java deleted file mode 100644 index 2755e949..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/profile/PlayerProfile.java +++ /dev/null @@ -1,134 +0,0 @@ -package com.onarandombox.multiverseinventories.profile; - -import com.onarandombox.multiverseinventories.share.Sharable; -import com.onarandombox.multiverseinventories.share.SharableEntry; -import com.onarandombox.multiverseinventories.profile.container.ContainerType; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -/** - * Contains all the world/group specific data for a player. - */ -public final class PlayerProfile implements Cloneable, Iterable { - - public static PlayerProfile createPlayerProfile(ContainerType containerType, String containerName, - ProfileType profileType, OfflinePlayer player) { - return new PlayerProfile(containerType, containerName, profileType, player); - } - - /** - * @deprecated Needs to use UUID for players - */ - @Deprecated - public static PlayerProfile createPlayerProfile(ContainerType containerType, String containerName, - ProfileType profileType, String playerName) { - return new PlayerProfile(containerType, containerName, profileType, Bukkit.getOfflinePlayer(playerName)); - } - - private Map data = new HashMap(); - - private final OfflinePlayer player; - private final ContainerType containerType; - private final String containerName; - private final ProfileType profileType; - - private PlayerProfile(ContainerType containerType, String containerName, ProfileType profileType, OfflinePlayer player) { - this.containerType = containerType; - this.profileType = profileType; - this.containerName = containerName; - this.player = player; - } - - /** - * @return The container type of profile, a group or world. - */ - public ContainerType getContainerType() { - return this.containerType; - } - - /** - * @return The name of the container, world or group, containing this profile. - */ - public String getContainerName() { - return this.containerName; - } - - /** - * @return the Player associated with this profile. - */ - public OfflinePlayer getPlayer() { - return this.player; - } - - /** - * @return The type of profile this object represents. - */ - public ProfileType getProfileType() { - return this.profileType; - } - - /** - * Retrieves the profile's value of the {@link Sharable} passed in. - * - * @param sharable Represents the key for the data wanted from the profile. - * @param This indicates the type of return value to be expected. - * @return The value of the sharable for this profile. Null if no value is set. - */ - public T get(Sharable sharable) { - SharableEntry entry = this.data.get(sharable); - return sharable.getType().cast(entry != null ? entry.getValue() : null); - } - - /** - * Sets the profile's value for the {@link Sharable} passed in. - * - * @param sharable Represents the key for the data to store. - * @param value The value of the data. - * @param The type of value to be expected. - */ - public void set(Sharable sharable, T value) { - this.data.put(sharable, new SharableEntry(sharable, value)); - } - - public PlayerProfile clone() { - try { - return (PlayerProfile) super.clone(); - } catch (CloneNotSupportedException e) { - throw new RuntimeException(e); - } - } - - @Override - public Iterator iterator() { - return new SharablesIterator(data.values().iterator()); - } - - private static class SharablesIterator implements Iterator { - - private final Iterator backingIterator; - - private SharablesIterator(Iterator backingIterator) { - this.backingIterator = backingIterator; - } - - @Override - public boolean hasNext() { - return backingIterator.hasNext(); - } - - @Override - public SharableEntry next() { - return backingIterator.next(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/profile/ProfileDataSource.java b/src/main/java/com/onarandombox/multiverseinventories/profile/ProfileDataSource.java deleted file mode 100644 index 15427986..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/profile/ProfileDataSource.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.onarandombox.multiverseinventories.profile; - -import com.onarandombox.multiverseinventories.profile.container.ContainerType; - -import java.io.IOException; -import java.util.UUID; - -/** - * A source for updating and retrieving player profiles via persistence. - */ -public interface ProfileDataSource { - - /** - * Updates the persisted data for a player for a specific profile. - * - * - * @param playerProfile The profile for the player that is being updated. - */ - void updatePlayerData(PlayerProfile playerProfile); - - /** - * Retrieves a PlayerProfile from the data source. - * - * @param containerType The type of container this profile is part of, world or group. - * @param dataName World/Group to retrieve from. - * @param profileType The type of profile to load data for, typically based on game mode. - * @param playerUUID UUID of the player to retrieve for. - * @return The player as returned from data. If no data was found, a new PlayerProfile will be - * created. - */ - PlayerProfile getPlayerData(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID); - - /** - * Removes the persisted data for a player for a specific profile. - * - * @param containerType The type of container this profile is part of, world or group. - * @param dataName The name of the world/group the player's data is associated with. - * @param profileType The type of profile we're removing, as per {@link ProfileType}. If null, this will remove - * remove all profile types. - * @param playerName The name of the player whose data is being removed. - * @return True if successfully removed. - */ - boolean removePlayerData(ContainerType containerType, String dataName, ProfileType profileType, String playerName); - - /** - * Retrieves the global profile for a player which contains meta-data for the player. - * - * @param playerName The name of player to retrieve for. - * @return The global profile for the specified player. - * @deprecated UUID must be supported now. - */ - @Deprecated - GlobalProfile getGlobalProfile(String playerName); - - /** - * Retrieves the global profile for a player which contains meta-data for the player. - * - * @param playerName The name of the player to retrieve for. This is required for updating name last known as. - * @param playerUUID The UUID of the player. - * @return the global profile for the player with the given UUID. - */ - GlobalProfile getGlobalProfile(String playerName, UUID playerUUID); - - /** - * Update the file for a player's global profile. - * - * @param globalProfile The GlobalProfile object to update the file for. - * @return True if data successfully saved to file. - */ - boolean updateGlobalProfile(GlobalProfile globalProfile); - - /** - * A convenience method to update the GlobalProfile of a player with a specified world. - * - * @param playerName The player whose global profile this will update. - * @param worldName The world to update the global profile with. - */ - void updateLastWorld(String playerName, String worldName); - - /** - * A convenience method for setting whether player data should be loaded on login for the specified player. - * - * @param playerName The player whose data should be loaded. - * @param loadOnLogin Whether or not to load on login. - */ - void setLoadOnLogin(String playerName, boolean loadOnLogin); - - /** - * Copies all the data belonging to oldName to newName and removes the old data. - * - * @param oldName the previous name of the player. - * @param newName the new name of the player. - * @param playerUUID the UUID of the player. - * @param removeOldData whether or not to remove the data belonging to oldName. - * @throws IOException Thrown if something goes wrong while migrating the files. - */ - void migratePlayerData(String oldName, String newName, UUID playerUUID, boolean removeOldData) throws IOException; - - /** - * Clears a single profile in cache. - */ - void clearProfileCache(ProfileKey key); -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/profile/ProfileKey.java b/src/main/java/com/onarandombox/multiverseinventories/profile/ProfileKey.java deleted file mode 100644 index 0609391e..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/profile/ProfileKey.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.onarandombox.multiverseinventories.profile; - -import com.google.common.base.Objects; -import com.onarandombox.multiverseinventories.profile.container.ContainerType; -import org.bukkit.Bukkit; - -import java.util.UUID; - -public final class ProfileKey { - - public static ProfileKey createProfileKey(ContainerType containerType, String dataName, - ProfileType profileType, UUID playerUUID, String playerName) { - return new ProfileKey(containerType, dataName, profileType, playerUUID, playerName); - } - - public static ProfileKey createProfileKey(ContainerType containerType, String dataName, - ProfileType profileType, UUID playerUUID) { - return new ProfileKey(containerType, dataName, profileType, playerUUID); - } - - public static ProfileKey createProfileKey(ProfileKey copyKey, ContainerType containerType) { - return new ProfileKey(containerType, copyKey.getDataName(), copyKey.getProfileType(), copyKey.getPlayerUUID(), - copyKey.getPlayerName()); - } - - public static ProfileKey createProfileKey(ProfileKey copyKey, ProfileType profileType) { - return new ProfileKey(copyKey.getContainerType(), copyKey.getDataName(), profileType, copyKey.getPlayerUUID(), - copyKey.getPlayerName()); - } - - public static ProfileKey createProfileKey(ProfileKey copyKey, ContainerType containerType, - ProfileType profileType) { - return new ProfileKey(containerType, copyKey.getDataName(), profileType, copyKey.getPlayerUUID(), - copyKey.getPlayerName()); - } - - public static ProfileKey createProfileKey(PlayerProfile profile) { - return new ProfileKey(profile.getContainerType(), profile.getContainerName(), profile.getProfileType(), - profile.getPlayer().getUniqueId(), profile.getPlayer().getName()); - } - - private final ContainerType containerType; - private final String dataName; - private final ProfileType profileType; - private final String playerName; - private final UUID playerUUID; - - private ProfileKey(ContainerType containerType, String dataName, ProfileType profileType, UUID playerUUID) { - this.containerType = containerType; - this.dataName = dataName; - this.profileType = profileType; - this.playerUUID = playerUUID; - this.playerName = Bukkit.getOfflinePlayer(playerUUID).getName(); - } - - private ProfileKey(ContainerType containerType, String dataName, ProfileType profileType, - UUID playerUUID, String playerName) { - this.containerType = containerType; - this.dataName = dataName; - this.profileType = profileType; - this.playerUUID = playerUUID; - this.playerName = playerName; - } - - public ContainerType getContainerType() { - return containerType; - } - - public String getDataName() { - return dataName; - } - - public ProfileType getProfileType() { - return profileType; - } - - public String getPlayerName() { - return playerName; - } - - public UUID getPlayerUUID() { - return playerUUID; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ProfileKey)) return false; - final ProfileKey that = (ProfileKey) o; - return getContainerType() == that.getContainerType() && - Objects.equal(getDataName(), that.getDataName()) && - Objects.equal(getProfileType(), that.getProfileType()) && - Objects.equal(getPlayerName(), that.getPlayerName()) && - Objects.equal(getPlayerUUID(), that.getPlayerUUID()); - } - - @Override - public int hashCode() { - return Objects.hashCode(getContainerType(), getDataName(), getProfileType(), getPlayerName(), getPlayerUUID()); - } - - @Override - public String toString() { - return "ProfileKey{" + - "containerType=" + containerType + - ", dataName='" + dataName + '\'' + - ", profileType=" + profileType + - ", playerName='" + playerName + '\'' + - ", playerUUID=" + playerUUID + - '}'; - } -} diff --git a/src/main/java/com/onarandombox/multiverseinventories/profile/ProfileTypes.java b/src/main/java/com/onarandombox/multiverseinventories/profile/ProfileTypes.java deleted file mode 100644 index 4de6121b..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/profile/ProfileTypes.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.onarandombox.multiverseinventories.profile; - -import org.bukkit.GameMode; - -/** - * Static class for profile type lookup and protected registration. - */ -public final class ProfileTypes { - - /** - * The profile type for the SURVIVAL Game Mode. - */ - public static final ProfileType SURVIVAL = ProfileType.createProfileType("SURVIVAL"); - - /** - * The profile type for the CREATIVE Game Mode. - */ - public static final ProfileType CREATIVE = ProfileType.createProfileType("CREATIVE"); - - /** - * The profile type for the ADVENTURE Game Mode. - */ - public static final ProfileType ADVENTURE = ProfileType.createProfileType("ADVENTURE"); - - /** - * Returns the appropriate ProfileType for the given game mode. - * - * @param mode The game mode to get the profile type for. - * @return The profile type for the given game mode. - */ - public static ProfileType forGameMode(GameMode mode) { - switch (mode) { - case SURVIVAL: - return SURVIVAL; - case CREATIVE: - return CREATIVE; - case ADVENTURE: - return ADVENTURE; - default: - return SURVIVAL; - } - } - - private ProfileTypes() { - throw new AssertionError(); - } -} diff --git a/src/main/java/com/onarandombox/multiverseinventories/profile/container/ProfileContainer.java b/src/main/java/com/onarandombox/multiverseinventories/profile/container/ProfileContainer.java deleted file mode 100644 index e6ffc560..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/profile/container/ProfileContainer.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.onarandombox.multiverseinventories.profile.container; - -import com.onarandombox.multiverseinventories.profile.ProfileType; -import com.onarandombox.multiverseinventories.profile.PlayerProfile; -import org.bukkit.OfflinePlayer; -import org.bukkit.entity.Player; - -/** - * A container for player profiles in a given world or world group (based on {@link #getContainerType()}). - *

Players may have separate profiles per game mode within this container if game mode profiles are enabled.

- */ -public interface ProfileContainer { - - /** - * Returns the name of this profile container which is primarily used for persistence purposes. - *

The name reflects the world name if this is a world profile container, or the arbitrary group name if - * this is a world group profile container.

- * - * @return The name to use to look up Data. - */ - String getContainerName(); - - /** - * Returns the container type for this container. - * - * @return the container type. - */ - ContainerType getContainerType(); - - /** - * Retrieves the profile for the given player. - *

If game mode profiles are enabled, the profile for their current game mode will be returned, otherwise their - * survival profile will be returned.

- * - * @param player Player to get profile for. - * @return The profile for the given player. - */ - PlayerProfile getPlayerData(Player player); - - /** - * Retrieves the profile of the given type for the given player. - * - * @param profileType The type of profile to get data for, typically Survival or Creative. - * @param player Player to get profile for. - * @return The profile of the given type for the given player. - */ - PlayerProfile getPlayerData(ProfileType profileType, OfflinePlayer player); - - /** - * Adds a player profile to this profile container. - * - * @param playerProfile Player player to add. - */ - void addPlayerData(PlayerProfile playerProfile); - - /** - * Removes all of the profile data for a given player in this profile container. - * - * @param player Player to remove data for. - */ - void removeAllPlayerData(OfflinePlayer player); - - /** - * Removes the profile data for a specific type of profile in this profile container. - * - * @param profileType The type of profile to remove data for. - * @param player Player to remove data for. - */ - void removePlayerData(ProfileType profileType, OfflinePlayer player); - - /** - * Clears all cached data in the container. - */ - void clearContainer(); -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/profile/container/ProfileContainerStore.java b/src/main/java/com/onarandombox/multiverseinventories/profile/container/ProfileContainerStore.java deleted file mode 100644 index b7b4d9e2..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/profile/container/ProfileContainerStore.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.onarandombox.multiverseinventories.profile.container; - -/** - * A utility for storing and retrieving profile containers. - */ -public interface ProfileContainerStore { - - /** - * Adds a profile container to the store. - * - * @param container profile container to add. - */ - void addContainer(ProfileContainer container); - - /** - * Returns the profile container for the given name. - * - * @param containerName Name of the profile container to retrieve. - * @return the profile container for given name. - */ - ProfileContainer getContainer(String containerName); -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/InventorySerializer.java b/src/main/java/com/onarandombox/multiverseinventories/share/InventorySerializer.java deleted file mode 100644 index e1baa956..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/share/InventorySerializer.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.onarandombox.multiverseinventories.share; - -import com.onarandombox.multiverseinventories.util.MinecraftTools; -import org.bukkit.Material; -import org.bukkit.inventory.ItemStack; - -import java.util.HashMap; -import java.util.Map; - -/** - * A simple {@link SharableSerializer} usable with ItemStack[] which converts the ItemStack[] to the string format - * that is used by default in Multiverse-Inventories. - */ -public final class InventorySerializer implements SharableSerializer { - - private int inventorySize; - - public InventorySerializer(final int inventorySize) { - this.inventorySize = inventorySize; - } - - @Override - public ItemStack[] deserialize(Object obj) { - return unmapSlots(obj); - } - - @Override - public Object serialize(ItemStack[] itemStacks) { - return mapSlots(itemStacks); - } - - private Map mapSlots(ItemStack[] itemStacks) { - Map result = new HashMap<>(itemStacks.length); - for (int i = 0; i < itemStacks.length; i++) { - if (itemStacks[i] != null && itemStacks[i].getType() != Material.AIR) { - result.put(Integer.toString(i), itemStacks[i]); - } - } - return result; - } - - private ItemStack[] unmapSlots(Object obj) { - ItemStack[] result = new ItemStack[inventorySize]; - if (obj instanceof Map) { - Map invMap = (Map) obj; - for (int i = 0; i < result.length; i++) { - Object value = invMap.get(Integer.toString(i)); - if (value != null && value instanceof ItemStack) { - result[i] = (ItemStack) value; - } else { - result[i] = new ItemStack(Material.AIR); - } - } - return result; - } - return MinecraftTools.fillWithAir(result); - } -} diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/PersistingProfile.java b/src/main/java/com/onarandombox/multiverseinventories/share/PersistingProfile.java deleted file mode 100644 index 20fc431b..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/share/PersistingProfile.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.onarandombox.multiverseinventories.share; - -import com.onarandombox.multiverseinventories.profile.PlayerProfile; - -/** - * Simple interface for groups that are going to be saved/loaded. This is used specifically for when a user's world - * change is being handled. - */ -public interface PersistingProfile { - - /** - * @return The shares that will be saved/loaded for the profile. This is the set of all Sharables that will be acted - * upon when passed through the ShareHandler class, or any of its subclasses. - */ - Shares getShares(); - - /** - * @return The player profile for the world/group that will be saved/loaded for. - */ - PlayerProfile getProfile(); -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/SharableEntry.java b/src/main/java/com/onarandombox/multiverseinventories/share/SharableEntry.java deleted file mode 100644 index 418c1fdc..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/share/SharableEntry.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.onarandombox.multiverseinventories.share; - -public final class SharableEntry { - - private final Sharable sharable; - private final T value; - - public SharableEntry(Sharable sharable, T initialValue) { - this.sharable = sharable; - this.value = initialValue; - } - - public Sharable getSharable() { - return sharable; - } - - public T getValue() { - return value; - } -} diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/package-info.java b/src/main/java/com/onarandombox/multiverseinventories/share/package-info.java deleted file mode 100644 index 6f538d65..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/share/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Contains all external API classes for {@link com.onarandombox.multiverseinventories.share.Sharable}s and handling the sharing of those between worlds. - */ -package com.onarandombox.multiverseinventories.share; - diff --git a/src/main/java/com/onarandombox/multiverseinventories/util/CommentedYamlConfiguration.java b/src/main/java/com/onarandombox/multiverseinventories/util/CommentedYamlConfiguration.java deleted file mode 100644 index 718b7da6..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/util/CommentedYamlConfiguration.java +++ /dev/null @@ -1,278 +0,0 @@ -package com.onarandombox.multiverseinventories.util; - -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.Reader; -import java.io.StringWriter; -import java.io.Writer; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * A Configuration wrapper class that allows for comments to be applied to the config paths. - */ -public final class CommentedYamlConfiguration { - - private final File file; - private final FileConfiguration config; - private final boolean doComments; - private final HashMap comments; - - private static final Pattern NEW_LINE_PATTERN = Pattern.compile("\r?\n"); - - public CommentedYamlConfiguration(File file, boolean doComments) { - this.file = file; - this.doComments = doComments; - this.comments = new HashMap(); - this.config = new YamlConfiguration(); - - try { - this.config.load(file); - } catch (Exception ignored) {} - } - - /** - * @return The underlying configuration object. - */ - public FileConfiguration getConfig() { - return this.config; - } - - /** - * Saves the file as per normal for YamlConfiguration and then parses the file and inserts - * comments where necessary. - * - * @return True if successful. - */ - public boolean save() { - // try to save the config file, return false on failure - try { - config.save(file); - } catch (Exception e) { - return false; - } - - // if we're not supposed to add comments, or there aren't any to add, we're done - if (!doComments || comments.isEmpty()) { - return true; - } - - // convert config file to String - String stringConfig = this.convertFileToString(file); - - // figure out where the header ends - int indexAfterHeader = 0; - Matcher newline = NEW_LINE_PATTERN.matcher(stringConfig); - - while (newline.find() && stringConfig.charAt(newline.end()) == '#') { - indexAfterHeader = newline.end(); - } - - // convert stringConfig to array, ignoring the header - String[] arrayConfig = stringConfig.substring(indexAfterHeader).split(newline.group()); - - // begin building the new config, starting with the header - StringBuilder newContents = new StringBuilder(); - newContents.append(stringConfig, 0, indexAfterHeader); - - // This holds the current path the lines are at in the config - StringBuilder currentPath = new StringBuilder(); - - // This flags if the line is a node or unknown text. - boolean node; - // The depth of the path. (number of words separated by periods - 1) - int depth = 0; - - // Loop through the config lines - for (final String line : arrayConfig) { - // If the line is a node (and not something like a list value) - if (line.contains(": ") || (line.length() > 1 && line.charAt(line.length() - 1) == ':')) { - // This is a node so flag it as one - node = true; - - // Grab the index of the end of the node name - int index; - index = line.indexOf(": "); - if (index < 0) { - index = line.length() - 1; - } - // If currentPath is empty, store the node name as the currentPath. (this is only on the first iteration, i think) - if (currentPath.toString().isEmpty()) { - currentPath = new StringBuilder(line.substring(0, index)); - } else { - // Calculate the whitespace preceding the node name - int whiteSpace = 0; - for (int n = 0; n < line.length(); n++) { - if (line.charAt(n) == ' ') { - whiteSpace++; - } else { - break; - } - } - - int whiteSpaceDividedByTwo = whiteSpace / 2; - // Find out if the current depth (whitespace * 2) is greater/lesser/equal to the previous depth - if (whiteSpaceDividedByTwo > depth) { - // Path is deeper. Add a dot and the node name - currentPath.append(".").append(line, whiteSpace, index); - depth++; - } else if (whiteSpaceDividedByTwo < depth) { - // Path is shallower, calculate current depth from whitespace (whitespace / 2) and subtract that many levels from the currentPath - for (int i = 0; i < depth - whiteSpaceDividedByTwo; i++) { - currentPath.replace(currentPath.lastIndexOf("."), currentPath.length(), ""); - } - // Grab the index of the final period - int lastIndex = currentPath.lastIndexOf("."); - if (lastIndex < 0) { - // if there isn't a final period, set the current path to nothing because we're at root - currentPath = new StringBuilder(); - } else { - // If there is a final period, replace everything after it with nothing - currentPath.replace(currentPath.lastIndexOf("."), currentPath.length(), "").append("."); - } - // Add the new node name to the path - currentPath.append(line, whiteSpace, index); - // Reset the depth - depth = whiteSpaceDividedByTwo; - } else { - // Path is same depth, replace the last path node name to the current node name - int lastIndex = currentPath.lastIndexOf("."); - if (lastIndex < 0) { - // if there isn't a final period, set the current path to nothing because we're at root - currentPath = new StringBuilder(); - } else { - // If there is a final period, replace everything after it with nothing - currentPath.replace(currentPath.lastIndexOf("."), currentPath.length(), "").append("."); - } - - currentPath.append(line, whiteSpace, index); - } - } - } else { - node = false; - } - - StringBuilder newLine = new StringBuilder(); - - if (node) { - // get the comment for the current node - String comment = comments.get(currentPath.toString()); - if (comment != null && !comment.isEmpty()) { - // if the previous line doesn't end in a colon - // and there's not already a newline character, - // add a newline before we add the comment - char previousChar = newContents.charAt(newContents.length() - 2); - if (previousChar != ':' && previousChar != '\n') { - newLine.append("\n"); - } - - // add the comment - newLine.append(comment).append("\n"); - } - } - - // add the config line - newLine.append(line).append("\n"); - - // Add the (modified) line to the total config String - newContents.append(newLine); - } - - // try to save the config file, returning whether it saved successfully - return this.stringToFile(newContents.toString(), file); - } - - /** - * Adds a comment just before the specified path. The comment can be multiple lines. An empty string will indicate - * a blank line. - * - * @param path Configuration path to add comment. - * @param commentLines Comments to add. One String per line. - */ - public void addComment(String path, List commentLines) { - StringBuilder commentString = new StringBuilder(); - StringBuilder leadingSpaces = new StringBuilder(); - for (int n = 0; n < path.length(); n++) { - if (path.charAt(n) == '.') { - leadingSpaces.append(" "); - } - } - for (String line : commentLines) { - if (commentString.length() > 0) { - commentString.append("\n"); - } - if (!line.isEmpty()) { - commentString.append(leadingSpaces).append(line); - } - } - comments.put(path, commentString.toString()); - } - - /** - * Pass a file and it will return it's contents as a string. - * - * @param file File to read. - * @return Contents of file. String will be empty in case of any errors. - */ - private String convertFileToString(File file) { - final int bufferSize = 1024; - if (file != null && file.exists() && file.canRead() && !file.isDirectory()) { - Writer writer = new StringWriter(); - char[] buffer = new char[bufferSize]; - - try (InputStream is = new FileInputStream(file)) { - int n; - Reader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); - while ((n = reader.read(buffer)) != -1) { - writer.write(buffer, 0, n); - } - } catch (IOException e) { - e.printStackTrace(); - } - return writer.toString(); - } else { - return ""; - } - } - - /** - * Writes the contents of a string to a file. - * - * @param source String to write. - * @param file File to write to. - * @return True on success. - */ - private boolean stringToFile(String source, File file) { - OutputStreamWriter out = null; - try { - out = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8); - out.write(source); - out.close(); - return true; - } catch (IOException e) { - e.printStackTrace(); - return false; - } finally { - if (out != null) { - try { - out.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/util/Font.java b/src/main/java/com/onarandombox/multiverseinventories/util/Font.java deleted file mode 100644 index 71779d20..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/util/Font.java +++ /dev/null @@ -1,196 +0,0 @@ -package com.onarandombox.multiverseinventories.util; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -/** - * Class for font functions including font width specifications. - */ -public class Font { - - /** - * Provides the char for a Section symbol. - */ - public static final char SECTION_SYMBOL = (char) 167; - - private static final int LINE_LENGTH = 318; - private static final HashMap FONT_WIDTH; - - static { - FONT_WIDTH = new HashMap(); - /* - * Widths is in pixels - * Got them from fontWidths.txt uploaded to the Bukkit forum by Edward Hand - * http://forums.bukkit.org/threads/formatting-module-output-text-into-columns.8481/ - */ - // BEGIN CHECKSTYLE-SUPPRESSION: MagicNumberCheck - FONT_WIDTH.put(" ", 4); - FONT_WIDTH.put("!", 2); - FONT_WIDTH.put("\"", 5); - FONT_WIDTH.put("#", 6); - FONT_WIDTH.put("$", 6); - FONT_WIDTH.put("%", 6); - FONT_WIDTH.put("&", 6); - FONT_WIDTH.put("'", 3); - FONT_WIDTH.put("(", 5); - FONT_WIDTH.put(")", 5); - FONT_WIDTH.put("*", 5); - FONT_WIDTH.put("+", 6); - FONT_WIDTH.put(",", 2); - FONT_WIDTH.put("-", 6); - FONT_WIDTH.put(".", 2); - FONT_WIDTH.put("/", 6); - FONT_WIDTH.put("0", 6); - FONT_WIDTH.put("1", 6); - FONT_WIDTH.put("2", 6); - FONT_WIDTH.put("3", 6); - FONT_WIDTH.put("4", 6); - FONT_WIDTH.put("5", 6); - FONT_WIDTH.put("6", 6); - FONT_WIDTH.put("7", 6); - FONT_WIDTH.put("8", 6); - FONT_WIDTH.put("9", 6); - FONT_WIDTH.put(":", 2); - FONT_WIDTH.put(";", 2); - FONT_WIDTH.put("<", 5); - FONT_WIDTH.put("=", 6); - FONT_WIDTH.put(">", 5); - FONT_WIDTH.put("?", 6); - FONT_WIDTH.put("@", 7); - FONT_WIDTH.put("A", 6); - FONT_WIDTH.put("B", 6); - FONT_WIDTH.put("C", 6); - FONT_WIDTH.put("D", 6); - FONT_WIDTH.put("E", 6); - FONT_WIDTH.put("F", 6); - FONT_WIDTH.put("G", 6); - FONT_WIDTH.put("H", 6); - FONT_WIDTH.put("I", 4); - FONT_WIDTH.put("J", 6); - FONT_WIDTH.put("K", 6); - FONT_WIDTH.put("L", 6); - FONT_WIDTH.put("M", 6); - FONT_WIDTH.put("N", 6); - FONT_WIDTH.put("O", 6); - FONT_WIDTH.put("P", 6); - FONT_WIDTH.put("Q", 6); - FONT_WIDTH.put("R", 6); - FONT_WIDTH.put("S", 6); - FONT_WIDTH.put("T", 6); - FONT_WIDTH.put("U", 6); - FONT_WIDTH.put("V", 6); - FONT_WIDTH.put("W", 6); - FONT_WIDTH.put("X", 6); - FONT_WIDTH.put("Y", 6); - FONT_WIDTH.put("Z", 6); - FONT_WIDTH.put("_", 6); - FONT_WIDTH.put("'", 3); - FONT_WIDTH.put("a", 6); - FONT_WIDTH.put("b", 6); - FONT_WIDTH.put("c", 6); - FONT_WIDTH.put("d", 6); - FONT_WIDTH.put("e", 6); - FONT_WIDTH.put("f", 5); - FONT_WIDTH.put("g", 6); - FONT_WIDTH.put("h", 6); - FONT_WIDTH.put("i", 2); - FONT_WIDTH.put("j", 6); - FONT_WIDTH.put("k", 5); - FONT_WIDTH.put("l", 3); - FONT_WIDTH.put("m", 6); - FONT_WIDTH.put("n", 6); - FONT_WIDTH.put("o", 6); - FONT_WIDTH.put("p", 6); - FONT_WIDTH.put("q", 6); - FONT_WIDTH.put("r", 6); - FONT_WIDTH.put("s", 6); - FONT_WIDTH.put("t", 4); - FONT_WIDTH.put("u", 6); - FONT_WIDTH.put("v", 6); - FONT_WIDTH.put("w", 6); - FONT_WIDTH.put("x", 6); - FONT_WIDTH.put("y", 6); - FONT_WIDTH.put("z", 6); - // END CHECKSTYLE-SUPPRESSION: MagicNumberCheck - } - - private Font() { } - - /** - * Get width of string in pixels. - * - * @param text String. - * @return Length of string in pixels. - */ - public static int stringWidth(String text) { - if (FONT_WIDTH.isEmpty()) { - return 0; - } - char[] chars = text.toCharArray(); - int width = 0; - int spacepos = 0; - for (int i = 0; i < chars.length; i++) { - if (FONT_WIDTH.containsKey(String.valueOf(chars[i]))) { - width += FONT_WIDTH.get(String.valueOf(chars[i])); - } else if (chars[i] == SECTION_SYMBOL) { - i++; - } - } - return width; - } - - /** - * Get a List of Strings where none exceed the maximum line length. - * - * @param text String - * @return List of Strings - */ - public static List splitString(String text) { - List split = new ArrayList(); - if (FONT_WIDTH.isEmpty()) { - split.add(text); - return split; - } - char[] chars = text.toCharArray(); - int width = 0; - int lastspaceindex = 0; - int lastlineindex = 0; - String lastcolor = null; - boolean colorfoundthisline = false; - for (int i = 0; i < chars.length; i++) { - if (FONT_WIDTH.containsKey(String.valueOf(chars[i]))) { - width += FONT_WIDTH.get(String.valueOf(chars[i])); - } else if (chars[i] == SECTION_SYMBOL) { - i++; - lastcolor = Character.toString(chars[i]); - colorfoundthisline = true; - } - if ((width > LINE_LENGTH) && (lastspaceindex != 0)) { - if (lastcolor != null && !colorfoundthisline) { - split.add(Character.toString(SECTION_SYMBOL) + lastcolor - + text.substring(lastlineindex, lastspaceindex)); - } else { - split.add(text.substring(lastlineindex, lastspaceindex)); - } - colorfoundthisline = false; - lastlineindex = lastspaceindex; - i = lastspaceindex; - width = 0; - } - if (String.valueOf(chars[i]).equals(" ")) { - lastspaceindex = i; - } - } - if (!text.substring(lastlineindex).isEmpty()) - if (lastcolor != null && !colorfoundthisline) { - split.add(Character.toString(SECTION_SYMBOL) + lastcolor - + text.substring(lastlineindex)); - } else { - split.add(text.substring(lastlineindex)); - } - - return split; - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/util/MinecraftTools.java b/src/main/java/com/onarandombox/multiverseinventories/util/MinecraftTools.java deleted file mode 100644 index a79e49cb..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/util/MinecraftTools.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.onarandombox.multiverseinventories.util; - -import org.bukkit.Material; -import org.bukkit.inventory.ItemStack; - -/** - * General tools to help with minecraftian things. - */ -public class MinecraftTools { - - private static final int TICKS_PER_SECOND = 20; - - private MinecraftTools() { } - - /** - * Converts an amount of seconds to the appropriate amount of ticks. - * - * @param seconds Amount of seconds to convert - * @return Ticks converted from seconds. - */ - public static long convertSecondsToTicks(long seconds) { - return seconds * TICKS_PER_SECOND; - } - - /** - * Fills an ItemStack array with air. - * - * @param items The ItemStack array to fill. - * @return The air filled ItemStack array. - */ - public static ItemStack[] fillWithAir(ItemStack[] items) { - for (int i = 0; i < items.length; i++) { - items[i] = new ItemStack(Material.AIR); - } - return items; - } -} - diff --git a/src/main/java/com/onarandombox/multiverseinventories/util/package-info.java b/src/main/java/com/onarandombox/multiverseinventories/util/package-info.java deleted file mode 100644 index 0826b3ff..00000000 --- a/src/main/java/com/onarandombox/multiverseinventories/util/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * This package contains utility classes. - */ -package com.onarandombox.multiverseinventories.util; - diff --git a/src/main/java/com/onarandombox/multiverseinventories/InventoriesDupingPatch.java b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesDupingPatch.java similarity index 90% rename from src/main/java/com/onarandombox/multiverseinventories/InventoriesDupingPatch.java rename to src/main/java/org/mvplugins/multiverse/inventories/InventoriesDupingPatch.java index 13fcbeae..3460e0e3 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/InventoriesDupingPatch.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/InventoriesDupingPatch.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories; +package org.mvplugins.multiverse.inventories; import java.util.HashMap; import java.util.Iterator; @@ -17,6 +17,7 @@ import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.inventory.InventoryHolder; import org.bukkit.plugin.Plugin; +import org.jvnet.hk2.annotations.Service; /** * This is a simple patch that fixes a @@ -40,7 +41,7 @@ * @author Irmo van den Berge (bergerkiller) * @version 1.0 */ -class InventoriesDupingPatch { +final class InventoriesDupingPatch { private static final int SLOT_TIMEOUT = 5; @@ -77,7 +78,7 @@ public void onCreativeSlotChange(InventoryCreativeEvent event) { return; // Saves performance for most common case } InventoryHolder holder = event.getInventory().getHolder(); - if (holder instanceof Player && timeouts.containsKey(((Player) holder).getUniqueId())) { + if (holder instanceof Player player && timeouts.containsKey(player.getUniqueId())) { event.setResult(Result.DENY); } } @@ -90,8 +91,8 @@ public void onCreativeSlotChange(InventoryCreativeEvent event) { */ public void enable(Plugin plugin) { Bukkit.getPluginManager().registerEvents(this.listener, plugin); - this.updateTimeoutsTaskId = Bukkit.getScheduler().scheduleSyncRepeatingTask( - plugin, new UpdateTimeoutsTask(), 1, 1); + this.updateTimeoutsTaskId = Bukkit.getScheduler().runTaskTimer( + plugin, new UpdateTimeoutsTask(), 1, 1).getTaskId(); } /** @@ -114,13 +115,13 @@ public void run() { Iterator> iter = timeouts.entrySet().iterator(); while (iter.hasNext()) { Map.Entry e = iter.next(); - int value = e.getValue().intValue() - 1; + int value = e.getValue() - 1; if (value > 0) { - e.setValue(Integer.valueOf(value)); + e.setValue(value); } else { iter.remove(); } } } } -} \ No newline at end of file +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MVEventsListener.java b/src/main/java/org/mvplugins/multiverse/inventories/MVEventsListener.java new file mode 100644 index 00000000..0f0d136d --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/MVEventsListener.java @@ -0,0 +1,112 @@ +package org.mvplugins.multiverse.inventories; + +import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.event.MVConfigReloadEvent; +import org.mvplugins.multiverse.core.event.MVDebugModeEvent; +import org.mvplugins.multiverse.core.event.MVDumpsDebugInfoEvent; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.ProfileCacheManager; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; + +import java.io.File; + +@Service +final class MVEventsListener implements Listener { + + private final MultiverseInventories inventories; + private final InventoriesConfig config; + private final WorldGroupManager worldGroupManager; + private final ProfileCacheManager profileCacheManager; + + @Inject + MVEventsListener( + @NotNull MultiverseInventories inventories, + @NotNull InventoriesConfig config, + @NotNull WorldGroupManager worldGroupManager, + @NotNull ProfileCacheManager profileCacheManager) { + this.inventories = inventories; + this.config = config; + this.worldGroupManager = worldGroupManager; + this.profileCacheManager = profileCacheManager; + } + + /** + * Adds Multiverse-Inventories version info to /mv version. + * + * @param event The MVVersionEvent that this plugin will listen for. + */ + @EventHandler + void dumpsDebugInfoRequest(MVDumpsDebugInfoEvent event) { + event.appendDebugInfo(getDebugInfo()); + File configFile = new File(this.inventories.getDataFolder(), "config.yml"); + File groupsFile = new File(this.inventories.getDataFolder(), "groups.yml"); + event.putDetailedDebugInfo("multiverse-inventories/config.yml", configFile); + event.putDetailedDebugInfo("multiverse-inventories/groups.yml", groupsFile); + event.putDetailedDebugInfo("multiverse-inventories/cachestats.md", generateCacheStatsContent()); + } + + private String generateCacheStatsContent() { + var builder = new StringBuilder(); + profileCacheManager.getCacheStats().forEach((cacheName, stats) -> { + builder.append("# ").append(cacheName).append("\n") + .append("- hits count: ").append(stats.hitCount()).append("\n") + .append("- misses count: ").append(stats.missCount()).append("\n") + .append("- loads count: ").append(stats.loadCount()).append("\n") + .append("- misses count: ").append(stats.missCount()).append("\n") + .append("- evictions: ").append(stats.evictionCount()).append("\n") + .append("- hit rate: ").append(stats.hitRate() * 100).append("%\n") + .append("- miss rate: ").append(stats.missRate() * 100).append("%\n") + .append("- avg load penalty: ").append(stats.averageLoadPenalty() / 1000000).append("ms\n") + .append("\n"); + }); + return builder.toString(); + } + + /** + * Builds a String containing Multiverse-Inventories' version info. + * + * @return The version info. + */ + private String getDebugInfo() { + StringBuilder versionInfo = new StringBuilder("[Multiverse-Inventories] Multiverse-Inventories Version: " + inventories.getDescription().getVersion() + '\n' + + "[Multiverse-Inventories] === Settings ===" + '\n' + + "[Multiverse-Inventories] First Run: " + config.getFirstRun() + '\n' + + "[Multiverse-Inventories] Using Bypass: " + config.getEnableBypassPermissions() + '\n' + + "[Multiverse-Inventories] Default Ungrouped Worlds: " + config.getDefaultUngroupedWorlds() + '\n' + + "[Multiverse-Inventories] Save and Load on Log In and Out: " + config.getApplyPlayerdataOnJoin() + '\n' + + "[Multiverse-Inventories] Using GameMode Profiles: " + config.getEnableGamemodeShareHandling() + '\n' + + "[Multiverse-Inventories] === Shares ===" + '\n' + + "[Multiverse-Inventories] Optionals for Ungrouped Worlds: " + config.getUseOptionalsForUngroupedWorlds() + '\n' + + "[Multiverse-Inventories] Enabled Optionals: " + config.getActiveOptionalShares() + '\n' + + "[Multiverse-Inventories] === Groups ===" + '\n'); + + for (WorldGroup group : worldGroupManager.getGroups()) { + versionInfo.append("[Multiverse-Inventories] ").append(group.toString()).append('\n'); + } + + return versionInfo.toString(); + } + + @EventHandler + void onDebugModeChange(MVDebugModeEvent event) { + Logging.setDebugLevel(event.getLevel()); + } + + /** + * Hooks Multiverse-Inventories into the Multiverse reload command. + * + * @param event The MVConfigReloadEvent that this plugin will listen for. + */ + @EventHandler + void configReload(MVConfigReloadEvent event) { + this.inventories.reloadConfig(); + event.addConfig("Multiverse-Inventories - config.yml"); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java new file mode 100644 index 00000000..f4f233da --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -0,0 +1,258 @@ +package org.mvplugins.multiverse.inventories; + +import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.entity.Player; +import org.bukkit.plugin.PluginManager; +import org.mvplugins.multiverse.core.config.CoreConfig; +import org.mvplugins.multiverse.core.destination.DestinationsProvider; +import org.mvplugins.multiverse.core.module.MultiverseModule; +import org.mvplugins.multiverse.core.utils.StringFormatter; +import org.mvplugins.multiverse.inventories.command.MVInvCommandConditions; +import org.mvplugins.multiverse.inventories.command.MVInvCommandPermissions; +import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; +import org.mvplugins.multiverse.inventories.command.MVInvCommandCompletion; +import org.mvplugins.multiverse.inventories.command.MVInvCommandContexts; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.dataimport.DataImportManager; +import org.mvplugins.multiverse.inventories.dataimport.DataImporter; +import org.mvplugins.multiverse.inventories.destination.LastLocationDestination; +import org.mvplugins.multiverse.inventories.handleshare.ShareHandleListener; +import org.mvplugins.multiverse.inventories.handleshare.SingleShareWriter; +import org.mvplugins.multiverse.inventories.handleshare.SpawnChangeListener; +import org.mvplugins.multiverse.inventories.handleshare.WriteOnlyShareHandler; +import org.mvplugins.multiverse.inventories.profile.PlayerNamesMapper; +import org.mvplugins.multiverse.inventories.profile.ProfileCacheManager; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.util.ItemStackConverter; +import org.mvplugins.multiverse.inventories.util.Perm; +import org.bukkit.Bukkit; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jakarta.inject.Provider; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.vavr.control.Try; + +/** + * Multiverse-Inventories plugin main class. + */ +@Service +public class MultiverseInventories extends MultiverseModule { + + private static final double TARGET_CORE_API_VERSION = 5.0; + + @Inject + private Provider coreConfig; + @Inject + private Provider destinationsProvider; + @Inject + private Provider inventoriesConfig; + @Inject + private Provider shareHandleListener; + @Inject + private Provider respawnListener; + @Inject + private Provider mvEventsListener; + @Inject + private Provider worldGroupManager; + @Inject + private Provider playerNamesMapperProvider; + @Inject + private Provider profileDataSource; + @Inject + private Provider profileCacheManager; + @Inject + private Provider profileContainerStoreProvider; + @Inject + private Provider dataImportManager; + @Inject + private Provider mvInvCommandCompletion; + @Inject + private Provider mvInvCommandContexts; + @Inject + private Provider mvInvCommandConditions; + @Inject + private Provider mvInvCommandPermissions; + + private InventoriesDupingPatch dupingPatch; + private boolean usingSpawnChangeEvent = false; + + public MultiverseInventories() { + super(); + } + + /** + * {@inheritDoc} + */ + @Override + public void onLoad() { + Logging.init(this); + this.getDataFolder().mkdirs(); + } + + /** + * {@inheritDoc} + */ + @Override + public final void onEnable() { + super.onEnable(); + + initializeDependencyInjection(new MultiverseInventoriesPluginBinder(this)); + ProfileTypes.init(this); + Sharables.init(this); + Perm.register(this); + ItemStackConverter.init(this); + Logging.fine("ItemStackConverter is using byte serialization: " + ItemStackConverter.hasByteSerializeSupport()); + this.reloadConfig(); + inventoriesConfig.get().save().onFailure(e -> Logging.severe("Failed to save config file!")); + + // Register Stuff + this.registerEvents(); + this.setUpLocales(); + this.registerCommands(); + this.registerDestinations(); + + // Hook plugins that can be imported from + this.hookImportables(); + + // Init other extensions + this.hookLuckPerms(); + this.dupingPatch = InventoriesDupingPatch.enableDupingPatch(this); + this.playerNamesMapperProvider.get().loadMap(); + + // Init api + MultiverseInventoriesApi.init(this.serviceLocator); + + Logging.config("Version %s (API v%s) Enabled - By %s", + this.getDescription().getVersion(), getVersionAsNumber(), StringFormatter.joinAnd(this.getDescription().getAuthors())); + } + + /** + * {@inheritDoc} + */ + @Override + public void onDisable() { + super.onDisable(); + + for (final Player player : getServer().getOnlinePlayers()) { + SingleShareWriter.of(this, player, Sharables.LAST_LOCATION).write(player.getLocation().clone()); + new WriteOnlyShareHandler(this, player).handleSharing(); + if (inventoriesConfig.get().getApplyPlayerdataOnJoin()) { + profileDataSource.get().modifyGlobalProfile( + GlobalProfileKey.of(player), profile -> profile.setLoadOnLogin(true)); + } + } + + MultiverseInventoriesApi.shutdown(); + this.dupingPatch.disable(); + this.shutdownDependencyInjection(); + Logging.shutdown(); + } + + private void registerEvents() { + PluginManager pluginManager = this.getServer().getPluginManager(); + pluginManager.registerEvents(shareHandleListener.get(), this); + pluginManager.registerEvents(respawnListener.get(), this); + pluginManager.registerEvents(mvEventsListener.get(), this); + if (inventoriesConfig.get().getUseImprovedRespawnLocationDetection()) { + try { + Class.forName("org.bukkit.event.player.PlayerSpawnChangeEvent"); + pluginManager.registerEvents(new SpawnChangeListener(this), this); + usingSpawnChangeEvent = true; + Logging.fine("Yayy PlayerSpawnChangeEvent will be used!"); + } catch (ClassNotFoundException e) { + Logging.fine("PlayerSpawnChangeEvent will not be used!"); + } + } + } + + private void registerCommands() { + Try.run(() -> { + mvInvCommandCompletion.get(); + mvInvCommandContexts.get(); + mvInvCommandConditions.get(); + mvInvCommandPermissions.get(); + }).onFailure(e -> { + Logging.warning("Failed to register command completers: %s", e.getMessage()); + }); + registerCommands(InventoriesCommand.class); + } + + private void registerDestinations() { + destinationsProvider.get().registerDestination(serviceLocator.getService(LastLocationDestination.class)); + } + + private void hookImportables() { + serviceLocator.getAllServices(DataImporter.class).forEach(dataImporter -> { + dataImportManager.get().register(dataImporter); + }); + } + + private void hookLuckPerms() { + Try.run(() -> Class.forName("net.luckperms.api.LuckPerms")) + .onFailure(e -> Logging.fine("Luckperms is not installed!")) + .andThenTry(() -> { + Logging.fine("Found luckperms!"); + serviceLocator.getService(WorldGroupContextCalculator.class); + }); + } + + /** + * {@inheritDoc} + */ + @Override + public double getTargetCoreVersion() { + return TARGET_CORE_API_VERSION; + } + + /** + * Nulls the config object and reloads a new one, also resetting the world groups in memory. + */ + @Override + public void reloadConfig() { + try { + Logging.setDebugLevel(coreConfig.get().getGlobalDebug()); + + inventoriesConfig.get().load().onFailure(e -> { + Logging.severe("Failed to load config file!"); + Logging.severe(e.getMessage()); + }); + worldGroupManager.get().load().onFailure(e -> { + Logging.severe("Failed to load world groups!"); + Logging.severe(e.getMessage()); + }); + profileContainerStoreProvider.get().clearCache(); + + if (profileDataSource.get() != null) { + profileCacheManager.get().clearAllCache(); + } + + Logging.fine("Reloaded all config and groups!"); + } catch (Exception e) { // Catch errors loading the config file and exit out if found. + Logging.severe("Encountered an error while loading the configuration file. Disabling..."); + Logging.severe(e.getMessage()); + Bukkit.getPluginManager().disablePlugin(this); + return; + } + + this.getServer().getScheduler().runTaskLater(this, () -> { + // Create initial World Group for first run IF NO GROUPS EXIST + if (inventoriesConfig.get().getFirstRun()) { + Logging.info("First run!"); + if (worldGroupManager.get().getGroups().isEmpty()) { + worldGroupManager.get().createDefaultGroup(); + } + + inventoriesConfig.get().setFirstRun(false); + } + worldGroupManager.get().checkForConflicts(null); + }, 1L); + } + + public boolean isUsingSpawnChangeEvent() { + return usingSpawnChangeEvent; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesApi.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesApi.java new file mode 100644 index 00000000..46c32213 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesApi.java @@ -0,0 +1,124 @@ +package org.mvplugins.multiverse.inventories; + +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.core.inject.PluginServiceLocator; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.dataimport.DataImportManager; +import org.mvplugins.multiverse.inventories.profile.PlayerNamesMapper; +import org.mvplugins.multiverse.inventories.profile.ProfileCacheManager; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; + +import java.util.Objects; + +/** + * Provides access to the Multiverse-Inventories API. + */ +public final class MultiverseInventoriesApi { + + private static MultiverseInventoriesApi instance; + + static void init(@NotNull PluginServiceLocator serviceLocator) { + if (instance != null) { + throw new IllegalStateException("MultiverseCoreApi has already been initialized!"); + } + instance = new MultiverseInventoriesApi(serviceLocator); + } + + static void shutdown() { + instance = null; + } + + /** + * Gets the MultiverseInventoriesApi. This will throw an exception if the Multiverse-Inventories has not been initialized. + * + * @return The MultiverseInventoriesApi + */ + public static @NotNull MultiverseInventoriesApi get() { + if (instance == null) { + throw new IllegalStateException("MultiverseInventoriesApi has not been initialized!"); + } + return instance; + } + + private final PluginServiceLocator serviceLocator; + + private MultiverseInventoriesApi(@NotNull PluginServiceLocator serviceProvider) { + this.serviceLocator = serviceProvider; + } + + /** + * Gets instance of our DataImportManager api. + * + * @return The DataImportManager instance + */ + public @NotNull DataImportManager getDataImportManager() { + return Objects.requireNonNull(serviceLocator.getService(DataImportManager.class)); + } + + /** + * Gets instance of our InventoriesConfig api. + * + * @return The InventoriesConfig instance + */ + public @NotNull InventoriesConfig getInventoriesConfig() { + return Objects.requireNonNull(serviceLocator.getService(InventoriesConfig.class)); + } + + /** + * Gets instance of our PlayerNamesMapper api. + * + * @return The PlayerNamesMapper instance + */ + public @NotNull PlayerNamesMapper getPlayerNamesMapper() { + return Objects.requireNonNull(serviceLocator.getService(PlayerNamesMapper.class)); + } + + /** + * Gets instance of our ProfileCacheManager api. + * + * @return The ProfileCacheManager instance + */ + public @NotNull ProfileCacheManager getProfileCacheManager() { + return Objects.requireNonNull(serviceLocator.getService(ProfileCacheManager.class)); + } + + /** + * Gets instance of our ProfileContainerStoreProvider api. + * + * @return The ProfileContainerStoreProvider instance + */ + public @NotNull ProfileContainerStoreProvider getProfileContainerStoreProvider() { + return Objects.requireNonNull(serviceLocator.getService(ProfileContainerStoreProvider.class)); + } + + /** + * Gets instance of our ProfileDataSource api. + * + * @return The ProfileDataSource instance + */ + public @NotNull ProfileDataSource getProfileDataSource() { + return Objects.requireNonNull(serviceLocator.getService(ProfileDataSource.class)); + } + + /** + * Gets instance of our WorldGroupManager api. + * + * @return The WorldGroupManager instance + */ + public @NotNull WorldGroupManager getWorldGroupManager() { + return Objects.requireNonNull(serviceLocator.getService(WorldGroupManager.class)); + } + + /** + * Gets the instance of Multiverse-Inventories's PluginServiceLocator. + *
+ * You can use this to hook into the hk2 dependency injection system used by Multiverse-Inventories. + * + * @return The Multiverse-Inventories's PluginServiceLocator + */ + public @NotNull PluginServiceLocator getServiceLocator() { + return serviceLocator; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesPluginBinder.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesPluginBinder.java new file mode 100644 index 00000000..fa8bf235 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventoriesPluginBinder.java @@ -0,0 +1,18 @@ +package org.mvplugins.multiverse.inventories; + +import org.mvplugins.multiverse.core.module.MultiverseModuleBinder; +import org.mvplugins.multiverse.external.glassfish.hk2.utilities.binding.ScopedBindingBuilder; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; + +final class MultiverseInventoriesPluginBinder extends MultiverseModuleBinder { + + MultiverseInventoriesPluginBinder(@NotNull MultiverseInventories plugin) { + super(plugin); + } + + @Override + protected ScopedBindingBuilder bindPluginClass + (ScopedBindingBuilder bindingBuilder) { + return super.bindPluginClass(bindingBuilder).to(MultiverseInventories.class); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/RespawnListener.java b/src/main/java/org/mvplugins/multiverse/inventories/RespawnListener.java new file mode 100644 index 00000000..176b6917 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/RespawnListener.java @@ -0,0 +1,134 @@ +package org.mvplugins.multiverse.inventories; + +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerRespawnEvent; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; +import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; + +import java.util.List; + +/** + * Specific events for handling player respawns location + */ +@Service +final class RespawnListener implements Listener { + + private final WorldGroupManager worldGroupManager; + private final WorldManager worldManager; + + private List currentGroups; + private Location spawnLoc = null; + + @Inject + RespawnListener(WorldGroupManager worldGroupManager, WorldManager worldManager) { + this.worldGroupManager = worldGroupManager; + this.worldManager = worldManager; + } + + /** + * Handles player respawns at the LOWEST priority. + * + * @param event The player respawn event. + */ + @EventHandler(priority = EventPriority.LOWEST) + void lowestPriorityRespawn(PlayerRespawnEvent event) { + if (!event.isBedSpawn()) { + World world = event.getPlayer().getWorld(); + this.currentGroups = worldGroupManager.getGroupsForWorld(world.getName()); + this.handleRespawn(event, EventPriority.LOWEST); + } + } + + /** + * Handles player respawns at the LOW priority. + * + * @param event The player respawn event. + */ + @EventHandler(priority = EventPriority.LOW) + void lowPriorityRespawn(PlayerRespawnEvent event) { + if (!event.isBedSpawn()) { + this.handleRespawn(event, EventPriority.LOW); + } + } + + /** + * Handles player respawns at the NORMAL priority. + * + * @param event The player respawn event. + */ + @EventHandler(priority = EventPriority.NORMAL) + void normalPriorityRespawn(PlayerRespawnEvent event) { + if (!event.isBedSpawn()) { + this.handleRespawn(event, EventPriority.NORMAL); + } + } + + /** + * Handles player respawns at the HIGH priority. + * + * @param event The player respawn event. + */ + @EventHandler(priority = EventPriority.HIGH) + void highPriorityRespawn(PlayerRespawnEvent event) { + if (!event.isBedSpawn()) { + this.handleRespawn(event, EventPriority.HIGH); + } + } + + /** + * Handles player respawns at the HIGHEST priority. + * + * @param event The player respawn event. + */ + @EventHandler(priority = EventPriority.HIGHEST) + void highestPriorityRespawn(PlayerRespawnEvent event) { + if (!event.isBedSpawn()) { + this.handleRespawn(event, EventPriority.HIGHEST); + } + } + + /** + * Handles player respawns at the MONITOR priority. + * + * @param event The player respawn event. + */ + @EventHandler(priority = EventPriority.MONITOR) + void monitorPriorityRespawn(PlayerRespawnEvent event) { + if (!event.isBedSpawn()) { + this.handleRespawn(event, EventPriority.MONITOR); + this.updateCompass(event); + } + } + + private void handleRespawn(PlayerRespawnEvent event, EventPriority priority) { + for (WorldGroup group : this.currentGroups) { + if (!group.getSpawnPriority().equals(priority)) { + continue; + } + String spawnWorldName = group.getSpawnWorld(); + if (spawnWorldName == null) { + continue; + } + LoadedMultiverseWorld mvWorld = worldManager.getLoadedWorld(spawnWorldName).getOrNull(); + if (mvWorld != null) { + this.spawnLoc = mvWorld.getSpawnLocation(); + event.setRespawnLocation(this.spawnLoc); + break; + } + } + } + + private void updateCompass(PlayerRespawnEvent event) { + if (event.getRespawnLocation().equals(this.spawnLoc)) { + event.getPlayer().setCompassTarget(this.spawnLoc); + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/WorldGroupContextCalculator.java b/src/main/java/org/mvplugins/multiverse/inventories/WorldGroupContextCalculator.java new file mode 100644 index 00000000..56c2378b --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/WorldGroupContextCalculator.java @@ -0,0 +1,53 @@ +package org.mvplugins.multiverse.inventories; + +import com.dumptruckman.minecraft.util.Logging; +import net.luckperms.api.LuckPermsProvider; +import net.luckperms.api.context.ContextCalculator; +import net.luckperms.api.context.ContextConsumer; +import net.luckperms.api.context.ContextSet; +import net.luckperms.api.context.ImmutableContextSet; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.annotation.PostConstruct; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.vavr.control.Try; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; + +@Service +final class WorldGroupContextCalculator implements ContextCalculator { + + private static final String WORLD_GROUP_CONTEXT_KEY = "mvinv:worldgroup"; + private final WorldGroupManager worldGroupManager; + + @Inject + WorldGroupContextCalculator(WorldGroupManager worldGroupManager) { + this.worldGroupManager = worldGroupManager; + } + + @PostConstruct + private void registerCalculator() { + Try.of(LuckPermsProvider::get) + .peek(luckPerms -> luckPerms.getContextManager().registerCalculator(this)) + .onFailure(e -> Logging.warning("Failed to hook LuckPerms! %s", e.getMessage())); + } + + @Override + public void calculate(@NotNull Player player, @NotNull ContextConsumer contextConsumer) { + ImmutableContextSet.Builder contextBuilder = ImmutableContextSet.builder(); + worldGroupManager.getGroupsForWorld(player.getWorld().getName()) + .forEach(worldGroup -> contextBuilder.add(WORLD_GROUP_CONTEXT_KEY, worldGroup.getName())); + + contextConsumer.accept(contextBuilder.build()); + } + + @NotNull + @Override + public ContextSet estimatePotentialContexts() { + ImmutableContextSet.Builder contextBuilder = ImmutableContextSet.builder(); + worldGroupManager.getGroups() + .forEach(worldGroup -> contextBuilder.add(WORLD_GROUP_CONTEXT_KEY, worldGroup.getName())); + + return contextBuilder.build(); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandCompletion.java b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandCompletion.java new file mode 100644 index 00000000..7f18bd1f --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandCompletion.java @@ -0,0 +1,179 @@ +package org.mvplugins.multiverse.inventories.command; + +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandCompletions; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.core.config.handle.PropertyModifyAction; +import org.mvplugins.multiverse.core.utils.StringFormatter; +import org.mvplugins.multiverse.external.acf.commands.BukkitCommandCompletionContext; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.vavr.control.Try; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.dataimport.DataImportManager; +import org.mvplugins.multiverse.inventories.profile.PlayerNamesMapper; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.share.Sharables; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.mvplugins.multiverse.core.utils.StringFormatter.addonToCommaSeperated; + +@Service +public final class MVInvCommandCompletion { + + private final InventoriesConfig inventoriesConfig; + private final WorldGroupManager worldGroupManager; + private final DataImportManager dataImportManager; + private final PlayerNamesMapper playerNamesMapper; + + @Inject + private MVInvCommandCompletion( + @NotNull InventoriesConfig inventoriesConfig, + @NotNull WorldGroupManager worldGroupManager, + @NotNull DataImportManager dataImportManager, + @NotNull MVCommandManager mvCommandManager, + @NotNull PlayerNamesMapper playerNamesMapper + ) { + this.inventoriesConfig = inventoriesConfig; + this.worldGroupManager = worldGroupManager; + this.dataImportManager = dataImportManager; + this.playerNamesMapper = playerNamesMapper; + + MVCommandCompletions commandCompletions = mvCommandManager.getCommandCompletions(); + commandCompletions.registerAsyncCompletion("dataimporters", this::suggestDataImporters); + commandCompletions.registerStaticCompletion("mvinvconfigs", inventoriesConfig.getStringPropertyHandle().getAllPropertyNames()); + commandCompletions.registerAsyncCompletion("mvinvconfigvalues", this::suggestConfigValues); + commandCompletions.registerAsyncCompletion("mvinvplayernames", this::suggestPlayerNames); + commandCompletions.registerAsyncCompletion("mvinvprofiletypes", this::suggestProfileTypes); + commandCompletions.registerAsyncCompletion("sharables", this::suggestSharables); + commandCompletions.registerAsyncCompletion("shares", this::suggestShares); + commandCompletions.registerAsyncCompletion("worldGroups", this::suggestWorldGroups); + commandCompletions.registerAsyncCompletion("worldGroupWorlds", this::suggestWorldGroupWorlds); + } + + private Collection suggestDataImporters(BukkitCommandCompletionContext context) { + return dataImportManager.getEnabledImporterNames(); + } + + private Collection suggestConfigValues(BukkitCommandCompletionContext context) { + return Try.of(() -> context.getContextValue(String.class)) + .map(propertyName -> inventoriesConfig.getStringPropertyHandle() + .getSuggestedPropertyValue(propertyName, context.getInput(), PropertyModifyAction.SET)) + .getOrElse(Collections.emptyList()); + } + + private Collection suggestPlayerNames(BukkitCommandCompletionContext context) { + if (Objects.equals(context.getInput(), "@all")) { + return Collections.emptyList(); + } + List playerNames = getPlayerNames(); + if (context.getInput().indexOf(',') == -1) { + playerNames.add("@all"); + return playerNames; + } + return StringFormatter.addonToCommaSeperated(context.getInput(), playerNames); + } + + private List getPlayerNames() { + return playerNamesMapper.getKeys() + .stream() + .map(GlobalProfileKey::getPlayerName) + .collect(Collectors.toList()); + } + + private Collection suggestProfileTypes(BukkitCommandCompletionContext context) { + if (!context.hasConfig("multiple")) { + return ProfileTypes.getTypes().stream() + .map(ProfileType::getName) + .map(String::toLowerCase) + .toList(); + } + + if (Objects.equals(context.getInput(), "@all")) { + return Collections.emptyList(); + } + List profileTypes = ProfileTypes.getTypes() + .stream() + .map(ProfileType::getName) + .map(String::toLowerCase) + .collect(Collectors.toList()); + if (context.getInput().indexOf(',') == -1) { + profileTypes.add("@all"); + return profileTypes; + } + return StringFormatter.addonToCommaSeperated(context.getInput(), profileTypes); + } + + private Collection suggestSharables(BukkitCommandCompletionContext context) { + String scope = context.getConfig("scope", "enabled"); + + return Sharables.all().stream() + .filter(sharable -> switch (scope) { + case "enabled" -> + !sharable.isOptional() || inventoriesConfig.getActiveOptionalShares().contains(sharable); + case "optional" -> sharable.isOptional(); + default -> true; + }) + .filter(sharable -> sharable.getNames().length > 0) + .map(sharable -> sharable.getNames()[0]) + .toList(); + } + + private Collection suggestShares(BukkitCommandCompletionContext context) { + String input = context.getInput(); + + if (input.isEmpty()) { + // No input, so we're suggesting the first share + return Sharables.getShareNames(); + } + + int lastComma = input.lastIndexOf(","); + if (lastComma == -1) { + // No comma, so we're suggesting the first share + if (input.startsWith("-")) { + return Sharables.getShareNames().stream() + .map(name -> "-" + name) + .collect(Collectors.toList()); + } + return Sharables.getShareNames(); + } + + // We're suggesting a share after a comma + String lastShare = input.substring(lastComma + 1); + String currentSharesString = input.substring(0, lastComma + (lastShare.startsWith("-") ? 2 : 1)); + Set currentShares = Arrays.stream(input.split(",")) + .map(share -> share.startsWith("-") ? share.substring(1) : share) + .collect(Collectors.toSet()); + + return Sharables.getShareNames().stream() + .filter(name -> !currentShares.contains(name)) + .map(name -> currentSharesString + name) + .toList(); + } + + private Collection suggestWorldGroups(BukkitCommandCompletionContext context) { + return worldGroupManager.getGroups().stream() + .map(WorldGroup::getName) + .toList(); + } + + private Collection suggestWorldGroupWorlds(BukkitCommandCompletionContext context) { + + var worlds = Try.of(() -> context.getContextValue(WorldGroup.class)) + .map(WorldGroup::getWorlds) + .getOrElse(Collections.emptySet()); + + return addonToCommaSeperated(context.getInput(), worlds); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandConditions.java b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandConditions.java new file mode 100644 index 00000000..b6e28aea --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandConditions.java @@ -0,0 +1,47 @@ +package org.mvplugins.multiverse.inventories.command; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.BukkitCommandExecutionContext; +import org.mvplugins.multiverse.external.acf.commands.BukkitCommandIssuer; +import org.mvplugins.multiverse.external.acf.commands.BukkitConditionContext; +import org.mvplugins.multiverse.external.acf.commands.CommandConditions; +import org.mvplugins.multiverse.external.acf.commands.ConditionContext; +import org.mvplugins.multiverse.external.acf.commands.ConditionFailedException; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +@Service +public final class MVInvCommandConditions { + + private final WorldGroupManager worldGroupManager; + + @Inject + private MVInvCommandConditions(@NotNull MVCommandManager commandManager, @NotNull WorldGroupManager worldGroupManager) { + this.worldGroupManager = worldGroupManager; + + CommandConditions commandConditions + = commandManager.getCommandConditions(); + commandConditions.addCondition(Sharable.class, "optionalSharable", this::checkOptionalSharable); + commandConditions.addCondition(String.class, "newWorldGroupName", this::checkNewWorldGroupName); + } + + private void checkOptionalSharable(ConditionContext context, + BukkitCommandExecutionContext executionContext, + Sharable sharable) { + if (sharable == null || !sharable.isOptional()) { + throw new ConditionFailedException(MVInvi18n.TOGGLE_NOOPTIONALSHARES); + } + } + + private void checkNewWorldGroupName(ConditionContext context, + BukkitCommandExecutionContext executionContext, + String worldGroupName) { + if (worldGroupManager.getGroup(worldGroupName) != null) { + throw new ConditionFailedException(MVInvi18n.GROUP_EXISTS, "{group}", worldGroupName); + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandContexts.java b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandContexts.java new file mode 100644 index 00000000..dbe0a7ef --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandContexts.java @@ -0,0 +1,185 @@ +package org.mvplugins.multiverse.inventories.command; + +import com.google.common.base.Strings; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.core.utils.REPatterns; +import org.mvplugins.multiverse.external.acf.commands.BukkitCommandExecutionContext; +import org.mvplugins.multiverse.external.acf.commands.CommandContexts; +import org.mvplugins.multiverse.external.acf.commands.InvalidCommandArgument; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.external.vavr.control.Option; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.PlayerNamesMapper; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.profile.key.ContainerKey; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +@Service +public final class MVInvCommandContexts { + + private final WorldGroupManager worldGroupManager; + private final PlayerNamesMapper playerNamesMapper; + private final InventoriesConfig config; + private final ProfileDataSource profileDataSource; + + @Inject + private MVInvCommandContexts( + @NotNull MVCommandManager commandManager, + @NotNull WorldGroupManager worldGroupManager, + @NotNull PlayerNamesMapper playerNamesMapper, + @NotNull InventoriesConfig config, + @NotNull ProfileDataSource profileDataSource + ) { + this.worldGroupManager = worldGroupManager; + this.playerNamesMapper = playerNamesMapper; + this.config = config; + this.profileDataSource = profileDataSource; + + CommandContexts commandContexts = commandManager.getCommandContexts(); + commandContexts.registerContext(ContainerKey[].class, this::parseContainerKeyArray); + commandContexts.registerContext(GlobalProfileKey[].class, this::parseGlobalProfileKeyArray); + commandContexts.registerIssuerAwareContext(ProfileType.class, this::parseProfileType); + commandContexts.registerIssuerAwareContext(ProfileType[].class, this::parseProfileTypeArray); + commandContexts.registerContext(Sharable.class, this::parseSharable); + commandContexts.registerContext(Shares.class, this::parseShares); + commandContexts.registerContext(WorldGroup.class, this::parseWorldGroup); + } + + private ProfileType parseProfileType(BukkitCommandExecutionContext context) { + if (!config.getEnableGamemodeShareHandling()) { + return ProfileTypes.getDefault(); + } + String profileType = context.popFirstArg(); + return ProfileTypes.forName(profileType) + .getOrElseThrow(() -> new InvalidCommandArgument("Invalid profile type: " + profileType)); + } + + private ContainerKey[] parseContainerKeyArray(BukkitCommandExecutionContext context) { + String keyStrings = context.popFirstArg(); + if (keyStrings.equals("@all")) { + return Arrays.stream(ContainerType.values()).flatMap(containerType -> + profileDataSource.listContainerDataNames(containerType) + .stream() + .map(containerName -> ContainerKey.create(containerType, containerName))) + .toArray(ContainerKey[]::new); + } + List containerKeys = new ArrayList<>(); + String[] typesSplit = REPatterns.SEMICOLON.split(keyStrings); + for (String typeSplit : typesSplit) { + String[] keyValueSplit = REPatterns.EQUALS.split(typeSplit, 2); + if (keyValueSplit.length != 2) { + // todo: Probably error invalid format + continue; + } + ContainerType containerType = ContainerType.valueOf(keyValueSplit[0].toUpperCase()); + String[] dataNameSplit = REPatterns.COMMA.split(keyValueSplit[1]); + List availableDataNames = profileDataSource.listContainerDataNames(containerType); + for (String dataName : dataNameSplit) { + if (availableDataNames.contains(dataName)) { + containerKeys.add(ContainerKey.create(containerType, dataName)); + } + } + } + return containerKeys.toArray(new ContainerKey[0]); + } + + private GlobalProfileKey[] parseGlobalProfileKeyArray(BukkitCommandExecutionContext context) { + String keyStrings = context.popFirstArg(); + if (keyStrings.equals("@all")) { + return playerNamesMapper.getKeys().toArray(GlobalProfileKey[]::new); + } + // todo: UUID parsing + String[] profileNames = REPatterns.COMMA.split(keyStrings); + return Arrays.stream(profileNames) + .map(playerNamesMapper::getKey) + .filter(Option::isDefined) + .map(Option::get) + .toArray(GlobalProfileKey[]::new); + } + + private ProfileType[] parseProfileTypeArray(BukkitCommandExecutionContext context) { + String keyStrings = context.popFirstArg(); + if (keyStrings.equals("@all")) { + return ProfileTypes.getTypes().toArray(ProfileType[]::new); + } + String[] profileNames = REPatterns.COMMA.split(keyStrings); + return Arrays.stream(profileNames) + .map(ProfileTypes::forName) + .filter(Option::isDefined) + .map(Option::get) + .toArray(ProfileType[]::new); + } + + private Sharable parseSharable(BukkitCommandExecutionContext context) { + String sharableName = context.popFirstArg(); + Sharable targetSharable = Sharables.all().stream() + .filter(sharable -> sharable.getNames().length > 0) + .filter(sharable -> sharable.getNames()[0].equals(sharableName)) + .findFirst() + .orElse(null); + + if (targetSharable != null) { + return targetSharable; + } + if (context.isOptional()) { + return null; + } + throw new InvalidCommandArgument(MVInvi18n.ERROR_NOSHARESSPECIFIED); + } + + private Shares parseShares(BukkitCommandExecutionContext context) { + String shareStrings = context.popFirstArg(); + if (Strings.isNullOrEmpty(shareStrings)) { + throw new InvalidCommandArgument(MVInvi18n.ERROR_NOSHARESSPECIFIED); + } + + String[] shareNames = shareStrings.split(","); + Shares newShares = Sharables.noneOf(); + Shares negativeShares = Sharables.noneOf(); + for (String shareName : shareNames) { + if (shareName.startsWith("-")) { + shareName = shareName.substring(1); + Option.of(Sharables.lookup(shareName)) + .peek(shares -> negativeShares.setSharing(shares, true)); + continue; + } + Option.of(Sharables.lookup(shareName)) + .peek(shares -> newShares.setSharing(shares, true)); + } + + newShares.setSharing(negativeShares, false); + if (newShares.isEmpty()) { + throw new InvalidCommandArgument(MVInvi18n.ERROR_NOSHARESSPECIFIED); + } + + return newShares; + } + + private WorldGroup parseWorldGroup(BukkitCommandExecutionContext context) { + String groupName = context.popFirstArg(); + WorldGroup group = worldGroupManager.getGroup(groupName); + if (group != null) { + return group; + } + if (context.isOptional()) { + return null; + } + throw new InvalidCommandArgument(MVInvi18n.ERROR_NOGROUP); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandPermissions.java b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandPermissions.java new file mode 100644 index 00000000..5a88faa9 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/command/MVInvCommandPermissions.java @@ -0,0 +1,20 @@ +package org.mvplugins.multiverse.inventories.command; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.core.command.MVCommandPermissions; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; + +@Service +public class MVInvCommandPermissions { + + @Inject + MVInvCommandPermissions(@NotNull MVCommandManager commandManager, @NotNull InventoriesConfig config) { + + MVCommandPermissions commandPermissions = commandManager.getCommandPermissions(); + commandPermissions.registerPermissionChecker("mvinv-gamemode-profile-true", commandIssuer -> config.getEnableGamemodeShareHandling()); + commandPermissions.registerPermissionChecker("mvinv-gamemode-profile-false", commandIssuer -> !config.getEnableGamemodeShareHandling()); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddDisabledSharesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddDisabledSharesCommand.java new file mode 100644 index 00000000..ab29340d --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddDisabledSharesCommand.java @@ -0,0 +1,53 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.share.Shares; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + +@Service +final class AddDisabledSharesCommand extends InventoriesCommand { + + private final WorldGroupManager worldGroupManager; + + @Inject + AddDisabledSharesCommand(@NotNull WorldGroupManager worldGroupManager) { + this.worldGroupManager = worldGroupManager; + } + + @Subcommand("add-disabled-shares") + @CommandPermission("multiverse.inventories.adddisabledshares") + @CommandCompletion("@worldGroups @shares") + @Syntax(" ") + @Description("Add one or more disabled shares to a group.") + void onAddDisabledSharesCommand( + MVCommandIssuer issuer, + + @Syntax("") + @Description("Group you want to add the disabled shares to.") + WorldGroup group, + + @Syntax("") + @Description("One or more sharables to disable for the given group.") + Shares shares + ) { + group.getDisabledShares().mergeShares(shares); + worldGroupManager.updateGroup(group); + issuer.sendInfo(MVInvi18n.DISABLEDSHARES_NOWSHARING, + replace("{group}").with(group.getName()), + replace("{shares}").with(group.getDisabledShares().toStringList())); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddSharesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddSharesCommand.java new file mode 100644 index 00000000..080398cc --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddSharesCommand.java @@ -0,0 +1,58 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.BukkitCommandIssuer; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + +@Service +final class AddSharesCommand extends InventoriesCommand { + + private final WorldGroupManager worldGroupManager; + + @Inject + AddSharesCommand(@NotNull WorldGroupManager worldGroupManager) { + this.worldGroupManager = worldGroupManager; + } + + @Subcommand("add-shares") + @CommandPermission("multiverse.inventories.addshares") + @CommandCompletion("@worldGroups @shares") + @Syntax(" ") + @Description("Add one or more shares to a group.") + void onAddSharesCommand( + MVCommandIssuer issuer, + + @Syntax("") + @Description("Group you want to add the shares to.") + WorldGroup group, + + @Syntax("") + @Description("One or more sharables to add.") + Shares shares + ) { + group.getShares().mergeShares(shares); + worldGroupManager.updateGroup(group); + var negativeshares = Sharables.allOf(); + negativeshares.setSharing(group.getShares(), false); + issuer.sendInfo(MVInvi18n.SHARES_NOWSHARING, + replace("{group}").with(group.getName()), + replace("{shares}").with(group.getShares().toStringList()), + replace("{negativeshares}").with(negativeshares.toStringList())); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/AddWorldsCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddWorldsCommand.java new file mode 100644 index 00000000..3cf97d1e --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/AddWorldsCommand.java @@ -0,0 +1,64 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; +import org.mvplugins.multiverse.core.world.MultiverseWorld; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import java.util.Arrays; +import java.util.List; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + +@Service +final class AddWorldsCommand extends InventoriesCommand { + + private final WorldGroupManager worldGroupManager; + + @Inject + AddWorldsCommand(@NotNull WorldGroupManager worldGroupManager) { + this.worldGroupManager = worldGroupManager; + } + + @Subcommand("add-worlds") + @CommandPermission("multiverse.inventories.addworlds") + @CommandCompletion("@worldGroups @mvworlds:multiple,scope=both") + @Syntax(" ") + @Description("Adds a World to a World Group.") + void onAddWorldCommand( + MVCommandIssuer issuer, + + @Syntax("") + @Description("Group you want to add the world to.") + WorldGroup group, + + @Syntax("") + @Description("World name to add.") + MultiverseWorld[] worlds + ) { + List worldNames = Arrays.stream(worlds).map(MultiverseWorld::getName).toList(); + String worldNamesString = String.join(", ", worldNames); + if (!group.getWorlds().addAll(worldNames)) { + issuer.sendError(MVInvi18n.ADDWORLD_WORLDALREADYEXISTS, + replace("{group}").with(group.getName()), + replace("{world}").with(worldNamesString)); + return; + } + worldGroupManager.updateGroup(group); + issuer.sendInfo(MVInvi18n.ADDWORLD_WORLDADDED, + replace("{group}").with(group.getName()), + replace("{world}").with(worldNamesString)); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java new file mode 100644 index 00000000..f63bd0ed --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/CacheCommand.java @@ -0,0 +1,63 @@ +package org.mvplugins.multiverse.inventories.commands; + +import com.github.benmanes.caffeine.cache.stats.CacheStats; +import org.bukkit.entity.Player; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Flags; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.profile.ProfileCacheManager; + +import java.util.Map; + +@Service +final class CacheCommand extends InventoriesCommand { + + private final ProfileCacheManager ProfileCacheManager; + + @Inject + CacheCommand(@NotNull ProfileCacheManager ProfileCacheManager) { + this.ProfileCacheManager = ProfileCacheManager; + } + + @Subcommand("cache stats") + @CommandPermission("multiverse.inventories.cache.stats") + void onCacheStatsCommand(MVCommandIssuer issuer) { + Map stats = this.ProfileCacheManager.getCacheStats(); + for (Map.Entry entry : stats.entrySet()) { + issuer.sendMessage("Cache: " + entry.getKey()); + issuer.sendMessage(" hits count: " + entry.getValue().hitCount()); + issuer.sendMessage(" misses count: " + entry.getValue().missCount()); + issuer.sendMessage(" loads count: " + entry.getValue().loadCount()); + issuer.sendMessage(" evictions: " + entry.getValue().evictionCount()); + issuer.sendMessage(" hit rate: " + entry.getValue().hitRate() * 100 + "%"); + issuer.sendMessage(" miss rate: " + entry.getValue().missRate() * 100 + "%"); + issuer.sendMessage(" avg load penalty: " + entry.getValue().averageLoadPenalty() / 1000000 + "ms"); + issuer.sendMessage("--------"); + } + } + + @Subcommand("cache invalidate all") + @CommandPermission("multiverse.inventories.cache.invalidate") + void onCacheClearAllCommand(MVCommandIssuer issuer) { + this.ProfileCacheManager.clearAllCache(); + } + + @Subcommand("cache invalidate player") + @CommandPermission("multiverse.inventories.cache.invalidate") + @CommandCompletion("@players") + @Syntax("") + void onCacheClearProfileCommand( + MVCommandIssuer issuer, + + @Flags("resolve=issuerAware") + Player player) { + this.ProfileCacheManager.clearPlayerProfileCache(key -> + key.getPlayerUUID().equals(player.getUniqueId())); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java new file mode 100644 index 00000000..998df4b6 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ConfigCommand.java @@ -0,0 +1,68 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.core.exceptions.MultiverseException; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Optional; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.vavr.control.Option; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; + +@Service +final class ConfigCommand extends InventoriesCommand { + + private final InventoriesConfig config; + + @Inject + ConfigCommand(@NotNull InventoriesConfig config) { + this.config = config; + } + + @Subcommand("config") + @CommandPermission("multiverse.inventories.config") + @CommandCompletion("@mvinvconfigs @mvinvconfigvalues") + @Syntax(" [value]") + @Description("Show or set a config value.") + void onConfigCommand( + MVCommandIssuer issuer, + + @Syntax("") + @Description("The name of the config to set or show.") + String name, + + @Optional + @Syntax("[value]") + @Description("The value to set the config to. If not specified, the current value will be shown.") + String value) { + if (value == null) { + showConfigValue(issuer, name); + return; + } + updateConfigValue(issuer, name, value); + } + + private void showConfigValue(MVCommandIssuer issuer, String name) { + config.getStringPropertyHandle().getProperty(name) + .onSuccess(value -> issuer.sendMessage(name + "is currently set to " + value)) + .onFailure(e -> issuer.sendMessage(e.getMessage())); + } + + private void updateConfigValue(MVCommandIssuer issuer, String name, String value) { + // TODO: Update with localization + config.getStringPropertyHandle().setPropertyString(name, value) + .onSuccess(ignore -> { + config.save(); + issuer.sendMessage("Successfully set " + name + " to " + value); + }) + .onFailure(ignore -> issuer.sendMessage("Unable to set " + name + " to " + value + ".")) + .onFailure(MultiverseException.class, e -> Option.of(e.getLocalizableMessage()).peek(issuer::sendError)); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java new file mode 100644 index 00000000..8717975c --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/CreateGroupCommand.java @@ -0,0 +1,68 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.core.world.MultiverseWorld; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Conditions; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Optional; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.share.Shares; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import java.util.Arrays; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + +@Service +final class CreateGroupCommand extends InventoriesCommand { + private final WorldGroupManager worldGroupManager; + + @Inject + CreateGroupCommand(@NotNull WorldGroupManager worldGroupManager) { + this.worldGroupManager = worldGroupManager; + } + + @Subcommand("create-group") + @CommandPermission("multiverse.inventories.creategroup") + @CommandCompletion("@empty @mvworlds:multiple,scope=both @shares") + @Syntax(" [share[,extra]] [world[,extra]]") + @Description("Creates a new empty World Group with no worlds and no shares.") + void onCreateGroupCommand( + MVCommandIssuer issuer, + + @Conditions("newWorldGroupName") + @Syntax("") + @Description("New group name to create.") + @NotNull String groupName, + + @Optional + @Syntax("[world[,extra]]") + MultiverseWorld[] worlds, + + @Optional + @Syntax("[share,[extra]]") + Shares shares + ) { + WorldGroup worldGroup = worldGroupManager.newEmptyGroup(groupName); + if (worlds != null) { + worldGroup.getWorlds().addAll(Arrays.stream(worlds).map(MultiverseWorld::getName).toList()); + } + if (shares != null) { + worldGroup.getShares().mergeShares(shares); + } + worldGroupManager.updateGroup(worldGroup); + issuer.sendInfo(MVInvi18n.GROUP_CREATIONCOMPLETE, replace("{group}").with(groupName)); + issuer.sendInfo(MVInvi18n.INFO_GROUP_INFO, replace("{worlds}").with(worldGroup.getWorlds())); + issuer.sendInfo(MVInvi18n.INFO_GROUP_INFOSHARES, replace("{shares}").with(worldGroup.getShares())); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/DeleteGroupCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/DeleteGroupCommand.java new file mode 100644 index 00000000..42529934 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/DeleteGroupCommand.java @@ -0,0 +1,58 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.core.command.queue.CommandQueueManager; +import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + +@Service +final class DeleteGroupCommand extends InventoriesCommand { + private final CommandQueueManager commandQueueManager; + private final WorldGroupManager worldGroupManager; + + @Inject + DeleteGroupCommand( + @NotNull CommandQueueManager commandQueueManager, + @NotNull WorldGroupManager worldGroupManager + ) { + this.commandQueueManager = commandQueueManager; + this.worldGroupManager = worldGroupManager; + } + + @Subcommand("delete-group") + @CommandPermission("multiverse.inventories.deletegroup") + @CommandCompletion("@worldGroups") + @Syntax("") + @Description("Deletes a World Group.") + void onDeleteGroupCommand( + MVCommandIssuer issuer, + + @Syntax("") + @Description("Inventories group to delete.") + WorldGroup group + ) { + commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) + .prompt(Message.of(MVInvi18n.DELETEGROUP_CONFIRMPROMPT, replace("{group}").with(group.getName()))) + .action(() -> doDeleteGroup(issuer, group))); + } + + private void doDeleteGroup(MVCommandIssuer issuer, WorldGroup group) { + worldGroupManager.removeGroup(group); + issuer.sendInfo(MVInvi18n.DELETEGROUP_SUCCESS, replace("{group}").with(group.getName())); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java new file mode 100644 index 00000000..9556d071 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/GiveCommand.java @@ -0,0 +1,186 @@ +package org.mvplugins.multiverse.inventories.commands; + +import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.utils.REPatterns; +import org.mvplugins.multiverse.core.world.MultiverseWorld; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.vavr.control.Try; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.handleshare.SingleShareReader; +import org.mvplugins.multiverse.inventories.handleshare.SingleShareWriter; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.util.PlayerStats; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; + +@Service +final class GiveCommand extends InventoriesCommand { + + private final MultiverseInventories inventories; + private final ProfileDataSource profileDataSource; + + @Inject + GiveCommand( + @NotNull MultiverseInventories inventories, + @NotNull ProfileDataSource profileDataSource + ) { + this.inventories = inventories; + this.profileDataSource = profileDataSource; + } + + // TODO Better offline player parsing + @Subcommand("give") + @CommandPermission("multiverse.inventories.give") + @CommandCompletion("@players " + + "@mvworlds:scope=both " + + "@mvinvprofiletypes:checkPermissions=@mvinv-gamemode-profile-true|@materials:checkPermissions=@mvinv-gamemode-profile-false " + + "@materials:checkPermissions=@mvinv-gamemode-profile-true|@range:64,checkPermissions=@mvinv-gamemode-profile-false " + + "@range:64,checkPermissions=@mvinv-gamemode-profile-true|@empty " + + "@empty") + @Syntax(" [gamemode] [amount]") + @Description("World and Group Information") + void onGiveCommand( + MVCommandIssuer issuer, + + @Syntax("") + OfflinePlayer player, + + @Syntax("") + MultiverseWorld world, + + @Syntax("[gamemode]") + ProfileType profileType, + + @Syntax(" [amount]") + String item + ) { + ItemStack itemStack = parseItemFromString(issuer, item); + if (itemStack == null) { + return; + } + Logging.finer("Giving player " + player.getName() + " item: " + itemStack); + + // Giving online player in same world + Player onlinePlayer = player.getPlayer(); + if (onlinePlayer != null + && world.getName().equals(onlinePlayer.getWorld().getName()) + && ProfileTypes.forPlayer(onlinePlayer).equals(profileType)) { + onlinePlayer.getInventory().addItem(itemStack); + issuer.sendInfo("Gave player %s %s %s in world %s." + .formatted(player.getName(), itemStack.getAmount(), itemStack, world.getName())); + return; + } + + SingleShareReader.of(inventories, player, world.getName(), profileType, Sharables.INVENTORY) + .read() + .thenCompose(inventory -> updatePlayerInventory(issuer, player, world, profileType, inventory, itemStack)) + .exceptionally(throwable -> { + issuer.sendError(throwable.getMessage()); + return null; + }); + } + + private @Nullable ItemStack parseItemFromString(MVCommandIssuer issuer, String item) { + // Get amount + int amount = 1; + AtomicBoolean endIsAmount = new AtomicBoolean(false); + int lastSpace = item.lastIndexOf(' '); + if (lastSpace != -1) { + String amountString = item.substring(lastSpace + 1); + amount = Try.of(() -> Integer.parseInt(amountString)) + .peek(ignore -> endIsAmount.set(true)) + .getOrElse(1); + } + if (amount < 1) { + issuer.sendError("You have to give at least 1 item."); + return null; + } + if (amount > 6400) { + issuer.sendError("Cannot give more than 6400 items at once."); + return null; + } + // Remove amount string from item + if (endIsAmount.get()) { + item = item.substring(0, lastSpace); + } + + // Get material + String[] split = REPatterns.get("\\[").split(item, 2); + String itemName = split[0]; + Material material = Material.matchMaterial(itemName); + if (material == null) { + issuer.sendError("Invalid Material: " + split[0]); + return null; + } + + // Create item and parse additional vanilla component data + ItemStack itemStack = new ItemStack(material, amount); + if (split.length < 2) { + return itemStack; + } + String additionalData = split[1]; + return Try.of(() -> Bukkit.getUnsafe().modifyItemStack(itemStack, itemStack.getType().getKey() + "[" + additionalData)) + .onFailure(throwable -> issuer.sendError(throwable.getMessage())) + .getOrNull(); + } + + private CompletableFuture updatePlayerInventory( + MVCommandIssuer issuer, + OfflinePlayer player, + MultiverseWorld world, + ProfileType profileType, + @Nullable ItemStack[] inventory, + @NotNull ItemStack itemStack + ) { + putItemInInventory(inventory, itemStack); + return SingleShareWriter.of(inventories, player, world.getName(), profileType, Sharables.INVENTORY) + .write(inventory, true) + .thenCompose(ignore -> player.isOnline() + ? CompletableFuture.completedFuture(null) + : profileDataSource.modifyGlobalProfile(GlobalProfileKey.of(player), profile -> profile.setLoadOnLogin(true))) + .thenRun(() -> issuer.sendInfo("Gave player %s %s %s in world %s." + .formatted(player.getName(), itemStack.getAmount(), itemStack.getI18NDisplayName(), world.getName()))); + } + + private void putItemInInventory(@Nullable ItemStack[] inventory, @NotNull ItemStack itemStack) { + if (inventory == null) { + inventory = new ItemStack[PlayerStats.INVENTORY_SIZE]; + } + int amountLeft = itemStack.getAmount(); + for (int i = 0; i < inventory.length; i++) { + if (inventory[i] == null || inventory[i].getType() == Material.AIR) { + int amountToGive = Math.min(amountLeft, itemStack.getMaxStackSize()); + inventory[i] = itemStack.clone(); + inventory[i].setAmount(amountToGive); + amountLeft -= amountToGive; + } else if (inventory[i].isSimilar(itemStack)) { + int amountToGive = Math.min(amountLeft, itemStack.getMaxStackSize() - inventory[i].getAmount()); + inventory[i].setAmount(inventory[i].getAmount() + amountToGive); + amountLeft -= amountToGive; + } + + if (amountLeft == 0) { + break; + } + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java new file mode 100644 index 00000000..ec821f38 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/GroupCommand.java @@ -0,0 +1,59 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.mvplugins.multiverse.core.command.LegacyAliasCommand; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.commands.prompts.GroupControlPrompt; +import org.bukkit.command.CommandSender; +import org.bukkit.conversations.Conversable; +import org.bukkit.conversations.Conversation; +import org.bukkit.conversations.ConversationFactory; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +@Service +class GroupCommand extends InventoriesCommand { + + private final MultiverseInventories plugin; + + @Inject + GroupCommand(@NotNull MultiverseInventories plugin) { + this.plugin = plugin; + } + + @Subcommand("group") + @CommandPermission("multiverse.inventories.group") + @Description("Manage a world group with prompts!") + void onGroupCommand(@NotNull MVCommandIssuer issuer) { + if (!(issuer.getIssuer() instanceof Conversable conversable)) { + issuer.sendError(MVInvi18n.GROUP_NONCONVERSABLE); + return; + } + Conversation conversation = new ConversationFactory(plugin) + .withFirstPrompt(new GroupControlPrompt(plugin, issuer)) + .withEscapeSequence("##") + .withModality(false).buildConversation(conversable); + conversation.begin(); + } + + @Service + private final static class LegacyAlias extends GroupCommand implements LegacyAliasCommand { + @Inject + LegacyAlias(MultiverseInventories plugin) { + super(plugin); + } + + @Override + @CommandAlias("mvinvgroup|mvinvg") + void onGroupCommand(MVCommandIssuer issuer) { + super.onGroupCommand(issuer); + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/HelpCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/HelpCommand.java new file mode 100644 index 00000000..54462e67 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/HelpCommand.java @@ -0,0 +1,38 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.CommandHelp; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; + +@Service +final class HelpCommand extends InventoriesCommand { + + private final MVCommandManager commandManager; + + @Inject + HelpCommand(@NotNull MVCommandManager commandManager) { + this.commandManager = commandManager; + } + + @org.mvplugins.multiverse.external.acf.commands.annotation.HelpCommand + @Subcommand("help") + @CommandPermission("multiverse.inventories.help") + @CommandCompletion("@commands:mvinv") + @Syntax("[filter] [page]") + @Description("Displays a list of available commands.") + void onUsageCommand(CommandHelp help) { + if (help.getIssuer().isPlayer()) { + // Prevent flooding the chat + help.setPerPage(4); + } + this.commandManager.showUsage(help); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java new file mode 100644 index 00000000..0ffc436c --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/InfoCommand.java @@ -0,0 +1,130 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.mvplugins.multiverse.core.command.LegacyAliasCommand; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.bukkit.Bukkit; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Optional; +import org.mvplugins.multiverse.external.acf.commands.annotation.Single; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import java.util.List; +import java.util.Set; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + +@Service +class InfoCommand extends InventoriesCommand { + + private final ProfileContainerStoreProvider profileContainerStoreProvider; + private final WorldGroupManager worldGroupManager; + + @Inject + InfoCommand( + @NotNull ProfileContainerStoreProvider profileContainerStoreProvider, + @NotNull WorldGroupManager worldGroupManager + ) { + this.profileContainerStoreProvider = profileContainerStoreProvider; + this.worldGroupManager = worldGroupManager; + } + + @Subcommand("info") + @CommandPermission("multiverse.inventories.info") + @CommandCompletion("@mvworlds") + @Syntax("") + @Description("World and Group Information") + void onInfoCommand( + @NotNull MVCommandIssuer issuer, + + @Optional + @Single + @Syntax("") + @Description("World or Group") + @NotNull String name + ) { + if (name == null) { + if (!issuer.isPlayer()) { + issuer.sendError(MVInvi18n.INFO_ZEROARG); + return; + } + name = issuer.getPlayer().getWorld().getName(); + } + + ProfileContainer worldProfileContainer = profileContainerStoreProvider.getStore(ContainerType.WORLD).getContainer(name); + issuer.sendInfo(MVInvi18n.INFO_WORLD, replace("{world}").with(name)); + if (worldProfileContainer != null && Bukkit.getWorld(worldProfileContainer.getContainerName()) != null) { + worldInfo(issuer, worldProfileContainer); + } else { + issuer.sendError(MVInvi18n.ERROR_NOWORLDPROFILE, replace("{world}").with(name)); + } + WorldGroup worldGroup = worldGroupManager.getGroup(name); + issuer.sendInfo(MVInvi18n.INFO_GROUP, replace("{group}").with(name)); + if (worldGroup != null) { + this.groupInfo(issuer, worldGroup); + } else { + issuer.sendError(MVInvi18n.ERROR_NOGROUP, replace("{group}").with(name)); + } + } + + private void groupInfo(MVCommandIssuer issuer, WorldGroup worldGroup) { + StringBuilder worldsString = new StringBuilder(); + Set worlds = worldGroup.getWorlds(); + if (worlds.isEmpty()) { + worldsString.append("N/A"); + } else { + for (String world : worlds) { + if (!worldsString.toString().isEmpty()) { + worldsString.append(", "); + } + worldsString.append(world); + } + } + issuer.sendInfo(MVInvi18n.INFO_GROUP_INFO, replace("{worlds}").with(worldsString)); + issuer.sendInfo(MVInvi18n.INFO_GROUP_INFOSHARES, replace("{shares}").with(worldGroup.getShares())); + } + + private void worldInfo(MVCommandIssuer issuer, ProfileContainer worldProfileContainer) { + StringBuilder groupsString = new StringBuilder(); + List worldGroups = worldGroupManager.getGroupsForWorld(worldProfileContainer.getContainerName()); + + if (worldGroups.isEmpty()) { + groupsString.append("N/A"); + } else { + for (WorldGroup worldGroup : worldGroups) { + if (!groupsString.toString().isEmpty()) { + groupsString.append(", "); + } + groupsString.append(worldGroup.getName()); + } + } + issuer.sendInfo(MVInvi18n.INFO_WORLD_INFO, replace("{groups}").with(groupsString)); + } + + @Service + private final static class LegacyAlias extends InfoCommand implements LegacyAliasCommand { + @Inject + LegacyAlias(ProfileContainerStoreProvider profileContainerStoreProvider, WorldGroupManager worldGroupManager) { + super(profileContainerStoreProvider, worldGroupManager); + } + + @Override + @CommandAlias("mvinvinfo|mvinvi") + void onInfoCommand(MVCommandIssuer issuer, String name) { + super.onInfoCommand(issuer, name); + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/InventoriesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/InventoriesCommand.java new file mode 100644 index 00000000..34c50c2c --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/InventoriesCommand.java @@ -0,0 +1,15 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.jvnet.hk2.annotations.Contract; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.core.command.MultiverseCommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; + +/** + * Base class for all multiverse inventories commands. + */ +@Contract +@CommandAlias("mvinv") +public abstract class InventoriesCommand extends MultiverseCommand { +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java new file mode 100644 index 00000000..ae5f4444 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ListCommand.java @@ -0,0 +1,66 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.mvplugins.multiverse.core.command.LegacyAliasCommand; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import java.util.Collection; +import java.util.List; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + +@Service +class ListCommand extends InventoriesCommand { + + private final WorldGroupManager worldGroupManager; + + @Inject + ListCommand(@NotNull WorldGroupManager worldGroupManager) { + this.worldGroupManager = worldGroupManager; + } + + @Subcommand("list") + @CommandPermission("multiverse.inventories.list") + @Description("World and Group Information") + void onListCommand(@NotNull MVCommandIssuer issuer) { + Collection groups = worldGroupManager.getGroups(); + String groupsString = "N/A"; + if (!groups.isEmpty()) { + StringBuilder builder = new StringBuilder(); + for (WorldGroup group : groups) { + if (!builder.toString().isEmpty()) { + builder.append(", "); + } + builder.append(group.getName()); + } + groupsString = builder.toString(); + } + issuer.sendInfo(MVInvi18n.LIST_GROUPS); + issuer.sendInfo(MVInvi18n.LIST_GROUPS_INFO, replace("{groups}").with(groupsString)); + } + + @Service + private final static class LegacyAlias extends ListCommand implements LegacyAliasCommand { + @Inject + LegacyAlias(WorldGroupManager worldGroupManager) { + super(worldGroupManager); + } + + @Override + @CommandAlias("mvinvlist|mvinvl") + void onListCommand(MVCommandIssuer issuer) { + super.onListCommand(issuer); + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/MigrateCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/MigrateCommand.java new file mode 100644 index 00000000..62965a1c --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/MigrateCommand.java @@ -0,0 +1,69 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.queue.CommandQueueManager; +import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Single; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.inventories.dataimport.DataImportManager; +import org.mvplugins.multiverse.inventories.dataimport.DataImporter; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + +@Service +final class MigrateCommand extends InventoriesCommand { + + private final DataImportManager dataImportManager; + private final CommandQueueManager commandQueueManager; + + @Inject + MigrateCommand( + @NotNull DataImportManager dataImportManager, + @NotNull CommandQueueManager commandQueueManager + ) { + this.dataImportManager = dataImportManager; + this.commandQueueManager = commandQueueManager; + } + + @Subcommand("migrate") + @Syntax("") + @CommandPermission("multiverse.inventories.migrate") + @CommandCompletion("@dataimporters") + @Description("Import inventories from MultiInv/WorldInventories/PerWorldInventory plugin.") + void onMigrateCommand( + MVCommandIssuer issuer, + + @Single + @Syntax("") + String pluginName) { + + dataImportManager.getImporter(pluginName) + .onEmpty(() -> issuer.sendError(MVInvi18n.MIGRATE_UNSUPPORTEDPLUGIN, replace("{plugin}").with(pluginName))) + .peek(dataImporter -> { + if (!dataImporter.isEnabled()) { + issuer.sendError(MVInvi18n.MIGRATE_PLUGINNOTENABLED, replace("{plugin}").with(pluginName)); + return; + } + commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) + .prompt(Message.of(MVInvi18n.MIGRATE_CONFIRMPROMPT, replace("{plugin}").with(pluginName))) + .action(() -> doDataImport(issuer, dataImporter))); + }); + } + + void doDataImport(MVCommandIssuer issuer, DataImporter dataImporter) { + if (dataImporter.importData()) { + issuer.sendInfo(MVInvi18n.MIGRATE_SUCCESS, replace("{plugin}").with(dataImporter.getPluginName())); + } else { + issuer.sendError(MVInvi18n.MIGRATE_FAILED, replace("{plugin}").with(dataImporter.getPluginName())); + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java new file mode 100644 index 00000000..cbf16d52 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ReloadCommand.java @@ -0,0 +1,47 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.mvplugins.multiverse.core.command.LegacyAliasCommand; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +@Service +class ReloadCommand extends InventoriesCommand { + + private final MultiverseInventories plugin; + + @Inject + ReloadCommand(@NotNull MultiverseInventories plugin) { + this.plugin = plugin; + } + + @Subcommand("reload") + @CommandPermission("multiverse.inventories.reload") + @Description("Reloads config file") + void onReloadCommand(@NotNull MVCommandIssuer issuer) { + this.plugin.reloadConfig(); + issuer.sendInfo(MVInvi18n.RELOAD_COMPLETE); + } + + @Service + private final static class LegacyAlias extends ReloadCommand implements LegacyAliasCommand { + @Inject + LegacyAlias(MultiverseInventories plugin) { + super(plugin); + } + + @Override + @CommandAlias("mvinvreload") + void onReloadCommand(MVCommandIssuer issuer) { + super.onReloadCommand(issuer); + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveDisabledSharesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveDisabledSharesCommand.java new file mode 100644 index 00000000..6ccf8c64 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveDisabledSharesCommand.java @@ -0,0 +1,53 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.share.Shares; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + +@Service +final class RemoveDisabledSharesCommand extends InventoriesCommand { + + private final WorldGroupManager worldGroupManager; + + @Inject + RemoveDisabledSharesCommand(@NotNull WorldGroupManager worldGroupManager) { + this.worldGroupManager = worldGroupManager; + } + + @Subcommand("remove-disabled-shares") + @CommandPermission("multiverse.inventories.removedisabledshares") + @CommandCompletion("@worldGroups @shares") + @Syntax(" ") + @Description("Remove one or more disabled shares from a group.") + void onRemoveSharesCommand( + MVCommandIssuer issuer, + + @Syntax("") + @Description("Group you want to remove the shares from.") + WorldGroup group, + + @Syntax("") + @Description("One or more sharables to remove.") + Shares shares + ) { + group.getDisabledShares().setSharing(shares, false); + worldGroupManager.updateGroup(group); + issuer.sendInfo(MVInvi18n.DISABLEDSHARES_NOWSHARING, + replace("{group}").with(group.getName()), + replace("{shares}").with(group.getDisabledShares().toStringList())); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveSharesCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveSharesCommand.java new file mode 100644 index 00000000..326471ce --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveSharesCommand.java @@ -0,0 +1,56 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + +@Service +final class RemoveSharesCommand extends InventoriesCommand { + private final WorldGroupManager worldGroupManager; + + @Inject + RemoveSharesCommand(@NotNull WorldGroupManager worldGroupManager) { + this.worldGroupManager = worldGroupManager; + } + + @Subcommand("remove-shares") + @CommandPermission("multiverse.inventories.removeshares") + @CommandCompletion("@worldGroups @shares") + @Syntax(" ") + @Description("Remove one or more shares from a group.") + void onRemoveSharesCommand( + MVCommandIssuer issuer, + + @Syntax("") + @Description("Group you want to remove the shares from.") + WorldGroup group, + + @Syntax("") + @Description("One or more sharables to remove.") + Shares shares + ) { + group.getShares().setSharing(shares, false); + worldGroupManager.updateGroup(group); + var negativeshares = Sharables.allOf(); + negativeshares.setSharing(group.getShares(), false); + issuer.sendInfo(MVInvi18n.SHARES_NOWSHARING, + replace("{group}").with(group.getName()), + replace("{shares}").with(group.getShares().toStringList()), + replace("{negativeshares}").with(negativeshares.toStringList())); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveWorldsCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveWorldsCommand.java new file mode 100644 index 00000000..0840f9a1 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/RemoveWorldsCommand.java @@ -0,0 +1,66 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; +import org.mvplugins.multiverse.core.world.MultiverseWorld; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Single; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import java.util.Arrays; +import java.util.List; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + + +@Service +final class RemoveWorldsCommand extends InventoriesCommand { + + private final WorldGroupManager worldGroupManager; + + @Inject + RemoveWorldsCommand(@NotNull WorldGroupManager worldGroupManager) { + this.worldGroupManager = worldGroupManager; + } + + @Subcommand("remove-worlds") + @CommandPermission("multiverse.inventories.removeworlds") + @CommandCompletion("@worldGroups @worldGroupWorlds") + @Syntax(", ") + @Description("Adds a World to a World Group.") + void onRemoveWorldCommand( + MVCommandIssuer issuer, + + @Syntax("") + @Description("Group you want to remove the world from.") + @NotNull WorldGroup group, + + @Single + @Syntax("") + @Description("World name to remove.") + String worldNames + ) { + List worldNamesArr = Arrays.stream(worldNames.split(",")).toList(); + if (!group.removeWorlds(worldNamesArr)) { + issuer.sendError(MVInvi18n.REMOVEWORLD_WORLDNOTINGROUP, + replace("{group}").with(group.getName()), + replace("{world}").with(worldNames)); + return; + } + worldGroupManager.updateGroup(group); + issuer.sendInfo(MVInvi18n.REMOVEWORLD_WORLDREMOVED, + replace("{group}").with(group.getName()), + replace("{world}").with(worldNames)); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java new file mode 100644 index 00000000..a6e93e9e --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/ToggleCommand.java @@ -0,0 +1,75 @@ +package org.mvplugins.multiverse.inventories.commands; + +import org.mvplugins.multiverse.core.command.LegacyAliasCommand; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.share.Shares; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandAlias; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Single; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + +@Service +class ToggleCommand extends InventoriesCommand { + + private final InventoriesConfig inventoriesConfig; + + @Inject + ToggleCommand(@NotNull InventoriesConfig inventoriesConfig) { + this.inventoriesConfig = inventoriesConfig; + } + + @Subcommand("toggle") + @CommandPermission("multiverse.inventories.addshares") + @CommandCompletion("@sharables:scope=optional") + @Syntax("") + @Description("Toggles the usage of optional sharables") + void onToggleCommand( + @NotNull MVCommandIssuer issuer, + + @Single + @Syntax("") + @Description("Share to toggle") + @NotNull Sharable sharable + ) { + Shares optionalShares = inventoriesConfig.getActiveOptionalShares(); + if (!sharable.isOptional()) { + issuer.sendError(MVInvi18n.TOGGLE_NOOPTIONALSHARES, replace("{share}").with(sharable.toString())); + return; + } + if (optionalShares.contains(sharable)) { + optionalShares.remove(sharable); + issuer.sendInfo(MVInvi18n.TOGGLE_NOWNOTUSINGOPTIONAL, replace("{share}").with(sharable.getNames()[0])); + } else { + optionalShares.add(sharable); + issuer.sendInfo(MVInvi18n.TOGGLE_NOWUSINGOPTIONAL, replace("{share}").with(sharable.getNames()[0])); + } + inventoriesConfig.setActiveOptionalShares(optionalShares); + inventoriesConfig.save(); + } + + @Service + private final static class LegacyAlias extends ToggleCommand implements LegacyAliasCommand { + @Inject + LegacyAlias(InventoriesConfig inventoriesConfig) { + super(inventoriesConfig); + } + + @Override + @CommandAlias("mvinvtoggle") + void onToggleCommand(MVCommandIssuer issuer, Sharable sharable) { + super.onToggleCommand(issuer, sharable); + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/BulkEditCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/BulkEditCommand.java new file mode 100644 index 00000000..e071d9ed --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/BulkEditCommand.java @@ -0,0 +1,46 @@ +package org.mvplugins.multiverse.inventories.commands.bulkedit; + +import org.jetbrains.annotations.ApiStatus; +import org.jvnet.hk2.annotations.Contract; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.utils.StringFormatter; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; +import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkEditAction; +import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkEditCreator; +import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkEditResult; + +@Contract +@ApiStatus.Internal +public abstract class BulkEditCommand extends InventoriesCommand { + + protected final BulkEditCreator bulkEditCreator; + + @Inject + protected BulkEditCommand(BulkEditCreator bulkEditCreator) { + this.bulkEditCreator = bulkEditCreator; + } + + protected void outputActionSummary(MVCommandIssuer issuer, BulkEditAction bulkEditAction) { + issuer.sendMessage("Summary of affected profiles:"); + bulkEditAction.getActionSummary().forEach((key, value) -> + issuer.sendMessage(" %s: %s".formatted(key, value.size() > 10 + ? value.size() + : StringFormatter.join(value, ", ")))); + + } + + protected void runBulkEditAction(MVCommandIssuer issuer, BulkEditAction bulkEditAction) { + issuer.sendMessage("Starting bulk edit action..."); + bulkEditAction.execute() + .thenAccept(result -> outputResult(issuer, result)); + } + + protected void outputResult(MVCommandIssuer issuer, BulkEditResult bulkEditResult) { + issuer.sendMessage("Successfully processed %d profiles!".formatted(bulkEditResult.getSuccessCount())); + if (bulkEditResult.getFailureCount() > 0) { + issuer.sendError("Failed to process %d profiles! See log for details.".formatted(bulkEditResult.getFailureCount())); + } + issuer.sendMessage("Bulk edit action completed in %.4f ms.".formatted(bulkEditResult.getTimeTaken())); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ClearCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ClearCommand.java new file mode 100644 index 00000000..7be2c960 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ClearCommand.java @@ -0,0 +1,85 @@ +package org.mvplugins.multiverse.inventories.commands.bulkedit.globalprofile; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.flag.CommandFlag; +import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager; +import org.mvplugins.multiverse.core.command.flag.FlagBuilder; +import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags; +import org.mvplugins.multiverse.core.command.queue.CommandQueueManager; +import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; +import org.mvplugins.multiverse.inventories.commands.bulkedit.BulkEditCommand; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkEditAction; +import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkEditCreator; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +@Service +final class ClearCommand extends BulkEditCommand { + + private final CommandQueueManager commandQueueManager; + private final Flags flags; + + @Inject + ClearCommand( + @NotNull BulkEditCreator bulkEditCreator, + @NotNull CommandQueueManager commandQueueManager, + @NotNull Flags flags + ) { + super(bulkEditCreator); + this.commandQueueManager = commandQueueManager; + this.flags = flags; + } + + @Subcommand("bulkedit globalprofile clear") + @CommandPermission("multiverse.inventories.bulkedit") + @CommandCompletion("@mvinvplayernames @flags:groupName=" + Flags.NAME) + @Syntax(" [--clear-all-player-profiles]") + void onCommand( + MVCommandIssuer issuer, + + @Syntax("") + GlobalProfileKey[] globalProfileKeys, + + @Syntax("[--clear-all-playerprofiles]") + String[] flagArray + ) { + ParsedCommandFlags parsedFlags = flags.parse(flagArray); + + BulkEditAction bulkEditAction = bulkEditCreator.globalProfileClear( + globalProfileKeys, + parsedFlags.hasFlag(flags.clearAllPlayerProfiles) + ); + + outputActionSummary(issuer, bulkEditAction); + + commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) + .prompt(Message.of("Are you sure you want to clear the selected global profiles?")) + .action(() -> runBulkEditAction(issuer, bulkEditAction))); + } + + @Service + private static final class Flags extends FlagBuilder { + private static final String NAME = "mvinvbulkeditglobalprofileclear"; + + @Inject + private Flags(@NotNull CommandFlagsManager flagsManager) { + super(NAME, flagsManager); + } + + private final CommandFlag clearAllPlayerProfiles = flag(CommandFlag.builder("--clear-all-player-profiles") + .addAlias("-a") + .build()); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ModifyCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ModifyCommand.java new file mode 100644 index 00000000..bd419200 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/globalprofile/ModifyCommand.java @@ -0,0 +1,69 @@ +package org.mvplugins.multiverse.inventories.commands.bulkedit.globalprofile; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.queue.CommandQueueManager; +import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload; +import org.mvplugins.multiverse.core.config.handle.PropertyModifyAction; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; + +@Service +final class ModifyCommand extends InventoriesCommand { + + private final CommandQueueManager commandQueueManager; + private final ProfileDataSource profileDataSource; + + @Inject + ModifyCommand(CommandQueueManager commandQueueManager, ProfileDataSource profileDataSource) { + this.commandQueueManager = commandQueueManager; + this.profileDataSource = profileDataSource; + } + + @Subcommand("bulkedit globalprofile modify") + @CommandPermission("multiverse.inventories.bulkedit") + @CommandCompletion("load-on-login|last-world @empty @mvinvplayernames") + @Syntax(" ") + void onCommand( + MVCommandIssuer issuer, + + @Syntax("") + String property, + + @Syntax("") + String value, + + @Syntax("") + GlobalProfileKey[] profileKeys + ) { + commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) + .prompt(Message.of("Are you sure you want to modify %s to %s for %d players?".formatted(property, value, profileKeys.length))) + .action(() -> doModify(issuer, property, value, profileKeys))); + } + + private void doModify(MVCommandIssuer issuer, String property, String value, GlobalProfileKey[] profileKeys) { + AtomicInteger counter = new AtomicInteger(0); + CompletableFuture[] futures = Arrays.stream(profileKeys) + .map(profileKey -> + profileDataSource.modifyGlobalProfile(profileKey, globalProfile -> + globalProfile.getStringPropertyHandle() + .modifyPropertyString(property, value, PropertyModifyAction.SET) + .onSuccess(ignore -> counter.incrementAndGet()) + .onFailure(throwable -> issuer.sendError("Failed to modify %s for %s. %s".formatted(property, profileKey, throwable.getMessage()))))) + .toArray(CompletableFuture[]::new); + + CompletableFuture.allOf(futures) + .thenRun(() -> issuer.sendMessage("Successfully modified %s to %s for %d players.".formatted(property, value, counter.get()))); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ClearCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ClearCommand.java new file mode 100644 index 00000000..ee32445e --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/ClearCommand.java @@ -0,0 +1,68 @@ +package org.mvplugins.multiverse.inventories.commands.bulkedit.playerprofile; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags; +import org.mvplugins.multiverse.core.command.queue.CommandQueueManager; +import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.commands.bulkedit.BulkEditCommand; +import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkEditAction; +import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkEditCreator; +import org.mvplugins.multiverse.inventories.profile.bulkedit.PlayerProfilesPayload; +import org.mvplugins.multiverse.inventories.profile.key.ContainerKey; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; + +@Service +final class ClearCommand extends BulkEditCommand { + + private final CommandQueueManager commandQueueManager; + private final IncludeGroupsWorldsFlag flags; + + @Inject + ClearCommand( + @NotNull BulkEditCreator bulkEditCreator, + @NotNull CommandQueueManager commandQueueManager, + @NotNull IncludeGroupsWorldsFlag flags + ) { + super(bulkEditCreator); + this.commandQueueManager = commandQueueManager; + this.flags = flags; + } + + @Subcommand("bulkedit playerprofile clear") + @CommandPermission("multiverse.inventories.bulkedit") + @CommandCompletion("@mvinvplayernames @empty @mvinvprofiletypes:multiple @flags:groupName=" + IncludeGroupsWorldsFlag.NAME) + @Syntax(" [profile-type] [--include-groups-worlds]") + void onCommand( + MVCommandIssuer issuer, + GlobalProfileKey[] globalProfileKeys, + ContainerKey[] containerKeys, + ProfileType[] profileTypes, + String[] flagArray + ) { + ParsedCommandFlags parsedFlags = flags.parse(flagArray); + + BulkEditAction bulkEditAction = bulkEditCreator.playerProfileClear( + new PlayerProfilesPayload( + globalProfileKeys, + containerKeys, + profileTypes, + parsedFlags.hasFlag(flags.includeGroupsWorlds) + ) + ); + + outputActionSummary(issuer, bulkEditAction); + + commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) + .prompt(Message.of("Are you sure you want to clear the selected profiles?")) + .action(() -> runBulkEditAction(issuer, bulkEditAction))); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java new file mode 100644 index 00000000..7308e7be --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/DeleteCommand.java @@ -0,0 +1,72 @@ +package org.mvplugins.multiverse.inventories.commands.bulkedit.playerprofile; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags; +import org.mvplugins.multiverse.core.command.queue.CommandQueueManager; +import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.core.utils.StringFormatter; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandCompletion; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.commands.bulkedit.BulkEditCommand; +import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkEditAction; +import org.mvplugins.multiverse.inventories.profile.bulkedit.BulkEditCreator; +import org.mvplugins.multiverse.inventories.profile.bulkedit.PlayerProfilesPayload; +import org.mvplugins.multiverse.inventories.profile.key.ContainerKey; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; +import org.mvplugins.multiverse.inventories.share.Sharable; + +@Service +final class DeleteCommand extends BulkEditCommand { + + private final CommandQueueManager commandQueueManager; + private final IncludeGroupsWorldsFlag flags; + + @Inject + DeleteCommand( + @NotNull BulkEditCreator bulkEditCreator, + @NotNull CommandQueueManager commandQueueManager, + @NotNull IncludeGroupsWorldsFlag flags + ) { + super(bulkEditCreator); + this.commandQueueManager = commandQueueManager; + this.flags = flags; + } + + @Subcommand("bulkedit playerprofile delete") + @CommandPermission("multiverse.inventories.bulkedit") + @CommandCompletion("@shares @mvinvplayernames @empty @mvinvprofiletypes:multiple @flags:groupName=" + IncludeGroupsWorldsFlag.NAME) + @Syntax(" [profile-type] [--include-groups-worlds]") + void onCommand( + MVCommandIssuer issuer, + Sharable sharable, + GlobalProfileKey[] globalProfileKeys, + ContainerKey[] containerKeys, + ProfileType[] profileTypes, + String[] flagArray + ) { + ParsedCommandFlags parsedFlags = flags.parse(flagArray); + + BulkEditAction bulkEditAction = bulkEditCreator.playerProfileDeleteSharable( + new PlayerProfilesPayload( + globalProfileKeys, + containerKeys, + profileTypes, + parsedFlags.hasFlag(flags.includeGroupsWorlds) + ), + sharable + ); + + outputActionSummary(issuer, bulkEditAction); + + commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) + .prompt(Message.of("Are you sure you want to delete %s from the selected profiles?".formatted(sharable.getNames()[0]))) + .action(() -> runBulkEditAction(issuer, bulkEditAction))); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/IncludeGroupsWorldsFlag.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/IncludeGroupsWorldsFlag.java new file mode 100644 index 00000000..b344cb48 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/IncludeGroupsWorldsFlag.java @@ -0,0 +1,21 @@ +package org.mvplugins.multiverse.inventories.commands.bulkedit.playerprofile; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.flag.CommandFlag; +import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager; +import org.mvplugins.multiverse.core.command.flag.FlagBuilder; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; + +@Service +final class IncludeGroupsWorldsFlag extends FlagBuilder { + static final String NAME = "mvinvincludegroupsworlds"; + + @Inject + private IncludeGroupsWorldsFlag(CommandFlagsManager flagsManager) { + super(NAME, flagsManager); + } + + final CommandFlag includeGroupsWorlds = flag(CommandFlag.builder("--include-groups-worlds") + .addAlias("-i") + .build()); +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java new file mode 100644 index 00000000..593695ad --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigrateInventorySerializationCommand.java @@ -0,0 +1,100 @@ +package org.mvplugins.multiverse.inventories.commands.bulkedit.playerprofile; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.queue.CommandQueueManager; +import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.GlobalProfile; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicLong; + +@Service +final class MigrateInventorySerializationCommand extends InventoriesCommand { + + private final CommandQueueManager commandQueueManager; + private final ProfileDataSource profileDataSource; + private final InventoriesConfig inventoriesConfig; + + @Inject + MigrateInventorySerializationCommand( + @NotNull CommandQueueManager commandQueueManager, + @NotNull ProfileDataSource profileDataSource, + @NotNull InventoriesConfig inventoriesConfig + ) { + this.commandQueueManager = commandQueueManager; + this.profileDataSource = profileDataSource; + this.inventoriesConfig = inventoriesConfig; + } + + @Subcommand("bulkedit migrate inventory-serialization nbt") + @CommandPermission("multiverse.inventories.bulkedit") + void onNbtCommand(MVCommandIssuer issuer) { + commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) + .prompt(Message.of("Are you sure you want to migrate all player data to NBT?")) + .action(() -> doMigration(issuer, true))); + } + + @Subcommand("bulkedit migrate inventory-serialization bukkit") + @CommandPermission("multiverse.inventories.bulkedit") + void onBukkitCommand(MVCommandIssuer issuer) { + commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) + .prompt(Message.of("Are you sure you want to migrate all player data to old Bukkit serialization?")) + .action(() -> doMigration(issuer, false))); + } + + private void doMigration(MVCommandIssuer issuer, boolean useByteSerialization) { + inventoriesConfig.setUseByteSerializationForInventoryData(useByteSerialization); + inventoriesConfig.save(); + + long startTime = System.nanoTime(); + AtomicLong profileCounter = new AtomicLong(0); + CompletableFuture.allOf(profileDataSource.listGlobalProfileUUIDs() + .stream() + .map(playerUUID -> profileDataSource.getGlobalProfile(GlobalProfileKey.of(playerUUID, "")) + .thenCompose(profile -> run(profile, profileCounter)) + .exceptionally(throwable -> { + issuer.sendMessage("Error updating player " + playerUUID + ": " + throwable.getMessage()); + return null; + })) + .toArray(CompletableFuture[]::new)) + .thenRun(() -> { + long timeDuration = (System.nanoTime() - startTime) / 1000000; + issuer.sendMessage("Updated " + profileCounter.get() + " player profiles."); + issuer.sendMessage("Bulk edit completed in " + timeDuration + " ms."); + }); + } + + private CompletableFuture run(GlobalProfile profile, AtomicLong profileCounter) { + return CompletableFuture.allOf(Arrays.stream(ContainerType.values()) + .flatMap(containerType -> profileDataSource.listContainerDataNames(containerType).stream() + .flatMap(dataName -> ProfileTypes.getTypes().stream() + .map(profileType -> profileDataSource.getPlayerProfile(ProfileKey.of( + containerType, + dataName, + profileType, + profile.getPlayerUUID(), + profile.getLastKnownName() + )).thenCompose(playerProfile -> { + if (playerProfile.getData().isEmpty()) { + return CompletableFuture.completedFuture(null); + } + profileCounter.incrementAndGet(); + return profileDataSource.updatePlayerProfile(playerProfile); + })))) + .toArray(CompletableFuture[]::new)); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigratePlayerNameCommand.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigratePlayerNameCommand.java new file mode 100644 index 00000000..e4aec8ad --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/bulkedit/playerprofile/MigratePlayerNameCommand.java @@ -0,0 +1,47 @@ +package org.mvplugins.multiverse.inventories.commands.bulkedit.playerprofile; + +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.queue.CommandQueueManager; +import org.mvplugins.multiverse.core.command.queue.CommandQueuePayload; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.external.acf.commands.annotation.CommandPermission; +import org.mvplugins.multiverse.external.acf.commands.annotation.Description; +import org.mvplugins.multiverse.external.acf.commands.annotation.Subcommand; +import org.mvplugins.multiverse.external.acf.commands.annotation.Syntax; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.external.vavr.control.Try; +import org.mvplugins.multiverse.inventories.commands.InventoriesCommand; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; + +final class MigratePlayerNameCommand extends InventoriesCommand { + + private final CommandQueueManager commandQueueManager; + private final ProfileDataSource profileDataSource; + + MigratePlayerNameCommand( + @NotNull CommandQueueManager commandQueueManager, + @NotNull ProfileDataSource profileDataSource) { + this.commandQueueManager = commandQueueManager; + this.profileDataSource = profileDataSource; + } + + @Subcommand("bulkedit migrate player-name") + @CommandPermission("multiverse.inventories.bulkedit") + @Syntax(" ") + @Description("Only use this if automatic migration failed for some reason.") + void onCommand( + MVCommandIssuer issuer, + String oldName, + String newName + ) { + commandQueueManager.addToQueue(CommandQueuePayload.issuer(issuer) + .prompt(Message.of("Are you sure you want to migrate all player data for %s to %s? This action cannot be undone." + .formatted(oldName, newName))) + .action(() -> doMigration(issuer, oldName, newName))); + } + + private void doMigration(MVCommandIssuer issuer, String oldName, String newName) { + Try.run(() -> profileDataSource.migratePlayerProfileName(oldName, newName)) + .onFailure(e -> issuer.sendMessage("Failed to migrate player data for " + oldName + ". " + e.getMessage())); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/package-info.java new file mode 100644 index 00000000..513b441b --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/package-info.java @@ -0,0 +1,5 @@ +/** + * This package contains all Commands. + */ +package org.mvplugins.multiverse.inventories.commands; + diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupControlPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupControlPrompt.java new file mode 100644 index 00000000..b3c44fdf --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupControlPrompt.java @@ -0,0 +1,37 @@ +package org.mvplugins.multiverse.inventories.commands.prompts; + +import org.bukkit.ChatColor; +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.bukkit.conversations.ConversationContext; +import org.bukkit.conversations.Prompt; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +public final class GroupControlPrompt extends InventoriesPrompt { + + public GroupControlPrompt(final MultiverseInventories plugin, final MVCommandIssuer issuer) { + super(plugin, issuer); + } + + @NotNull + @Override + Message getPromptMessage(@NotNull final ConversationContext conversationContext) { + return Message.of(MVInvi18n.GROUP_COMMANDPROMPT); + } + + @Override + public Prompt acceptInput(@NotNull final ConversationContext conversationContext, final String input) { + if (input.equalsIgnoreCase("delete")) { + return new GroupDeletePrompt(plugin, issuer); + } else if (input.equalsIgnoreCase("create")) { + return new GroupCreatePrompt(plugin, issuer); + } else if (input.equalsIgnoreCase("edit")) { + return new GroupEditPrompt(plugin, issuer); + } else { + issuer.sendError(MVInvi18n.GROUP_INVALIDOPTION); + return this; + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupCreatePrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupCreatePrompt.java new file mode 100644 index 00000000..e2cf9306 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupCreatePrompt.java @@ -0,0 +1,42 @@ +package org.mvplugins.multiverse.inventories.commands.prompts; + +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.bukkit.conversations.ConversationContext; +import org.bukkit.conversations.Prompt; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + +final class GroupCreatePrompt extends InventoriesPrompt { + + public GroupCreatePrompt(final MultiverseInventories plugin, final MVCommandIssuer issuer) { + super(plugin, issuer); + } + + @NotNull + @Override + Message getPromptMessage(@NotNull final ConversationContext conversationContext) { + return Message.of(MVInvi18n.GROUP_CREATEPROMPT); + } + + @Override + public Prompt acceptInput(@NotNull final ConversationContext conversationContext, final String s) { + final WorldGroup group = worldGroupManager.getGroup(s); + if (group == null) { + if (s.isEmpty() || !s.matches("^[a-zA-Z0-9][a-zA-Z0-9_]*$")) { + issuer.sendError(MVInvi18n.GROUP_INVALIDNAME); + return this; + } + final WorldGroup newGroup = worldGroupManager.newEmptyGroup(s); + return new GroupWorldsPrompt(plugin, issuer, newGroup, + new GroupSharesPrompt(plugin, issuer, newGroup, Prompt.END_OF_CONVERSATION, true), true); + } else { + issuer.sendError(MVInvi18n.GROUP_EXISTS, replace("{group}").with(s)); + } + return Prompt.END_OF_CONVERSATION; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupDeletePrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupDeletePrompt.java new file mode 100644 index 00000000..a21022ba --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupDeletePrompt.java @@ -0,0 +1,47 @@ +package org.mvplugins.multiverse.inventories.commands.prompts; + +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.bukkit.ChatColor; +import org.bukkit.conversations.ConversationContext; +import org.bukkit.conversations.Prompt; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + +final class GroupDeletePrompt extends InventoriesPrompt { + + public GroupDeletePrompt(final MultiverseInventories plugin, final MVCommandIssuer issuer) { + super(plugin, issuer); + } + + @NotNull + @Override + Message getPromptMessage(@NotNull final ConversationContext conversationContext) { + final StringBuilder builder = new StringBuilder(); + for (WorldGroup group : worldGroupManager.getGroups()) { + if (builder.isEmpty()) { + builder.append(ChatColor.WHITE); + } else { + builder.append(ChatColor.GOLD).append(", ").append(ChatColor.WHITE); + } + builder.append(group.getName()); + } + return Message.of(MVInvi18n.GROUP_DELETEPROMPT, replace("{groups}").with(builder.toString())); + } + + @Override + public Prompt acceptInput(@NotNull final ConversationContext conversationContext, final String input) { + final WorldGroup group = worldGroupManager.getGroup(input); + if (group == null) { + issuer.sendError(MVInvi18n.ERROR_NOGROUP, replace("{group}").with(input)); + } else { + worldGroupManager.removeGroup(group); + issuer.sendInfo(MVInvi18n.GROUP_REMOVED, replace("{group}").with(input)); + } + return Prompt.END_OF_CONVERSATION; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupEditPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupEditPrompt.java new file mode 100644 index 00000000..b700f608 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupEditPrompt.java @@ -0,0 +1,46 @@ +package org.mvplugins.multiverse.inventories.commands.prompts; + +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.bukkit.ChatColor; +import org.bukkit.conversations.ConversationContext; +import org.bukkit.conversations.Prompt; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + +final class GroupEditPrompt extends InventoriesPrompt { + + public GroupEditPrompt(final MultiverseInventories plugin, final MVCommandIssuer issuer) { + super(plugin, issuer); + } + + @NotNull + @Override + public Message getPromptMessage(@NotNull final ConversationContext conversationContext) { + final StringBuilder builder = new StringBuilder(); + for (WorldGroup group : worldGroupManager.getGroups()) { + if (builder.isEmpty()) { + builder.append(ChatColor.WHITE); + } else { + builder.append(ChatColor.GOLD).append(", ").append(ChatColor.WHITE); + } + builder.append(group.getName()); + } + return Message.of(MVInvi18n.GROUP_EDITPROMPT, replace("{groups}").with(builder.toString())); + } + + @Override + public Prompt acceptInput(@NotNull final ConversationContext conversationContext, final String input) { + final WorldGroup group = worldGroupManager.getGroup(input); + if (group == null) { + issuer.sendError(MVInvi18n.ERROR_NOGROUP, replace("{group}").with(input)); + } else { + return new GroupModifyPrompt(plugin, issuer, group); + } + return Prompt.END_OF_CONVERSATION; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupModifyPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupModifyPrompt.java new file mode 100644 index 00000000..f60ec7f8 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupModifyPrompt.java @@ -0,0 +1,41 @@ +package org.mvplugins.multiverse.inventories.commands.prompts; + +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.bukkit.conversations.ConversationContext; +import org.bukkit.conversations.Prompt; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + +final class GroupModifyPrompt extends InventoriesPrompt { + + protected final WorldGroup group; + + public GroupModifyPrompt(final MultiverseInventories plugin, final MVCommandIssuer issuer, + final WorldGroup group) { + super(plugin, issuer); + this.group = group; + } + + @NotNull + @Override + public Message getPromptMessage(@NotNull final ConversationContext conversationContext) { + return Message.of(MVInvi18n.GROUP_MODIFYPROMPT, replace("{group}").with(group.getName())); + } + + @Override + public Prompt acceptInput(@NotNull final ConversationContext conversationContext, final String input) { + if ("worlds".equalsIgnoreCase(input)) { + return new GroupWorldsPrompt(plugin, issuer, group, this, false); + } else if (input.equalsIgnoreCase("shares")) { + return new GroupSharesPrompt(plugin, issuer, group, this, false); + } else { + issuer.sendError(MVInvi18n.GROUP_INVALIDOPTION); + return this; + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupSharesPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupSharesPrompt.java new file mode 100644 index 00000000..408778ec --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupSharesPrompt.java @@ -0,0 +1,88 @@ +package org.mvplugins.multiverse.inventories.commands.prompts; + +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; +import org.bukkit.ChatColor; +import org.bukkit.conversations.ConversationContext; +import org.bukkit.conversations.Prompt; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + +final class GroupSharesPrompt extends InventoriesPrompt { + + protected final WorldGroup group; + protected final Prompt nextPrompt; + protected final boolean isCreating; + protected final Shares shares; + + public GroupSharesPrompt(final MultiverseInventories plugin, final MVCommandIssuer issuer, + final WorldGroup group, final Prompt nextPrompt, + final boolean creatingGroup) { + super(plugin, issuer); + this.group = group; + this.nextPrompt = nextPrompt; + this.isCreating = creatingGroup; + this.shares = Sharables.fromShares(group.getShares()); + } + + @NotNull + @Override + public Message getPromptMessage(@NotNull final ConversationContext conversationContext) { + final StringBuilder builder = new StringBuilder(); + for (final Sharable sharable : shares) { + if (builder.isEmpty()) { + builder.append(ChatColor.WHITE); + } else { + builder.append(ChatColor.GOLD).append(", ").append(ChatColor.WHITE); + } + builder.append(sharable.toString()); + } + return Message.of(MVInvi18n.GROUP_SHARESPROMPT, + replace("{group}").with(group.getName()), + replace("{shares}").with(builder.toString())); + } + + @Override + public Prompt acceptInput(@NotNull final ConversationContext conversationContext, final String input) { + if ("@".equals(input)) { + group.getShares().clear(); + group.getShares().addAll(this.shares); + worldGroupManager.updateGroup(group); + if (isCreating) { + issuer.sendInfo(MVInvi18n.GROUP_CREATIONCOMPLETE, replace("{group}").with(group.getName())); + } else { + issuer.sendInfo(MVInvi18n.GROUP_UPDATED); + } + issuer.sendInfo(MVInvi18n.INFO_GROUP, replace("{group}").with(group.getName())); + issuer.sendInfo(MVInvi18n.INFO_GROUP_INFO, replace("{worlds}").with(group.getWorlds())); + issuer.sendInfo(MVInvi18n.INFO_GROUP_INFOSHARES, replace("{shares}").with(group.getShares())); + worldGroupManager.checkForConflicts(issuer); + return nextPrompt; + } + + boolean negative = false; + Shares shares = Sharables.lookup(input.toLowerCase()); + if (shares == null && input.startsWith("-") && input.length() > 1) { + negative = true; + shares = Sharables.lookup(input.toLowerCase().substring(1)); + } + + if (shares == null) { + issuer.sendError(MVInvi18n.ERROR_NOSHARESSPECIFIED); + return this; + } + if (negative) { + this.shares.removeAll(shares); + return this; + } + this.shares.addAll(shares); + return this; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupWorldsPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupWorldsPrompt.java new file mode 100644 index 00000000..0ce588c0 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/GroupWorldsPrompt.java @@ -0,0 +1,97 @@ +package org.mvplugins.multiverse.inventories.commands.prompts; + +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.World; +import org.bukkit.conversations.ConversationContext; +import org.bukkit.conversations.Prompt; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; + +import java.util.HashSet; +import java.util.Set; + +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + +final class GroupWorldsPrompt extends InventoriesPrompt { + + protected final WorldGroup group; + protected final Prompt nextPrompt; + protected final boolean isCreating; + protected final Set worlds; + + public GroupWorldsPrompt(final MultiverseInventories plugin, final MVCommandIssuer issuer, + final WorldGroup group, final Prompt nextPrompt, + final boolean creatingGroup) { + super(plugin, issuer); + this.group = group; + this.nextPrompt = nextPrompt; + this.isCreating = creatingGroup; + this.worlds = new HashSet(group.getWorlds()); + } + + @NotNull + @Override + public Message getPromptMessage(@NotNull final ConversationContext conversationContext) { + final StringBuilder builder = new StringBuilder(); + for (final String world : worlds) { + if (builder.length() == 0) { + builder.append(ChatColor.WHITE); + } else { + builder.append(ChatColor.GOLD).append(", ").append(ChatColor.WHITE); + } + builder.append(world); + } + return Message.of(MVInvi18n.GROUP_WORLDSPROMPT, + replace("{group}").with(group.getName()), + replace("{worlds}").with(builder.toString())); + } + + @Override + public Prompt acceptInput(@NotNull final ConversationContext conversationContext, final String input) { + if (input.equals("@")) { + if (worlds.isEmpty()) { + issuer.sendInfo(MVInvi18n.GROUP_WORLDSEMPTY); + return this; + } + group.removeAllWorlds(false); + group.addWorlds(worlds, false); + if (!isCreating) { + worldGroupManager.updateGroup(group); + issuer.sendInfo(MVInvi18n.GROUP_UPDATED); + issuer.sendInfo(MVInvi18n.INFO_GROUP, replace("{group}").with(group.getName())); + issuer.sendInfo(MVInvi18n.INFO_GROUP_INFO, replace("{worlds}").with(group.getWorlds())); + issuer.sendInfo(MVInvi18n.INFO_GROUP_INFOSHARES, replace("{shares}").with(group.getShares())); + } + return nextPrompt; + } + + boolean negative = false; + World world = Bukkit.getWorld(input); + if (world == null && input.startsWith("-") && input.length() > 1) { + negative = true; + world = Bukkit.getWorld(input.substring(1)); + } + + if (world == null) { + issuer.sendError(MVInvi18n.ERROR_NOWORLD, replace("{world}").with(input)); + return this; + } + if (negative) { + if (!worlds.contains(world.getName())) { + issuer.sendError(MVInvi18n.REMOVEWORLD_WORLDNOTINGROUP, + replace("{world}").with(input), + replace("{group}").with(group.getName())); + return this; + } + worlds.remove(world.getName()); + return this; + } + worlds.add(world.getName()); + return this; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/InventoriesPrompt.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/InventoriesPrompt.java new file mode 100644 index 00000000..1a85eb3f --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/InventoriesPrompt.java @@ -0,0 +1,40 @@ +package org.mvplugins.multiverse.inventories.commands.prompts; + +import org.bukkit.ChatColor; +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.core.locale.PluginLocales; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.bukkit.conversations.ConversationContext; +import org.bukkit.conversations.Prompt; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; + +abstract class InventoriesPrompt implements Prompt { + + protected final MultiverseInventories plugin; + protected final PluginLocales locales; + protected final WorldGroupManager worldGroupManager; + protected final MVCommandIssuer issuer; + + InventoriesPrompt(final MultiverseInventories plugin, final MVCommandIssuer issuer) { + this.plugin = plugin; + this.locales = plugin.getServiceLocator().getService(MVCommandManager.class).getLocales(); + this.issuer = issuer; + this.worldGroupManager = this.plugin.getServiceLocator().getService(WorldGroupManager.class); + } + + abstract Message getPromptMessage(@NotNull ConversationContext conversationContext); + + @NotNull + @Override + public String getPromptText(@NotNull ConversationContext context) { + return ChatColor.translateAlternateColorCodes('&', getPromptMessage(context).formatted(locales, issuer)); + } + + @Override + public boolean blocksForInput(@NotNull final ConversationContext conversationContext) { + return true; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/package-info.java new file mode 100644 index 00000000..474860af --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/commands/prompts/package-info.java @@ -0,0 +1 @@ +package org.mvplugins.multiverse.inventories.commands.prompts; \ No newline at end of file diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java new file mode 100644 index 00000000..7694c636 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java @@ -0,0 +1,283 @@ +package org.mvplugins.multiverse.inventories.config; + +import com.dumptruckman.minecraft.util.Logging; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.config.handle.CommentedConfigurationHandle; +import org.mvplugins.multiverse.core.config.handle.StringPropertyHandle; +import org.mvplugins.multiverse.core.config.migration.ConfigMigrator; +import org.mvplugins.multiverse.core.config.migration.action.MoveMigratorAction; +import org.mvplugins.multiverse.core.config.migration.VersionMigrator; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.vavr.control.Try; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.share.Shares; +import org.bukkit.configuration.file.FileConfiguration; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +/** + * Provides methods for interacting with the configuration of Multiverse-Inventories. + */ +@Service +public final class InventoriesConfig { + + public static final String CONFIG_FILENAME = "config.yml"; + + private final InventoriesConfigNodes configNodes; + private final CommentedConfigurationHandle configHandle; + private final StringPropertyHandle stringPropertyHandle; + + @Inject + InventoriesConfig(MultiverseInventories inventories) throws IOException { + this.configNodes = new InventoriesConfigNodes(); + var configPath = Path.of(inventories.getDataFolder().getPath(), CONFIG_FILENAME); + this.configHandle = CommentedConfigurationHandle.builder(configPath, this.configNodes.getNodes()) + .logger(Logging.getLogger()) + .migrator(ConfigMigrator.builder(this.configNodes.version) + .addVersionMigrator(VersionMigrator.builder(5.0) + .addAction(MoveMigratorAction.of("settings.first_run", "first-run")) + .addAction(MoveMigratorAction.of("settings.use_bypass", "share-handling.enable-bypass-permissions")) + .addAction(MoveMigratorAction.of("settings.default_ungrouped_worlds", "share-handling.default-ungrouped-worlds")) + .addAction(MoveMigratorAction.of("settings.save_load_on_log_in_out", "performance.apply-playerdata-on-join")) + .addAction(MoveMigratorAction.of("settings.use_game_mode_profiles", "share-handling.enable-gamemode-share-handling")) + .addAction(MoveMigratorAction.of("shares.optionals_for_ungrouped_worlds", "share-handling.use-optionals-for-ungrouped-worlds")) + .addAction(MoveMigratorAction.of("shares.use_optionals", "share-handling.active-optional-shares")) + .build()) + .build()) + .build(); + this.stringPropertyHandle = new StringPropertyHandle(this.configHandle); + } + + public Try load() { + return this.configHandle.load(); + } + + public FileConfiguration getConfig() { + return this.configHandle.getConfig(); + } + + public StringPropertyHandle getStringPropertyHandle() { + return stringPropertyHandle; + } + + /** + * @return True if we should check for bypass permissions. + */ + public boolean getEnableBypassPermissions() { + return this.configHandle.get(configNodes.enableBypassPermissions); + } + + /** + * @param useBypass Whether or not to check for bypass permissions. + */ + public Try setEnableBypassPermissions(boolean useBypass) { + return this.configHandle.set(configNodes.enableBypassPermissions, useBypass); + } + + /** + * @return True if using separate data for game modes. + */ + public boolean getEnableGamemodeShareHandling() { + return this.configHandle.get(configNodes.enableGamemodeShareHandling); + } + + /** + * @param useGameModeProfile whether to use separate data for game modes. + */ + public Try setEnableGamemodeShareHandling(boolean useGameModeProfile) { + return this.configHandle.set(configNodes.enableGamemodeShareHandling, useGameModeProfile); + } + + /** + * @return true if worlds with no group should be considered part of the default group. + */ + public boolean getDefaultUngroupedWorlds() { + return this.configHandle.get(configNodes.defaultUngroupedWorlds); + } + + /** + * @param useDefaultGroup Set this to true to use the default group for ungrouped worlds. + */ + public Try setDefaultUngroupedWorlds(boolean useDefaultGroup) { + return this.configHandle.set(configNodes.defaultUngroupedWorlds, useDefaultGroup); + } + + /** + * Whether Multiverse-Inventories will utilize optional shares in worlds that are not grouped. + * + * @return true if should utilize optional shares in worlds that are not grouped. + */ + public boolean getUseOptionalsForUngroupedWorlds() { + return this.configHandle.get(configNodes.useOptionalsForUngroupedWorlds); + } + + /** + * Sets whether Multiverse-Inventories will utilize optional shares in worlds that are not grouped. + * + * @param usingOptionalsForUngrouped true if should utilize optional shares in worlds that are not grouped. + */ + public Try setUseOptionalsForUngroupedWorlds(final boolean usingOptionalsForUngrouped) { + return this.configHandle.set(configNodes.useOptionalsForUngroupedWorlds, usingOptionalsForUngrouped); + } + + /** + * @return A list of optional {@link Sharable}s to be treated as + * regular {@link Sharable}s throughout the code. + * A {@link Sharable} marked as optional is ignored if it is not + * contained in this list. + */ + public Shares getActiveOptionalShares() { + return this.configHandle.get(configNodes.activeOptionalShares); + } + + /** + * Sets the optional shares to be used. + * + * @param shares The optional shares to be used. + * @return True if successful. + */ + public Try setActiveOptionalShares(Shares shares) { + return this.configHandle.set(configNodes.activeOptionalShares, shares); + } + + public boolean getUseImprovedRespawnLocationDetection() { + return this.configHandle.get(configNodes.useImprovedRespawnLocationDetection); + } + + public Try setUseImprovedRespawnLocationDetection(boolean useImprovedRespawnLocationDetection) { + return this.configHandle.set(configNodes.useImprovedRespawnLocationDetection, useImprovedRespawnLocationDetection); + } + + public boolean getResetLastLocationOnDeath() { + return this.configHandle.get(configNodes.resetLastLocationOnDeath); + } + + public Try setResetLastLocationOnDeath(boolean resetLastLocationOnDeath) { + return this.configHandle.set(configNodes.resetLastLocationOnDeath, resetLastLocationOnDeath); + } + + public boolean getApplyLastLocationForAllTeleports() { + return this.configHandle.get(configNodes.applyLastLocationForAllTeleports); + } + + public Try setApplyLastLocationForAllTeleports(boolean applyLastLocationForAllTeleports) { + return this.configHandle.set(configNodes.applyLastLocationForAllTeleports, applyLastLocationForAllTeleports); + } + + public boolean getUseByteSerializationForInventoryData() { + return this.configHandle.get(configNodes.useByteSerializationForInventoryData); + } + + public Try setUseByteSerializationForInventoryData(boolean useByteSerializationForInventoryData) { + return this.configHandle.set(configNodes.useByteSerializationForInventoryData, useByteSerializationForInventoryData); + } + + public boolean getApplyPlayerdataOnJoin() { + return this.configHandle.get(configNodes.applyPlayerdataOnJoin); + } + + public Try setApplyPlayerdataOnJoin(boolean applyPlayerdataOnJoin) { + return this.configHandle.set(configNodes.applyPlayerdataOnJoin, applyPlayerdataOnJoin); + } + + public boolean getAlwaysWriteWorldProfile() { + return this.configHandle.get(configNodes.alwaysWriteWorldProfile); + } + + public Try setAlwaysWriteWorldProfile(boolean alwaysWriteWorldProfile) { + return this.configHandle.set(configNodes.alwaysWriteWorldProfile, alwaysWriteWorldProfile); + } + + public List getPreloadDataOnJoinWorlds() { + return this.configHandle.get(configNodes.preloadDataOnJoinWorlds); + } + + public Try setPreloadDataOnJoinWorlds(List preloadDataOnJoinWorlds) { + return this.configHandle.set(configNodes.preloadDataOnJoinWorlds, preloadDataOnJoinWorlds); + } + + public List getPreloadDataOnJoinGroups() { + return this.configHandle.get(configNodes.preloadDataOnJoinGroups); + } + + public Try setPreloadDataOnJoinGroups(List preloadDataOnJoinGroups) { + return this.configHandle.set(configNodes.preloadDataOnJoinGroups, preloadDataOnJoinGroups); + } + + public int getPlayerFileCacheSize() { + return this.configHandle.get(configNodes.playerFileCacheSize); + } + + public Try setPlayerFileCacheSize(int playerFileCacheSize) { + return this.configHandle.set(configNodes.playerFileCacheSize, playerFileCacheSize); + } + + public int getPlayerFileCacheExpiry() { + return this.configHandle.get(configNodes.playerFileCacheExpiry); + } + + public Try setPlayerFileCacheExpiry(int playerFileCacheExpiry) { + return this.configHandle.set(configNodes.playerFileCacheExpiry, playerFileCacheExpiry); + } + + public int getPlayerProfileCacheSize() { + return this.configHandle.get(configNodes.playerProfileCacheSize); + } + + public Try setPlayerProfileCacheSize(int playerProfileCacheSize) { + return this.configHandle.set(configNodes.playerProfileCacheSize, playerProfileCacheSize); + } + + public int getPlayerProfileCacheExpiry() { + return this.configHandle.get(configNodes.playerProfileCacheExpiry); + } + + public Try setPlayerProfileCacheExpiry(int playerProfileCacheExpiry) { + return this.configHandle.set(configNodes.playerProfileCacheExpiry, playerProfileCacheExpiry); + } + + public int getGlobalProfileCacheSize() { + return this.configHandle.get(configNodes.globalProfileCacheSize); + } + + public Try setGlobalProfileCacheSize(int globalProfileCacheSize) { + return this.configHandle.set(configNodes.globalProfileCacheSize, globalProfileCacheSize); + } + + public int getGlobalProfileCacheExpiry() { + return this.configHandle.get(configNodes.globalProfileCacheExpiry); + } + + public Try setGlobalProfileCacheExpiry(int globalProfileCacheExpiry) { + return this.configHandle.set(configNodes.globalProfileCacheExpiry, globalProfileCacheExpiry); + } + + /** + * Tells whether this is the first time the plugin has run as set by a config flag. + * + * @return True if first_run is set to true in config. + */ + public boolean getFirstRun() { + return this.configHandle.get(configNodes.firstRun); + } + + /** + * Sets the first_run flag in the config so that the plugin no longer thinks it is the first run. + * + * @param firstRun What to set the flag to in the config. + */ + public Try setFirstRun(boolean firstRun) { + return this.configHandle.set(configNodes.firstRun, firstRun); + } + + /** + * Saves the configuration file to disk. + */ + public Try save() { + return this.configHandle.save(); + } +} + diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java new file mode 100644 index 00000000..bbf6af36 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java @@ -0,0 +1,239 @@ +package org.mvplugins.multiverse.inventories.config; + +import org.mvplugins.multiverse.core.config.node.ConfigHeaderNode; +import org.mvplugins.multiverse.core.config.node.ConfigNode; +import org.mvplugins.multiverse.core.config.node.ListConfigNode; +import org.mvplugins.multiverse.core.config.node.Node; +import org.mvplugins.multiverse.core.config.node.NodeGroup; +import org.mvplugins.multiverse.core.config.node.serializer.NodeSerializer; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +final class InventoriesConfigNodes { + + private final NodeGroup nodes = new NodeGroup(); + + InventoriesConfigNodes() { + } + + NodeGroup getNodes() { + return nodes; + } + + private N node(N node) { + nodes.add(node); + return node; + } + + private final ConfigHeaderNode shareHandlingHeader = node(ConfigHeaderNode.builder("share-handling") + .comment("#-----------------------------------------------------------------------------------------------------------------#") + .comment("# #") + .comment("# __ __ _ _ _____ _ _____ _____ ___ ___ ___ ___ _ ___ _____ _ _ _____ ___ ___ ___ ___ ___ #") + .comment("# | \\/ | | | |_ _| | |_ _\\ \\ / / __| _ \\/ __| __| |_ _| \\| \\ \\ / / __| \\| |_ _/ _ \\| _ \\_ _| __/ __| #") + .comment("# | |\\/| | |_| | | | | |__ | | \\ V /| _|| /\\__ \\ _| | || .` |\\ V /| _|| .` | | || (_) | /| || _|\\__ \\ #") + .comment("# |_| |_|\\___/ |_| |____|___| \\_/ |___|_|_\\|___/___| |___|_|\\_| \\_/ |___|_|\\_| |_| \\___/|_|_\\___|___|___/ #") + .comment("# #") + .comment("# #") + .comment("# #") + .comment("# #") + .comment("# WIKI: https://github.com/Multiverse/Multiverse-Core/wiki/Basics-(Inventories) #") + .comment("# DISCORD: https://discord.gg/NZtfKky #") + .comment("# BUG REPORTS: https://github.com/Multiverse/Multiverse-Inventories/issues #") + .comment("# #") + .comment("# #") + .comment("# New options are added to this file automatically. If you manually made changes #") + .comment("# to this file while your server is running, please run `/mvinv reload` command. #") + .comment("# #") + .comment("#-----------------------------------------------------------------------------------------------------------------#") + .comment("") + .comment("") + .build()); + + final ConfigNode enableBypassPermissions = node(ConfigNode.builder("share-handling.enable-bypass-permissions", Boolean.class) + .comment("If this is set to true, it will enable bypass permissions (Check the wiki for more info.)") + .defaultValue(false) + .name("enable-bypass") + .build()); + + final ConfigNode enableGamemodeShareHandling = node(ConfigNode.builder("share-handling.enable-gamemode-share-handling", Boolean.class) + .comment("") + .comment("If this is set to true, players will have different inventories/stats for each game mode.") + .comment("Please note that old data migrated to the version that has this feature will have their data copied for both game modes.") + .defaultValue(false) + .name("enable-gamemode-share-handling") + .build()); + + final ConfigNode defaultUngroupedWorlds = node(ConfigNode.builder("share-handling.default-ungrouped-worlds", Boolean.class) + .comment("") + .comment("If set to true, any world not listed in a group will automatically be assigned to the 'default' group!") + .defaultValue(false) + .name("default-ungrouped-worlds") + .build()); + + final ConfigNode useOptionalsForUngroupedWorlds = node(ConfigNode.builder("share-handling.use-optionals-for-ungrouped-worlds", Boolean.class) + .comment("") + .comment("When set to true, optional shares WILL be utilized in cases where a group does not cover their uses for a world.") + .comment("An example of this in action would be an ungrouped world using last_location. When this is true, players will return to their last location in that world.") + .comment("When set to false, optional shares WILL NOT be utilized in these cases, effectively disabling it for ungrouped worlds.") + .defaultValue(true) + .name("use-optionals-for-ungrouped-worlds") + .build()); + + final ConfigNode activeOptionalShares = node(ConfigNode.builder("share-handling.active-optional-shares", Shares.class) + .comment("") + .comment("You must specify optional shares you wish to use here or they will be ignored.") + .comment("Built-in optional shares are: \"economy\" and \"last_location\".") + .defaultValue(Sharables.noneOf()) + .hidden() + .serializer(new NodeSerializer<>() { + @Override + public Shares deserialize(Object o, Class aClass) { + if (o instanceof List) { + return Sharables.fromList((List) o); + } + return Sharables.fromList(List.of(Objects.toString(o))); + } + + @Override + public Object serialize(Shares sharables, Class aClass) { + return sharables.toStringList(); + } + }) + .onSetValue((oldValue, newValue) -> Sharables.recalculateEnabledShares()) + .build()); + + private final ConfigHeaderNode sharablesHeader = node(ConfigHeaderNode.builder("sharables") + .comment("") + .comment("") + .build()); + + final ConfigNode useImprovedRespawnLocationDetection = node(ConfigNode.builder("sharables.use-improved-respawn-location-detection", Boolean.class) + .comment("When enabled, we will use 1.21's PlayerSpawnChangeEvent to better detect bed and anchor respawn locations.") + .comment("This options is not applicable for older minecraft server versions.") + .defaultValue(true) + .name("use-improved-respawn-location-detection") + .build()); + + final ConfigNode resetLastLocationOnDeath = node(ConfigNode.builder("sharables.reset-last-location-on-death", Boolean.class) + .comment("") + .comment("When set to true, the last location of the player will be reset when they die.") + .comment("This is useful if they respawn in a different world and you do not want them to return to their death location.") + .defaultValue(false) + .name("reset-last-location-on-death") + .build()); + + final ConfigNode applyLastLocationForAllTeleports = node(ConfigNode.builder("sharables.apply-last-location-for-all-teleports", Boolean.class) + .comment("") + .comment("When enabled, the last location of the player will be applied for any teleportation.") + .comment("This is useful as you want to use the last location for any teleportation, such as the warp system.") + .comment("When disabled, you can only use `/mv tp ll:worldname` to teleport to the player's last location.") + .defaultValue(true) + .name("apply-last-location-for-all-teleports") + .build()); + + final ConfigNode useByteSerializationForInventoryData = node(ConfigNode.builder("sharables.use-byte-serialization-for-inventory-data", Boolean.class) + .comment("") + .comment("When enabled, we will use paper's improved byte serialization for inventory data.") + .comment("When disabled, we will use the legacy configuration serialization method.") + .comment("!!!!!BIG NOTE:") + .comment(" This option is only applicable on PAPERMC.") + .comment(" Once you enable this option, you cannot change your server software back to SPIGOT.") + .comment("------------") + .comment("Byte serialization will use minecraft's NBT format. NBT is safer for data migrations as it will use the built in ") + .comment("data converter instead of bukkits dangerous serialization system. This will fix various issues with the inventory data") + .comment("such as Skulker Box data loss, equip-sound crash, FoodEffect error, and more.") + .defaultValue(false) + .name("use-byte-serialization-for-inventory-data") + .build()); + + private final ConfigHeaderNode performanceHeader = node(ConfigHeaderNode.builder("performance") + .comment("") + .comment("") + .build()); + + final ConfigNode applyPlayerdataOnJoin = node(ConfigNode.builder("performance.apply-playerdata-on-join", Boolean.class) + .comment("") + .comment("This will only work if save-playerdata-on-quit is set to true.") + .comment("Minecraft will already load the most up-to-date player data and this option will generally be redundant.") + .comment("The only possible edge case uses is if you have a need to always modify the mvinv playerdata while the player is offline.") + .defaultValue(false) + .name("apply-playerdata-on-join") + .build()); + + final ConfigNode alwaysWriteWorldProfile = node(ConfigNode.builder("performance.always-write-world-profile", Boolean.class) + .comment("") + .comment("By default, even when the group shares all or going to a world within the same group, the world profile will still be written to disk.") + .comment("This will ensure that the world profile is always up-to-date, so when removing the world from the group, it will not be missing data.") + .comment("However, if you are certain that your world will always be in a group, you can set this to false to slightly improve performance.") + .defaultValue(true) + .name("always-write-world-profile") + .build()); + + private final ConfigHeaderNode preloadHeader = node(ConfigHeaderNode.builder("performance.preload-data-on-join") + .comment("") + .comment("Pre-loads player data into caches when joining the server.") + .comment("This will reduce the load time on first teleport to the world/group, with the cost of increased memory usage and join time.") + .build()); + + final ListConfigNode preloadDataOnJoinWorlds = node(ListConfigNode.listBuilder("performance.preload-data-on-join.worlds", String.class) + .defaultValue(ArrayList::new) + .name("preload-data-on-join-worlds") + .build()); + + final ListConfigNode preloadDataOnJoinGroups = node(ListConfigNode.listBuilder("performance.preload-data-on-join.groups", String.class) + .defaultValue(ArrayList::new) + .name("preload-data-on-join-groups") + .build()); + + private final ConfigHeaderNode cacheHeader = node(ConfigHeaderNode.builder("performance.cache") + .comment("") + .comment("NOTE: Cache options require a server restart to take effect.") + .build()); + + final ConfigNode playerFileCacheSize = node(ConfigNode.builder("performance.cache.player-file-cache-size", Integer.class) + .defaultValue(2000) + .name("player-file-cache-size") + .build()); + + final ConfigNode playerFileCacheExpiry = node(ConfigNode.builder("performance.cache.player-file-cache-expiry", Integer.class) + .defaultValue(60) + .name("player-file-cache-expiry") + .build()); + + final ConfigNode playerProfileCacheSize = node(ConfigNode.builder("performance.cache.player-profile-cache-size", Integer.class) + .defaultValue(6000) + .name("player-profile-cache-size") + .build()); + + final ConfigNode playerProfileCacheExpiry = node(ConfigNode.builder("performance.cache.player-profile-cache-expiry", Integer.class) + .defaultValue(60) + .name("player-profile-cache-expiry") + .build()); + + final ConfigNode globalProfileCacheSize = node(ConfigNode.builder("performance.cache.global-profile-cache-size", Integer.class) + .defaultValue(500) + .name("global-profile-cache-size") + .build()); + + final ConfigNode globalProfileCacheExpiry = node(ConfigNode.builder("performance.cache.global-profile-cache-expiry", Integer.class) + .defaultValue(60) + .name("global-profile-cache-expiry") + .build()); + + final ConfigNode firstRun = node(ConfigNode.builder("first-run", Boolean.class) + .comment("") + .comment("") + .comment("Do not edit the following values!!!!!") + .defaultValue(true) + .hidden() + .build()); + + final ConfigNode version = node(ConfigNode.builder("version", Double.class) + .defaultValue(0.0) + .hidden() + .build()); +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/handle/JsonConfigurationHandle.java b/src/main/java/org/mvplugins/multiverse/inventories/config/handle/JsonConfigurationHandle.java new file mode 100644 index 00000000..3b6133eb --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/handle/JsonConfigurationHandle.java @@ -0,0 +1,73 @@ +package org.mvplugins.multiverse.inventories.config.handle; + +import com.dumptruckman.bukkit.configuration.json.JsonConfiguration; +import org.bukkit.configuration.InvalidConfigurationException; +import org.mvplugins.multiverse.core.config.handle.FileConfigurationHandle; +import org.mvplugins.multiverse.core.config.migration.ConfigMigrator; +import org.mvplugins.multiverse.core.config.node.NodeGroup; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.external.jetbrains.annotations.Nullable; +import org.mvplugins.multiverse.external.vavr.control.Try; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.logging.Logger; + +public class JsonConfigurationHandle extends FileConfigurationHandle { + + /** + * Creates a new builder for {@link JsonConfigurationHandle}. + * + * @param configPath The path to the config file. + * @param nodes The nodes. + * @return The builder. + */ + public static @NotNull Builder builder(@NotNull Path configPath, @NotNull NodeGroup nodes) { + return new Builder<>(configPath, nodes); + } + + protected JsonConfigurationHandle( + @NotNull Path configPath, + @Nullable Logger logger, + @NotNull NodeGroup nodes, + @Nullable ConfigMigrator migrator + ) { + super(configPath, logger, nodes, migrator); + } + + @Override + protected void loadConfigObject() throws IOException, InvalidConfigurationException { + config = new JsonConfiguration(); + config.load(configFile); + } + + /** + * {@inheritDoc} + */ + @Override + public Try save() { + return Try.run(() -> config = new JsonConfiguration()) + .flatMap(ignore -> super.save()) + .andThenTry(ignore -> config.save(configFile)); + } + + /** + * Builder for {@link JsonConfigurationHandle}. + * + * @param The type of the builder. + */ + public static class Builder> extends FileConfigurationHandle.Builder { + + protected Builder(@NotNull Path configPath, @NotNull NodeGroup nodes) { + super(configPath, nodes); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull JsonConfigurationHandle build() { + return new JsonConfigurationHandle(configPath, logger, nodes, migrator); + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/config/package-info.java new file mode 100644 index 00000000..e8e69681 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/package-info.java @@ -0,0 +1 @@ +package org.mvplugins.multiverse.inventories.config; \ No newline at end of file diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/AbstractDataImporter.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/AbstractDataImporter.java new file mode 100644 index 00000000..a399506d --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/AbstractDataImporter.java @@ -0,0 +1,124 @@ +package org.mvplugins.multiverse.inventories.dataimport; + +import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.jvnet.hk2.annotations.Contract; + +/** + * Abstract implementation of {@link DataImporter} without actual import logic. + */ +@Contract +public abstract class AbstractDataImporter implements DataImporter { + + protected Plugin importer = null; + + public AbstractDataImporter() { + } + + /** + * Logic that does the actual importing data. + * + * @throws DataImportException Errors occurred that caused import to fail. + */ + protected abstract void doDataImport() throws DataImportException; + + /** + * {@inheritDoc} + */ + @Override + public boolean importData(boolean disableOnSuccess) { + if (!isEnabled()) { + Logging.severe("Data importer %s not enabled. No data is imported.", this.getPluginName()); + return false; + } + + try { + doDataImport(); + } catch (DataImportException e) { + Logging.severe(e.getMessage()); + if(e.getCauseException() != null) { + Logging.severe("Cause: %s", e.getCauseException().getMessage()); + } + e.printStackTrace(); + return false; + } + + Logging.info("Successfully imported data from %s!", this.getPluginName()); + if (disableOnSuccess) { + Logging.info("Disabling %s...", this.getPluginName()); + Bukkit.getPluginManager().disablePlugin(this.importer); + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean importData() { + return importData(true); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean enable(Plugin importerPlugin) { + if (isEnabled()) { + return false; + } + if (!importerPlugin.getClass().equals(this.getPluginClass())) { + Logging.warning("Plugin '%s' is not data importer for '%s'.", + importerPlugin.getClass().getName(), getPluginName()); + return false; + } + try { + this.importer = importerPlugin; + } catch (ClassCastException | NoClassDefFoundError e) { + Logging.warning("Error while enabling data importer for '%s'.", getPluginName()); + return false; + } + Logging.info("Successfully enabled data importer for '%s'.", getPluginName()); + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean enable() { + Plugin importerPlugin = Bukkit.getPluginManager().getPlugin(this.getPluginName()); + if (importerPlugin == null) { + Logging.finer("Unable to get plugin '%s' for import hook.", this.getPluginName()); + return false; + } + return enable(importerPlugin); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean disable() { + this.importer = null; + Logging.info("Successfully disabled data importer for '%s'.", getPluginName()); + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isEnabled() { + return importer != null; + } + + /** + * {@inheritDoc} + */ + @Override + public Plugin getPlugin() { + return importer; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/DataImportException.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/DataImportException.java new file mode 100644 index 00000000..13cc563b --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/DataImportException.java @@ -0,0 +1,56 @@ +package org.mvplugins.multiverse.inventories.dataimport; + +import org.mvplugins.multiverse.core.exceptions.MultiverseException; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.external.jetbrains.annotations.Nullable; + +/** + * Exception thrown when migration doesn't go well. + */ +public class DataImportException extends MultiverseException { + + private Exception causeException = null; + + public DataImportException(@Nullable String message) { + super(message); + } + + public DataImportException(@Nullable String message, Exception causeException) { + super(message); + this.causeException = causeException; + } + + public DataImportException(@Nullable Message message, Exception causeException) { + super(message); + this.causeException = causeException; + } + + public DataImportException(@Nullable String message, @Nullable Throwable cause, Exception causeException) { + super(message, cause); + this.causeException = causeException; + } + + public DataImportException(@Nullable Message message, @Nullable Throwable cause, Exception causeException) { + super(message, cause); + this.causeException = causeException; + } + + /** + * Sets what the causing exception was, if any. + * + * @param exception The cause exception. + * @return This exception for easy chainability. + */ + public DataImportException setCauseException(Exception exception) { + this.causeException = exception; + return this; + } + + /** + * @return The causing exception or null if none. + */ + public Exception getCauseException() { + return this.causeException; + } +} + diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/DataImportManager.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/DataImportManager.java new file mode 100644 index 00000000..4925b847 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/DataImportManager.java @@ -0,0 +1,107 @@ +package org.mvplugins.multiverse.inventories.dataimport; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.server.PluginDisableEvent; +import org.bukkit.event.server.PluginEnableEvent; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginManager; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.vavr.control.Option; +import org.mvplugins.multiverse.inventories.MultiverseInventories; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Manager class for importing data from other inventory plugins or similar, e.g. PerWorldInventory. + */ +@Service +public final class DataImportManager implements Listener { + + final private Map dataImporters; + + @Inject + DataImportManager(@NotNull MultiverseInventories inventories, @NotNull PluginManager pluginManager) { + this.dataImporters = new HashMap<>(); + pluginManager.registerEvents(this, inventories); + } + + /** + * Register a Data Importer and optionally try to enable to it as well. + * + * @param dataImporter The Data Importer to register. + * @param tryEnable Whether to try and {@link DataImporter#enable(Plugin)} the Data Importer. + */ + public void register(DataImporter dataImporter, boolean tryEnable) { + this.dataImporters.put(dataImporter.getPluginName().toLowerCase(), dataImporter); + if (tryEnable) { + dataImporter.enable(); + } + } + + /** + * Register a Data Importer and try to enable to it as well. + * + * @param dataImporter The Data Importer to register. + */ + public void register(DataImporter dataImporter) { + this.register(dataImporter, true); + } + + /** + * Gets a {@link DataImporter} based on an importable plugin name. + * + * @param pluginName The plugin name you want to import data from. + * @return The {@link DataImporter} if Data Importer present for that plugin, else null. + */ + public Option getImporter(String pluginName) { + return Option.of(this.dataImporters.get(pluginName.toLowerCase())); + } + + /** + * Gets a {@link DataImporter} based on an importable {@link Plugin}. + * + * @param plugin The plugin you want to import data from. + * @return The {@link DataImporter} if Data Importer present for that plugin, else null. + */ + public Option getImporter(Plugin plugin) { + return getImporter(plugin.getName()); + } + + /** + * Gets all the Data Importer names that are enabled. + * + * @return A collection of Data Importer names that are enabled. + */ + public Collection getEnabledImporterNames() { + return this.dataImporters.values().stream() + .filter(DataImporter::isEnabled) + .map(DataImporter::getPluginName) + .collect(Collectors.toList()); + } + + /** + * Called when a plugin is enabled. + * + * @param event The plugin enable event. + */ + @EventHandler + private void pluginEnable(PluginEnableEvent event) { + getImporter(event.getPlugin()).peek(dataImporter -> dataImporter.enable(event.getPlugin())); + } + + /** + * Called when a plugin is disabled. + * + * @param event The plugin disable event. + */ + @EventHandler + private void pluginDisable(PluginDisableEvent event) { + getImporter(event.getPlugin()).peek(DataImporter::disable); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/DataImporter.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/DataImporter.java new file mode 100644 index 00000000..40d540f7 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/DataImporter.java @@ -0,0 +1,73 @@ +package org.mvplugins.multiverse.inventories.dataimport; + +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jvnet.hk2.annotations.Contract; + +/** + * Interface for data migration importers. + */ +@Contract +public interface DataImporter { + + /** + * Imports the data from another plugin and optionally disable it after successful import. + * + * @param disableOnSuccess Whether to disable the importer plugin after a successful import. + * @return True if data import is successful, else false. + */ + boolean importData(boolean disableOnSuccess); + + /** + * Imports the data from another plugin and disabled it upon success so Multiverse inventories + * can work without conflicts. + * + * @return True if data import is successful, else false. + */ + boolean importData(); + + /** + * Hooks plugin for importing its data. Needs plugin class of {@link #getPluginClass()}. + * + * @param plugin The target plugin instance to hook. + * @return True if successfully enabled, else false. + */ + boolean enable(Plugin plugin); + + /** + * Hooks plugin for importing its data. Needs plugin class of {@link #getPluginClass()}. + * + * @return True if successfully enabled, else false. + */ + boolean enable(); + + /** + * Unhook plugin from this Data Importer. + * + * @return True if successfully disabled, else false. + */ + boolean disable(); + + /** + * Checks if this Data Importer has been {@link #enable(Plugin)} successfully. + * + * @return True if is enabled, else false. + */ + boolean isEnabled(); + + /** + * @return The plugin associated with this Data Importer, null if not enabled. + */ + @Nullable Plugin getPlugin(); + + /** + * @return The plugin name associated with this Data Importer. + */ + @NotNull String getPluginName(); + + /** + * @return The plugin class associated with this Data Importer. + */ + @NotNull Class getPluginClass(); +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIInventoryConverter.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIInventoryConverter.java similarity index 81% rename from src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIInventoryConverter.java rename to src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIInventoryConverter.java index 33f1d5e2..2cc77354 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIInventoryConverter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIInventoryConverter.java @@ -1,13 +1,13 @@ -package com.onarandombox.multiverseinventories.migration.multiinv; +package org.mvplugins.multiverse.inventories.dataimport.multiinv; -import com.onarandombox.multiverseinventories.util.MinecraftTools; +import org.mvplugins.multiverse.inventories.util.MinecraftTools; import org.bukkit.inventory.ItemStack; import uk.co.tggl.pluckerpluck.multiinv.inventory.MIItemStack; /** * Utility class for converting proprietary shit from MultiInv. */ -public class MIInventoryConverter { +final class MIInventoryConverter { /** * @param oldContents Proprietary shiet from MultiInv. diff --git a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIInventoryInterface.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIInventoryInterface.java similarity index 67% rename from src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIInventoryInterface.java rename to src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIInventoryInterface.java index 04f46b18..980e649b 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIInventoryInterface.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIInventoryInterface.java @@ -1,11 +1,11 @@ -package com.onarandombox.multiverseinventories.migration.multiinv; +package org.mvplugins.multiverse.inventories.dataimport.multiinv; import org.bukkit.inventory.ItemStack; /** * A little interface for retrieving normal ItemStack from the MultiInv inventory classes. */ -public interface MIInventoryInterface { +sealed interface MIInventoryInterface permits MIInventoryWrapper, MIInventoryOldWrapper { /** * @return The inventory contents. diff --git a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIInventoryOldWrapper.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIInventoryOldWrapper.java similarity index 78% rename from src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIInventoryOldWrapper.java rename to src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIInventoryOldWrapper.java index d624a566..8f5d90d1 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIInventoryOldWrapper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIInventoryOldWrapper.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories.migration.multiinv; +package org.mvplugins.multiverse.inventories.dataimport.multiinv; import org.bukkit.inventory.ItemStack; import uk.co.tggl.pluckerpluck.multiinv.inventory.MIInventoryOld; @@ -6,7 +6,7 @@ /** * Wraps MIInventoryOld to provide a way of accessing the inventory/armor contents. */ -public class MIInventoryOldWrapper extends MIInventoryOld implements MIInventoryInterface { +final class MIInventoryOldWrapper extends MIInventoryOld implements MIInventoryInterface { public MIInventoryOldWrapper(String inventoryString) { super(inventoryString); diff --git a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIInventoryWrapper.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIInventoryWrapper.java similarity index 79% rename from src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIInventoryWrapper.java rename to src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIInventoryWrapper.java index 5572b3b3..b333eece 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIInventoryWrapper.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIInventoryWrapper.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories.migration.multiinv; +package org.mvplugins.multiverse.inventories.dataimport.multiinv; import org.bukkit.inventory.ItemStack; import uk.co.tggl.pluckerpluck.multiinv.inventory.MIInventory; @@ -6,7 +6,7 @@ /** * Wraps MIInventory to provide a way of accessing the inventory/armor contents. */ -public class MIInventoryWrapper extends MIInventory implements MIInventoryInterface { +final class MIInventoryWrapper extends MIInventory implements MIInventoryInterface { public MIInventoryWrapper(String inventoryString) { super(inventoryString); diff --git a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIPlayerFileLoader.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIPlayerFileLoader.java similarity index 90% rename from src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIPlayerFileLoader.java rename to src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIPlayerFileLoader.java index ba0e6e02..97dc42de 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MIPlayerFileLoader.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MIPlayerFileLoader.java @@ -1,114 +1,114 @@ -package com.onarandombox.multiverseinventories.migration.multiinv; - -import com.onarandombox.multiverseinventories.PlayerStats; -import org.bukkit.OfflinePlayer; -import org.bukkit.configuration.file.YamlConfiguration; -import uk.co.tggl.pluckerpluck.multiinv.MultiInv; - -import java.io.File; - -/** - * A replacement for MultiInv's MIPlayerFile class so that it may accept an OfflinePlayer instead of Player. - */ -public class MIPlayerFileLoader { - - private YamlConfiguration playerFile; - private File file; - - public MIPlayerFileLoader(MultiInv plugin, OfflinePlayer player, String group) { - // Find and load configuration file for the player - File worldsFolder = new File(plugin.getDataFolder(), "Groups"); - file = new File(worldsFolder, group + File.separator + player.getName() + ".yml"); - - playerFile = new YamlConfiguration(); - } - - /** - * Loads the player file into memory. - * - * @return True if there was a file to load and it loaded successfully. - */ - public boolean load() { - if (file.exists()) { - try { - playerFile.load(file); - return true; - } catch (Exception ignore) { } - } - return false; - } - - /** - * Load particular inventory for specified player from specified group. - * - * @param inventoryName The gamemode for the inventory to load. - * @return An interface for retrieve the inventory/armor contents. - */ - public MIInventoryInterface getInventory(String inventoryName) { - // Get stored string from configuration file - MIInventoryInterface inventory; - String inventoryString = playerFile.getString(inventoryName, null); - // Check for old inventory save - if (inventoryString == null || inventoryString.contains(";-;")) { - inventory = new MIInventoryOldWrapper(inventoryString); - } else { - inventory = new MIInventoryWrapper(inventoryString); - } - return inventory; - } - - /** - * @return The player's health. - */ - public double getHealth() { - double health = playerFile.getDouble("health", PlayerStats.HEALTH); - if (health <= 0 || health > PlayerStats.HEALTH) { - health = PlayerStats.HEALTH; - } - return health; - } - - /** - * @return The player's hunger. - */ - public int getHunger() { - int hunger = playerFile.getInt("hunger", PlayerStats.FOOD_LEVEL); - if (hunger <= 0 || hunger > PlayerStats.FOOD_LEVEL) { - hunger = PlayerStats.FOOD_LEVEL; - } - return hunger; - } - - /** - * @return The player's saturation. - */ - public float getSaturation() { - double saturationDouble = playerFile.getDouble("saturation", 0); - float saturation = (float) saturationDouble; - return saturation; - } - - /** - * @return The player's total exp. - */ - public int getTotalExperience() { - return playerFile.getInt("experience", 0); - } - - /** - * @return The player's level. - */ - public int getLevel() { - return playerFile.getInt("level", 0); - } - - /** - * @return The player's exp. - */ - public float getExperience() { - double expDouble = playerFile.getDouble("exp", 0); - float exp = (float) expDouble; - return exp; - } -} - +package org.mvplugins.multiverse.inventories.dataimport.multiinv; + +import org.mvplugins.multiverse.inventories.util.PlayerStats; +import org.bukkit.OfflinePlayer; +import org.bukkit.configuration.file.YamlConfiguration; +import uk.co.tggl.pluckerpluck.multiinv.MultiInv; + +import java.io.File; + +/** + * A replacement for MultiInv's MIPlayerFile class so that it may accept an OfflinePlayer instead of Player. + */ +final class MIPlayerFileLoader { + + private final YamlConfiguration playerFile; + private final File file; + + public MIPlayerFileLoader(MultiInv plugin, OfflinePlayer player, String group) { + // Find and load configuration file for the player + File worldsFolder = new File(plugin.getDataFolder(), "Groups"); + file = new File(worldsFolder, group + File.separator + player.getName() + ".yml"); + + playerFile = new YamlConfiguration(); + } + + /** + * Loads the player file into memory. + * + * @return True if there was a file to load and it loaded successfully. + */ + public boolean load() { + if (file.exists()) { + try { + playerFile.load(file); + return true; + } catch (Exception ignore) { } + } + return false; + } + + /** + * Load particular inventory for specified player from specified group. + * + * @param inventoryName The gamemode for the inventory to load. + * @return An interface for retrieve the inventory/armor contents. + */ + public MIInventoryInterface getInventory(String inventoryName) { + // Get stored string from configuration file + MIInventoryInterface inventory; + String inventoryString = playerFile.getString(inventoryName, null); + // Check for old inventory save + if (inventoryString == null || inventoryString.contains(";-;")) { + inventory = new MIInventoryOldWrapper(inventoryString); + } else { + inventory = new MIInventoryWrapper(inventoryString); + } + return inventory; + } + + /** + * @return The player's health. + */ + public double getHealth() { + double health = playerFile.getDouble("health", PlayerStats.HEALTH); + if (health <= 0 || health > PlayerStats.HEALTH) { + health = PlayerStats.HEALTH; + } + return health; + } + + /** + * @return The player's hunger. + */ + public int getHunger() { + int hunger = playerFile.getInt("hunger", PlayerStats.FOOD_LEVEL); + if (hunger <= 0 || hunger > PlayerStats.FOOD_LEVEL) { + hunger = PlayerStats.FOOD_LEVEL; + } + return hunger; + } + + /** + * @return The player's saturation. + */ + public float getSaturation() { + double saturationDouble = playerFile.getDouble("saturation", 0); + float saturation = (float) saturationDouble; + return saturation; + } + + /** + * @return The player's total exp. + */ + public int getTotalExperience() { + return playerFile.getInt("experience", 0); + } + + /** + * @return The player's level. + */ + public int getLevel() { + return playerFile.getInt("level", 0); + } + + /** + * @return The player's exp. + */ + public float getExperience() { + double expDouble = playerFile.getDouble("exp", 0); + float exp = (float) expDouble; + return exp; + } +} + diff --git a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MultiInvImporter.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImportHelper.java similarity index 53% rename from src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MultiInvImporter.java rename to src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImportHelper.java index c0f3ae8c..9c4baf63 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/MultiInvImporter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImportHelper.java @@ -1,167 +1,149 @@ -package com.onarandombox.multiverseinventories.migration.multiinv; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.profile.ProfileTypes; -import com.onarandombox.multiverseinventories.profile.container.ContainerType; -import com.onarandombox.multiverseinventories.profile.PlayerProfile; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.migration.DataImporter; -import com.onarandombox.multiverseinventories.migration.MigrationException; -import org.bukkit.Bukkit; -import org.bukkit.GameMode; -import org.bukkit.OfflinePlayer; -import org.bukkit.World; -import org.bukkit.plugin.Plugin; -import uk.co.tggl.pluckerpluck.multiinv.MIYamlFiles; -import uk.co.tggl.pluckerpluck.multiinv.MultiInv; - -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.Map; - -/** - * A class to help with importing data from MultiInv. - */ -public class MultiInvImporter implements DataImporter { - - private MultiInv miPlugin; - private MultiverseInventories inventories; - - public MultiInvImporter(MultiverseInventories inventories, MultiInv miPlugin) { - this.inventories = inventories; - this.miPlugin = miPlugin; - } - - /** - * @return The MultiInv plugin hooked to the importer. - */ - public MultiInv getMIPlugin() { - return this.miPlugin; - } - - /** - * {@inheritDoc} - */ - @Override - public Plugin getPlugin() { - return this.getMIPlugin(); - } - - /** - * Imports the data from MultiInv. - * - * @throws MigrationException If there was any MAJOR issue loading the data. - */ - @Override - public void importData() throws MigrationException { - HashMap miGroupMap = this.getGroupMap(); - if (miGroupMap == null) { - throw new MigrationException("There is no data to import from MultiInv!"); - } - if (!miGroupMap.isEmpty()) { - WorldGroup defaultWorldGroup = this.inventories.getGroupManager().getDefaultGroup(); - if (defaultWorldGroup != null) { - this.inventories.getGroupManager().removeGroup(defaultWorldGroup); - Logging.info("Removed automatically created world group in favor of imported groups."); - } - } - for (Map.Entry groupEntry : miGroupMap.entrySet()) { - WorldGroup worldGroup = this.inventories.getGroupManager().getGroup(groupEntry.getValue()); - if (worldGroup == null) { - worldGroup = this.inventories.getGroupManager().newEmptyGroup(groupEntry.getValue()); - worldGroup.getShares().mergeShares(Sharables.allOf()); - Logging.info("Importing group: " + groupEntry.getValue()); - this.inventories.getGroupManager().updateGroup(worldGroup); - } - worldGroup.addWorld(groupEntry.getValue()); - } - this.inventories.getMVIConfig().save(); - - for (OfflinePlayer player : Bukkit.getServer().getOfflinePlayers()) { - Logging.info("Processing MultiInv data for player: " + player.getName()); - for (Map.Entry entry : miGroupMap.entrySet()) { - String worldName = entry.getKey(); - String groupName = entry.getValue(); - MIPlayerFileLoader playerFileLoader = - new MIPlayerFileLoader(this.getMIPlugin(), player, groupName); - if (!playerFileLoader.load()) { - continue; - } - Logging.info("Processing MultiInv data for player: " + player.getName() - + " for group: " + groupName); - mergeData(player, playerFileLoader, groupName, ContainerType.GROUP); - } - for (World world : Bukkit.getWorlds()) { - String worldName = world.getName(); - MIPlayerFileLoader playerFileLoader = - new MIPlayerFileLoader(this.getMIPlugin(), player, worldName); - if (!playerFileLoader.load()) { - continue; - } - Logging.info("Processing MultiInv data for player: " + player.getName() - + " for world only: " + worldName); - mergeData(player, playerFileLoader, worldName, ContainerType.WORLD); - } - } - - Logging.info("Import from MultiInv finished. Disabling MultiInv."); - Bukkit.getPluginManager().disablePlugin(this.getMIPlugin()); - } - - private void mergeData(OfflinePlayer player, MIPlayerFileLoader playerFileLoader, - String dataName, ContainerType type) { - PlayerProfile playerProfile; - if (type.equals(ContainerType.GROUP)) { - WorldGroup group = this.inventories.getGroupManager() - .getGroup(dataName); - if (group == null) { - Logging.warning("Could not import player data for group: " + dataName); - return; - } - playerProfile = group.getGroupProfileContainer().getPlayerData(ProfileTypes.SURVIVAL, player); - } else { - playerProfile = this.inventories.getWorldProfileContainerStore() - .getContainer(dataName).getPlayerData(ProfileTypes.SURVIVAL, player); - } - MIInventoryInterface inventoryInterface = - playerFileLoader.getInventory(GameMode.SURVIVAL.toString()); - playerProfile.set(Sharables.INVENTORY, inventoryInterface.getInventoryContents()); - playerProfile.set(Sharables.ARMOR, inventoryInterface.getArmorContents()); - playerProfile.set(Sharables.HEALTH, playerFileLoader.getHealth()); - playerProfile.set(Sharables.SATURATION, playerFileLoader.getSaturation()); - playerProfile.set(Sharables.EXPERIENCE, playerFileLoader.getExperience()); - playerProfile.set(Sharables.TOTAL_EXPERIENCE, playerFileLoader.getTotalExperience()); - playerProfile.set(Sharables.LEVEL, playerFileLoader.getLevel()); - playerProfile.set(Sharables.FOOD_LEVEL, playerFileLoader.getHunger()); - this.inventories.getData().updatePlayerData(playerProfile); - } - - /** - * @return The group mapping from MultiInv, where worldName -> groupName. - * @throws MigrationException If there was any issues getting the data through reflection. - */ - private HashMap getGroupMap() throws MigrationException { - Field field; - try { - field = MIYamlFiles.class.getDeclaredField("groups"); - } catch (NoSuchFieldException nsfe) { - throw new MigrationException("The running version of MultiInv is " - + "incompatible with the import feature.").setCauseException(nsfe); - } - field.setAccessible(true); - HashMap miGroupMap = null; - try { - miGroupMap = (HashMap) field.get(null); - } catch (IllegalAccessException iae) { - throw new MigrationException("The running version of MultiInv is " - + "incompatible with the import feature.").setCauseException(iae); - } catch (ClassCastException cce) { - throw new MigrationException("The running version of MultiInv is " - + "incompatible with the import feature.").setCauseException(cce); - } - return miGroupMap; - } -} - +package org.mvplugins.multiverse.inventories.dataimport.multiinv; + +import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.OfflinePlayer; +import org.bukkit.World; +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.dataimport.DataImportException; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.share.Sharables; +import uk.co.tggl.pluckerpluck.multiinv.MIYamlFiles; +import uk.co.tggl.pluckerpluck.multiinv.MultiInv; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +final class MultiInvImportHelper { + + @NotNull + private final MultiInv multiInv; + private final WorldGroupManager worldGroupManager; + private final InventoriesConfig inventoriesConfig; + private final ProfileContainerStoreProvider profileContainerStoreProvider; + private final ProfileDataSource profileDataSource; + + MultiInvImportHelper( + @NotNull MultiInv multiInv, + @NotNull WorldGroupManager worldGroupManager, + @NotNull InventoriesConfig inventoriesConfig, + @NotNull ProfileContainerStoreProvider profileContainerStoreProvider, + @NotNull ProfileDataSource profileDataSource) { + super(); + this.multiInv = multiInv; + this.worldGroupManager = worldGroupManager; + this.inventoriesConfig = inventoriesConfig; + this.profileContainerStoreProvider = profileContainerStoreProvider; + this.profileDataSource = profileDataSource; + } + + void importData() throws DataImportException { + HashMap miGroupMap = this.getGroupMap(); + if (miGroupMap == null) { + throw new DataImportException("There is no data to import from MultiInv!"); + } + if (!miGroupMap.isEmpty()) { + WorldGroup defaultWorldGroup = worldGroupManager.getDefaultGroup(); + if (defaultWorldGroup != null) { + worldGroupManager.removeGroup(defaultWorldGroup); + Logging.info("Removed automatically created world group in favor of imported groups."); + } + } + for (Map.Entry groupEntry : miGroupMap.entrySet()) { + WorldGroup worldGroup = worldGroupManager.getGroup(groupEntry.getValue()); + if (worldGroup == null) { + worldGroup = worldGroupManager.newEmptyGroup(groupEntry.getValue()); + worldGroup.getShares().mergeShares(Sharables.allOf()); + Logging.info("Importing group: " + groupEntry.getValue()); + worldGroupManager.updateGroup(worldGroup); + } + worldGroup.addWorld(groupEntry.getValue()); + } + inventoriesConfig.save(); + + for (OfflinePlayer player : Bukkit.getServer().getOfflinePlayers()) { + Logging.info("Processing MultiInv data for player: " + player.getName()); + for (Map.Entry entry : miGroupMap.entrySet()) { + String worldName = entry.getKey(); + String groupName = entry.getValue(); + MIPlayerFileLoader playerFileLoader = new MIPlayerFileLoader(multiInv, player, groupName); + if (!playerFileLoader.load()) { + continue; + } + Logging.info("Processing MultiInv data for player: " + player.getName() + + " for group: " + groupName); + mergeData(player, playerFileLoader, groupName, ContainerType.GROUP); + } + for (World world : Bukkit.getWorlds()) { + String worldName = world.getName(); + MIPlayerFileLoader playerFileLoader = new MIPlayerFileLoader(multiInv, player, worldName); + if (!playerFileLoader.load()) { + continue; + } + Logging.info("Processing MultiInv data for player: " + player.getName() + + " for world only: " + worldName); + mergeData(player, playerFileLoader, worldName, ContainerType.WORLD); + } + } + } + + private void mergeData(OfflinePlayer player, MIPlayerFileLoader playerFileLoader, + String dataName, ContainerType type) { + PlayerProfile playerProfile; + if (type.equals(ContainerType.GROUP)) { + WorldGroup group = worldGroupManager + .getGroup(dataName); + if (group == null) { + Logging.warning("Could not import player data for group: " + dataName); + return; + } + playerProfile = group.getGroupProfileContainer().getPlayerProfileNow(ProfileTypes.SURVIVAL, player); + } else { + playerProfile = profileContainerStoreProvider.getStore(type) + .getContainer(dataName).getPlayerProfileNow(ProfileTypes.SURVIVAL, player); + } + MIInventoryInterface inventoryInterface = + playerFileLoader.getInventory(GameMode.SURVIVAL.toString()); + playerProfile.set(Sharables.INVENTORY, inventoryInterface.getInventoryContents()); + playerProfile.set(Sharables.ARMOR, inventoryInterface.getArmorContents()); + playerProfile.set(Sharables.HEALTH, playerFileLoader.getHealth()); + playerProfile.set(Sharables.SATURATION, playerFileLoader.getSaturation()); + playerProfile.set(Sharables.EXPERIENCE, playerFileLoader.getExperience()); + playerProfile.set(Sharables.TOTAL_EXPERIENCE, playerFileLoader.getTotalExperience()); + playerProfile.set(Sharables.LEVEL, playerFileLoader.getLevel()); + playerProfile.set(Sharables.FOOD_LEVEL, playerFileLoader.getHunger()); + profileDataSource.updatePlayerProfile(playerProfile); + } + + /** + * @return The group mapping from MultiInv, where worldName -> groupName. + * @throws DataImportException If there was any issues getting the data through reflection. + */ + private HashMap getGroupMap() throws DataImportException { + Field field; + try { + field = MIYamlFiles.class.getDeclaredField("groups"); + } catch (NoSuchFieldException nsfe) { + throw new DataImportException("The running version of MultiInv is " + + "incompatible with the import feature.").setCauseException(nsfe); + } + field.setAccessible(true); + HashMap miGroupMap = null; + try { + miGroupMap = (HashMap) field.get(null); + } catch (IllegalAccessException | ClassCastException iae) { + throw new DataImportException("The running version of MultiInv is " + + "incompatible with the import feature.").setCauseException(iae); + } + return miGroupMap; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImporter.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImporter.java new file mode 100644 index 00000000..3fd3b292 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/MultiInvImporter.java @@ -0,0 +1,63 @@ +package org.mvplugins.multiverse.inventories.dataimport.multiinv; + +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.dataimport.AbstractDataImporter; +import org.mvplugins.multiverse.inventories.dataimport.DataImportException; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import uk.co.tggl.pluckerpluck.multiinv.MultiInv; + +@Service +final class MultiInvImporter extends AbstractDataImporter { + + private final WorldGroupManager worldGroupManager; + private final InventoriesConfig inventoriesConfig; + private final ProfileContainerStoreProvider profileContainerStoreProvider; + private final ProfileDataSource profileDataSource; + + @Inject + MultiInvImporter( + @NotNull WorldGroupManager worldGroupManager, + @NotNull InventoriesConfig inventoriesConfig, + @NotNull ProfileContainerStoreProvider profileContainerStoreProvider, + @NotNull ProfileDataSource profileDataSource) { + super(); + this.worldGroupManager = worldGroupManager; + this.inventoriesConfig = inventoriesConfig; + this.profileContainerStoreProvider = profileContainerStoreProvider; + this.profileDataSource = profileDataSource; + } + + /** + * {@inheritDoc} + */ + @Override + protected void doDataImport() throws DataImportException { + new MultiInvImportHelper( + (MultiInv) importer, + worldGroupManager, + inventoriesConfig, + profileContainerStoreProvider, + profileDataSource + ).importData(); + } + /** + * {@inheritDoc} + */ + @Override + public @NotNull String getPluginName() { + return "MultiInv"; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Class getPluginClass() { + return MultiInv.class; + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/package-info.java similarity index 55% rename from src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/package-info.java rename to src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/package-info.java index 62a128b2..46044732 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/migration/multiinv/package-info.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/multiinv/package-info.java @@ -1,5 +1,5 @@ /** * This package contains MultiInv classes to handle importing their data. */ -package com.onarandombox.multiverseinventories.migration.multiinv; +package org.mvplugins.multiverse.inventories.dataimport.multiinv; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/package-info.java new file mode 100644 index 00000000..ce0d8910 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/package-info.java @@ -0,0 +1 @@ +package org.mvplugins.multiverse.inventories.dataimport; \ No newline at end of file diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PerWorldInventoryImporter.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PerWorldInventoryImporter.java new file mode 100644 index 00000000..54fb88f5 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PerWorldInventoryImporter.java @@ -0,0 +1,66 @@ +package org.mvplugins.multiverse.inventories.dataimport.perworldinventory; + +import me.ebonjaeger.perworldinventory.PerWorldInventory; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.dataimport.AbstractDataImporter; +import org.mvplugins.multiverse.inventories.dataimport.DataImportException; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; + +import java.util.Objects; + +@Service +final class PerWorldInventoryImporter extends AbstractDataImporter { + + private final InventoriesConfig inventoriesConfig; + private final WorldManager worldManager; + private final WorldGroupManager worldGroupManager; + private final ProfileDataSource profileDataSource; + + @Inject + PerWorldInventoryImporter( + @NotNull InventoriesConfig inventoriesConfig, + @NotNull WorldManager worldManager, + @NotNull WorldGroupManager worldGroupManager, + @NotNull ProfileDataSource profileDataSource) { + super(); + this.inventoriesConfig = inventoriesConfig; + this.worldManager = worldManager; + this.worldGroupManager = worldGroupManager; + this.profileDataSource = profileDataSource; + } + + /** + * {@inheritDoc} + */ + @Override + protected void doDataImport() throws DataImportException { + new PwiImportHelper( + Objects.requireNonNull(((PerWorldInventory) importer).getApi()), + inventoriesConfig, + worldManager, + worldGroupManager, + profileDataSource + ).importData(); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull String getPluginName() { + return "PerWorldInventory"; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Class getPluginClass() { + return PerWorldInventory.class; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java new file mode 100644 index 00000000..0ff6c039 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/PwiImportHelper.java @@ -0,0 +1,351 @@ +package org.mvplugins.multiverse.inventories.dataimport.perworldinventory; + +import com.dumptruckman.bukkit.configuration.util.SerializationHelper; +import com.dumptruckman.minecraft.util.Logging; +import me.ebonjaeger.perworldinventory.Group; +import me.ebonjaeger.perworldinventory.GroupManager; +import me.ebonjaeger.perworldinventory.api.PerWorldInventoryAPI; +import me.ebonjaeger.perworldinventory.configuration.PlayerSettings; +import me.ebonjaeger.perworldinventory.configuration.PluginSettings; +import me.ebonjaeger.perworldinventory.configuration.Settings; +import me.ebonjaeger.perworldinventory.data.FlatFile; +import me.ebonjaeger.perworldinventory.data.ProfileKey; +import me.ebonjaeger.perworldinventory.data.ProfileManager; +import me.ebonjaeger.perworldinventory.libs.json.JSONObject; +import me.ebonjaeger.perworldinventory.libs.json.parser.JSONParser; +import me.ebonjaeger.perworldinventory.serialization.PlayerSerializer; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.OfflinePlayer; +import org.bukkit.potion.PotionEffect; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.mvplugins.multiverse.core.utils.ReflectHelper; +import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.dataimport.DataImportException; +import org.mvplugins.multiverse.inventories.profile.GlobalProfile; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.util.FutureNow; +import org.mvplugins.multiverse.inventories.util.PlayerStats; + +import java.io.File; +import java.io.FileInputStream; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +final class PwiImportHelper { + + private final PerWorldInventoryAPI pwiAPI; + private final InventoriesConfig inventoriesConfig; + private final WorldManager worldManager; + private final WorldGroupManager worldGroupManager; + private final ProfileDataSource profileDataSource; + + private Settings pwiSettings; + private GroupManager pwiGroupManager; + private FlatFile pwiFlatFile; + private File dataDirectory; + private Method getFileMethod; + private Method deserializeMethod; + + private List playerList; + + PwiImportHelper( + @NotNull PerWorldInventoryAPI pwiAPI, + @NotNull InventoriesConfig inventoriesConfig, + @NotNull WorldManager worldManager, + @NotNull WorldGroupManager worldGroupManager, + @NotNull ProfileDataSource profileDataSource) { + this.pwiAPI = pwiAPI; + this.inventoriesConfig = inventoriesConfig; + this.worldManager = worldManager; + this.worldGroupManager = worldGroupManager; + this.profileDataSource = profileDataSource; + } + + /** + * The 'Main' method for the import. + */ + void importData() throws DataImportException { + pwiSetUp(); + transferConfigOptions(); + findPlayersWithData(); + // Since there is no such thing as individual world container in PerWorldInventory, + // everything is just groups. No need for world playerData import. + for (Group group : getPWIGroups()) { + createMVGroup(group); + saveMVDataForGroup(group); + } + } + + /** + * Do the necessary reflection to get access to the classes needed for data import. + */ + private void pwiSetUp() { + this.pwiSettings = ReflectHelper.getFieldValue(pwiAPI, "settings", Settings.class); + this.pwiGroupManager = ReflectHelper.getFieldValue(this.pwiAPI, "groupManager", GroupManager.class); + ProfileManager pwiProfileManager = ReflectHelper.getFieldValue(this.pwiAPI, "profileManager", ProfileManager.class); + this.pwiFlatFile = ReflectHelper.getFieldValue(pwiProfileManager, "dataSource", FlatFile.class); + this.getFileMethod = ReflectHelper.getMethod(this.pwiFlatFile, "getFile", ProfileKey.class); + this.dataDirectory = ReflectHelper.getFieldValue(this.pwiFlatFile, "dataDirectory", File.class); + this.deserializeMethod = ReflectHelper.getMethod(SerializationHelper.class, "deserialize", Map.class, boolean.class); + } + + /** + * Set similar/supported config options in MultiverseInventories with the values used in PerWorldInventory. + */ + private void transferConfigOptions() { + inventoriesConfig.setEnableGamemodeShareHandling(this.pwiSettings.getProperty(PluginSettings.SEPARATE_GM_INVENTORIES)); + inventoriesConfig.setApplyPlayerdataOnJoin(this.pwiSettings.getProperty(PluginSettings.LOAD_DATA_ON_JOIN)); + inventoriesConfig.setDefaultUngroupedWorlds(this.pwiSettings.getProperty(PluginSettings.SHARE_IF_UNCONFIGURED)); + inventoriesConfig.getActiveOptionalShares().setSharing(Sharables.ECONOMY, this.pwiSettings.getProperty(PlayerSettings.USE_ECONOMY)); + inventoriesConfig.save(); + } + + private void findPlayersWithData() throws DataImportException { + if (dataDirectory == null) { + throw new DataImportException("PerWorldInventory data directory not found!"); + } + File[] playerFolders = dataDirectory.listFiles(); + if (playerFolders == null) { + throw new DataImportException("Unable to traverse PerWorldInventory data directory!"); + } + this.playerList = Arrays.stream(playerFolders) + .filter(File::isDirectory) + .map(file -> UUID.fromString(file.getName())) + .map(Bukkit::getOfflinePlayer) + .toList(); + } + + /** + * Gets all PerWorldInventory groups based on all the worlds known by Multiverse. + * + * @return A collection of PerWorldInventory groups. + */ + private Collection getPWIGroups() { + Set groups = new HashSet<>(this.pwiGroupManager.getGroups().values()); + if (!inventoriesConfig.getDefaultUngroupedWorlds()) { + worldManager.getWorlds().forEach(world -> + groups.add(this.pwiGroupManager.getGroupFromWorld(world.getName()))); + } + return groups; + } + + /** + * Create a MultiverseInventories {@link WorldGroup} based on PerWorldInventory Group. + * + * @param group A PerWorldInventory Group. + */ + private void createMVGroup(Group group) { + Logging.finer("PerWorldInventory Group: %s", group); + WorldGroup worldGroup = worldGroupManager.getGroup(group.getName()); + if (worldGroup == null) { + worldGroup = worldGroupManager.newEmptyGroup(group.getName()); + } + + // In PerWorldInventory, shares can only be toggled to be enabled or disabled globally. + // So setting all shares here then transferring only those enabled later should work enough, + // since you can't actually disable shares in MultiverseInventories. + worldGroup.getShares().addAll(Sharables.allOf()); + worldGroup.addWorlds(group.getWorlds()); + if (group.getRespawnWorld() != null) { + worldGroup.setSpawnWorld(group.getRespawnWorld()); + } + worldGroupManager.updateGroup(worldGroup); + } + + /** + * Transfer all player data from PerWorldInventory to MultiverseInventories for a given group. + * + * @param group A PerWorldInventory Group. + */ + private void saveMVDataForGroup(Group group) throws DataImportException { + for (OfflinePlayer offlinePlayer : this.playerList) { + saveMVDataForPlayer(group, offlinePlayer); + } + } + + private void saveMVDataForPlayer(Group group, OfflinePlayer offlinePlayer) throws DataImportException { + GlobalProfile globalProfile = FutureNow.get(profileDataSource.getGlobalProfile(GlobalProfileKey.of(offlinePlayer))); + globalProfile.setLoadOnLogin(pwiSettings.getProperty(PluginSettings.LOAD_DATA_ON_JOIN)); + profileDataSource.updateGlobalProfile(globalProfile); + for (GameMode gameMode : GameMode.values()) { + me.ebonjaeger.perworldinventory.data.PlayerProfile pwiPlayerData = getPWIPlayerData(offlinePlayer, group, gameMode); + if (pwiPlayerData == null) { + continue; + } + for (var mvProfile : getMVPlayerData(offlinePlayer, group, gameMode)) { + transferToMVPlayerData(mvProfile, pwiPlayerData); + } + } + } + + /** + * Gets MultiverseInventories PlayerProfile based on PerWorldInventory ProfileKey. + * + * @param offlinePlayer OfflinePlayer to get data for. + * @param group Group to get data for. + * @param gameMode GameMode to get data for. + * @return A MultiverseInventories PLayerProfile. + */ + private List getMVPlayerData( + @NotNull OfflinePlayer offlinePlayer, @NotNull Group group, @NotNull GameMode gameMode) { + List profiles = new ArrayList<>(); + profiles.add(FutureNow.get(profileDataSource.getPlayerProfile(org.mvplugins.multiverse.inventories.profile.key.ProfileKey + .of(ContainerType.GROUP, group.getName(), ProfileTypes.forGameMode(gameMode), offlinePlayer.getUniqueId())))); + for (var worldName : group.getWorlds()) { + profiles.add(FutureNow.get(profileDataSource.getPlayerProfile(org.mvplugins.multiverse.inventories.profile.key.ProfileKey + .of(ContainerType.WORLD, worldName, ProfileTypes.forGameMode(gameMode), offlinePlayer.getUniqueId())))); + } + return profiles; + } + + /** + * Gets PerWorldInventory PlayerProfile based on PerWorldInventory ProfileKey. + * + * @param offlinePlayer OfflinePlayer to get data for. + * @param group Group to get data for. + * @param gameMode GameMode to get data for. + * @return A PerWorldInventory PLayerProfile. + */ + private @Nullable me.ebonjaeger.perworldinventory.data.PlayerProfile getPWIPlayerData( + @NotNull OfflinePlayer offlinePlayer, @NotNull Group group, @NotNull GameMode gameMode) throws DataImportException { + ProfileKey pwiKey = new ProfileKey(offlinePlayer.getUniqueId(), group, gameMode); + File pwiPlayerDataFile = getPWIFile(pwiKey); + if (!pwiPlayerDataFile.isFile()) { + Logging.finer("No data for %s.", pwiKey.toString()); + return null; + } + + me.ebonjaeger.perworldinventory.data.PlayerProfile pwiPlayerProfile; + try { + JSONParser parser = new JSONParser(JSONParser.USE_INTEGER_STORAGE); + JSONObject jsonObject = (JSONObject) parser.parse(new FileInputStream(pwiPlayerDataFile)); + if (jsonObject.containsKey("==")) { + pwiPlayerProfile = ReflectHelper.invokeMethod(null, deserializeMethod, jsonObject, true); + } else { + // Use legacy serialization that doesn't use ConfigurationSerializable + pwiPlayerProfile = PlayerSerializer.INSTANCE.deserialize( + jsonObject, + Objects.requireNonNull(offlinePlayer.getName()), + PlayerStats.INVENTORY_SIZE, + PlayerStats.ENDER_CHEST_SIZE); + } + } catch (Exception e) { + Logging.severe("Unable to parse file into profile: " + pwiPlayerDataFile.getAbsolutePath()); + e.printStackTrace(); + return null; + } + if (pwiPlayerProfile == null) { + Logging.warning("Empty serialization for %s.", pwiKey.toString()); + return null; + } + Logging.finer("Got pwiPlayerProfile for %s.", pwiKey.toString()); + return pwiPlayerProfile; + } + + /** + * Gets a PerWorldInventory data file based on it's ProfileKey. + * + * @param pwiKey PerWorldInventory profile key. + * @return A PerWorldInventory data file. + */ + private File getPWIFile(ProfileKey pwiKey) throws DataImportException { + return ReflectHelper.invokeMethod(this.pwiFlatFile, this.getFileMethod, pwiKey); + } + + /** + * Transfers supported player data from PerWorldInventory to MultiverseInventories. + * + * @param mvPlayerProfile MultiverseInventories PlayerProfile to transfer to. + * @param pwiPlayerProfile PerWorldInventory PlayerProfile to transfer from. + */ + private void transferToMVPlayerData( + PlayerProfile mvPlayerProfile, + me.ebonjaeger.perworldinventory.data.PlayerProfile pwiPlayerProfile + ) throws DataImportException { + if (pwiPlayerProfile == null || mvPlayerProfile == null) { + Logging.finer("Null profile(s). No data transferred for %s and %s.", mvPlayerProfile, pwiPlayerProfile); + return; + } + + // Move data from PerWorldInventory profile to MultiverseInventories profile + // Shares that are not available are commented out. + if (pwiSettings.getProperty(PlayerSettings.LOAD_INVENTORY)) { + mvPlayerProfile.set(Sharables.ARMOR, pwiPlayerProfile.getArmor()); + mvPlayerProfile.set(Sharables.INVENTORY, pwiPlayerProfile.getInventory()); + } + if (pwiSettings.getProperty(PlayerSettings.USE_ECONOMY)) { + mvPlayerProfile.set(Sharables.ECONOMY, pwiPlayerProfile.getBalance()); + } + if (pwiSettings.getProperty(PlayerSettings.LOAD_ENDER_CHEST)) { + mvPlayerProfile.set(Sharables.ENDER_CHEST, pwiPlayerProfile.getEnderChest()); + } + if (pwiSettings.getProperty(PlayerSettings.LOAD_EXHAUSTION)) { + mvPlayerProfile.set(Sharables.EXHAUSTION, pwiPlayerProfile.getExhaustion()); + } + if (pwiSettings.getProperty(PlayerSettings.LOAD_EXP)) { + mvPlayerProfile.set(Sharables.EXPERIENCE, pwiPlayerProfile.getExperience()); + } + if (pwiSettings.getProperty(PlayerSettings.LOAD_FALL_DISTANCE)) { + mvPlayerProfile.set(Sharables.FALL_DISTANCE, pwiPlayerProfile.getFallDistance()); + } + if (pwiSettings.getProperty(PlayerSettings.LOAD_FIRE_TICKS)) { + mvPlayerProfile.set(Sharables.FIRE_TICKS, pwiPlayerProfile.getFireTicks()); + } + if (pwiSettings.getProperty(PlayerSettings.LOAD_HUNGER)) { + mvPlayerProfile.set(Sharables.FOOD_LEVEL, pwiPlayerProfile.getFoodLevel()); + } + if (pwiSettings.getProperty(PlayerSettings.LOAD_HUNGER)) { + mvPlayerProfile.set(Sharables.FOOD_LEVEL, pwiPlayerProfile.getFoodLevel()); + } + if (pwiSettings.getProperty(PlayerSettings.LOAD_HEALTH)) { + mvPlayerProfile.set(Sharables.HEALTH, pwiPlayerProfile.getHealth()); + // mvPlayerProfile.set(Sharables, pwiPlayerProfile.getMaxHealth()); + } + if (pwiSettings.getProperty(PlayerSettings.LOAD_LEVEL)) { + mvPlayerProfile.set(Sharables.LEVEL, pwiPlayerProfile.getLevel()); + } + if (pwiSettings.getProperty(PlayerSettings.LOAD_MAX_AIR)) { + mvPlayerProfile.set(Sharables.MAXIMUM_AIR, pwiPlayerProfile.getMaximumAir()); + } + if (pwiSettings.getProperty(PlayerSettings.LOAD_POTION_EFFECTS)) { + mvPlayerProfile.set(Sharables.POTIONS, pwiPlayerProfile.getPotionEffects().toArray(new PotionEffect[0])); + } + if (pwiSettings.getProperty(PlayerSettings.LOAD_REMAINING_AIR)) { + mvPlayerProfile.set(Sharables.REMAINING_AIR, pwiPlayerProfile.getRemainingAir()); + } + if (pwiSettings.getProperty(PlayerSettings.LOAD_SATURATION)) { + mvPlayerProfile.set(Sharables.REMAINING_AIR, pwiPlayerProfile.getRemainingAir()); + } + // if (pwiSettings.getProperty(PlayerSettings.LOAD_DISPLAY_NAME)) { + // mvPlayerProfile.set(Sharables, pwiPlayerProfile.getDisplayName()); + // } + // if (pwiSettings.getProperty(PlayerSettings.LOAD_FLYING)) { + // mvPlayerProfile.set(Sharables, pwiPlayerProfile.getAllowFlight()); + // } + // mvPlayerProfile.set(Sharables.BED_SPAWN, pwiPlayerProfile); + // mvPlayerProfile.set(Sharables.HUNGER, pwiPlayerProfile); + // mvPlayerProfile.set(Sharables.LAST_LOCATION, pwiPlayerProfile); + // mvPlayerProfile.set(Sharables.OFF_HAND, pwiPlayerProfile); + // mvPlayerProfile.set(Sharables.TOTAL_EXPERIENCE, pwiPlayerProfile); + + profileDataSource.updatePlayerProfile(mvPlayerProfile); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/package-info.java new file mode 100644 index 00000000..c8e7f714 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/perworldinventory/package-info.java @@ -0,0 +1 @@ +package org.mvplugins.multiverse.inventories.dataimport.perworldinventory; \ No newline at end of file diff --git a/src/main/java/com/onarandombox/multiverseinventories/migration/worldinventories/WorldInventoriesImporter.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImportHelper.java similarity index 70% rename from src/main/java/com/onarandombox/multiverseinventories/migration/worldinventories/WorldInventoriesImporter.java rename to src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImportHelper.java index 7ff07ffd..363fc9ca 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/migration/worldinventories/WorldInventoriesImporter.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImportHelper.java @@ -1,274 +1,264 @@ -package com.onarandombox.multiverseinventories.migration.worldinventories; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.profile.ProfileTypes; -import com.onarandombox.multiverseinventories.profile.PlayerProfile; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainer; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.migration.DataImporter; -import com.onarandombox.multiverseinventories.migration.MigrationException; -import me.drayshak.WorldInventories.Group; -import me.drayshak.WorldInventories.WIPlayerInventory; -import me.drayshak.WorldInventories.WIPlayerStats; -import me.drayshak.WorldInventories.WorldInventories; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; -import org.bukkit.World; -import org.bukkit.plugin.Plugin; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -/** - * Handles the importing of data from WorldInventories. - */ -public class WorldInventoriesImporter implements DataImporter { - - private WorldInventories wiPlugin; - private MultiverseInventories inventories; - - public WorldInventoriesImporter(MultiverseInventories inventories, WorldInventories wiPlugin) { - this.inventories = inventories; - this.wiPlugin = wiPlugin; - } - - /** - * @return The WorldInventories plugin hooked to the importer. - */ - public WorldInventories getWIPlugin() { - return this.wiPlugin; - } - - /** - * {@inheritDoc} - */ - @Override - public Plugin getPlugin() { - return this.getWIPlugin(); - } - - /** - * Imports the data from WorldInventories into MultiverseInventories. - * - * @throws MigrationException If there was any MAJOR issues importing the data. - */ - @Override - public void importData() throws MigrationException { - List wiGroups; - try { - wiGroups = this.getWIPlugin().getGroups(); - } catch (Exception e) { - throw new MigrationException("Unable to import from this version of WorldInventories!") - .setCauseException(e); - } catch (Error e) { - throw new MigrationException("Unable to import from this version of WorldInventories!"); - } - if (wiGroups == null) { - throw new MigrationException("No data to import from WorldInventories!"); - } - - if (!wiGroups.isEmpty()) { - WorldGroup defaultWorldGroup = this.inventories.getGroupManager().getDefaultGroup(); - if (defaultWorldGroup != null) { - this.inventories.getGroupManager().removeGroup(defaultWorldGroup); - Logging.info("Removed automatically created world group in favor of imported groups."); - } - } - - this.createGroups(wiGroups); - Set noGroupWorlds = this.getWorldsWithoutGroups(); - this.inventories.getMVIConfig().save(); - - OfflinePlayer[] offlinePlayers = Bukkit.getServer().getOfflinePlayers(); - Logging.info("Processing data for " + offlinePlayers.length + " players. The larger than number, the longer" - + " this process will take. Please be patient. :) Your server will freeze for the duration."); - int playerCount = 0; - for (OfflinePlayer player : offlinePlayers) { - playerCount++; - Logging.finer("(" + playerCount + "/" + offlinePlayers.length - + ")Processing WorldInventories data for player: " + player.getName()); - for (Group wiGroup : wiGroups) { - WorldGroup worldGroup = inventories.getGroupManager().getGroup(wiGroup.getName()); - if (worldGroup == null) { - Logging.finest("Could not import player data for WorldInventories group: " + wiGroup.getName() - + " because there is no Multiverse-Inventories group by that name."); - continue; - } - this.transferData(player, wiGroup, worldGroup.getGroupProfileContainer()); - } - for (ProfileContainer container : noGroupWorlds) { - this.transferData(player, null, container); - } - } - - Logging.info("Import from WorldInventories finished. Disabling WorldInventories."); - Bukkit.getPluginManager().disablePlugin(this.getWIPlugin()); - } - - private void createGroups(List wiGroups) { - for (Group wiGroup : wiGroups) { - if (wiGroup.getWorlds().isEmpty()) { - Logging.warning("Group '" + wiGroup.getName() + "' has no worlds." - + " You may need to add these manually!"); - } - WorldGroup newGroup = inventories.getGroupManager().newEmptyGroup(wiGroup.getName()); - for (String worldName : wiGroup.getWorlds()) { - newGroup.addWorld(worldName); - } - - try { - if (WorldInventories.doStats) { - newGroup.getShares().mergeShares(Sharables.allOf()); - } else { - newGroup.getShares().setSharing(Sharables.ALL_INVENTORY, true); - } - } catch (Exception ignore) { - Logging.warning("Group '" + wiGroup.getName() + "' unable to import fully, sharing only inventory."); - newGroup.getShares().setSharing(Sharables.ALL_INVENTORY, true); - } catch (Error e) { - Logging.warning("Group '" + wiGroup.getName() + "' unable to import fully, sharing only inventory."); - newGroup.getShares().setSharing(Sharables.ALL_INVENTORY, true); - } - this.inventories.getGroupManager().updateGroup(newGroup); - Logging.info("Created Multiverse-Inventories group: " + wiGroup.getName()); - } - } - - private Set getWorldsWithoutGroups() { - Set noGroupWorlds = new LinkedHashSet<>(); - for (World world : Bukkit.getWorlds()) { - if (this.inventories.getGroupManager().getGroupsForWorld(world.getName()).isEmpty()) { - Logging.fine("Added ungrouped world for importing."); - ProfileContainer container = this.inventories.getWorldProfileContainerStore().getContainer(world.getName()); - noGroupWorlds.add(container); - } - } - return noGroupWorlds; - } - - private void transferData(OfflinePlayer player, Group wiGroup, ProfileContainer profileContainer) { - PlayerProfile playerProfile = profileContainer.getPlayerData(ProfileTypes.SURVIVAL, player); - WIPlayerInventory wiInventory = this.loadPlayerInventory(player, wiGroup); - WIPlayerStats wiStats = this.loadPlayerStats(player, wiGroup); - if (wiInventory != null) { - playerProfile.set(Sharables.INVENTORY, wiInventory.getItems()); - playerProfile.set(Sharables.ARMOR, wiInventory.getArmour()); - } - if (wiStats != null) { - playerProfile.set(Sharables.HEALTH, (double) wiStats.getHealth()); - playerProfile.set(Sharables.SATURATION, wiStats.getSaturation()); - playerProfile.set(Sharables.EXPERIENCE, wiStats.getExp()); - playerProfile.set(Sharables.LEVEL, wiStats.getLevel()); - playerProfile.set(Sharables.EXHAUSTION, wiStats.getExhaustion()); - playerProfile.set(Sharables.FOOD_LEVEL, wiStats.getFoodLevel()); - } - this.inventories.getData().updatePlayerData(playerProfile); - Logging.finest("Player's data imported successfully for group: " + profileContainer.getContainerName()); - } - - private File getFile(OfflinePlayer player, Group group, DataType dataType) { - StringBuilder path = new StringBuilder(); - path.append(File.separator); - - // Use default group - if (group == null) { - path.append("default"); - } else { - path.append(group.getName()); - } - path.insert(0, this.getWIPlugin().getDataFolder().getAbsolutePath()); - path.append(File.separator).append(player.getName()).append(dataType.fileExtension); - - File file = new File(path.toString()); - if (!file.exists()) { - file = null; - } - return file; - } - - // Copied and modified from WorldInventories - private WIPlayerInventory loadPlayerInventory(OfflinePlayer player, Group group) { - File file = this.getFile(player, group, DataType.INVENTORY); - if (file == null) { - return null; - } - WIPlayerInventory playerInventory = null; - FileInputStream fIS = null; - ObjectInputStream obIn = null; - try { - fIS = new FileInputStream(file); - obIn = new ObjectInputStream(fIS); - playerInventory = (WIPlayerInventory) obIn.readObject(); - } catch (Exception ignore) { - } finally { - if (obIn != null) { - try { - obIn.close(); - } catch (IOException ignore) { - } - } - if (fIS != null) { - try { - fIS.close(); - } catch (IOException ignore) { - } - } - } - - return playerInventory; - } - - // Copied and modified from WorldInventories - private WIPlayerStats loadPlayerStats(OfflinePlayer player, Group group) { - File file = this.getFile(player, group, DataType.STATS); - if (file == null) { - return null; - } - WIPlayerStats playerstats = null; - FileInputStream fIS = null; - ObjectInputStream obIn = null; - try { - fIS = new FileInputStream(file); - obIn = new ObjectInputStream(fIS); - playerstats = (WIPlayerStats) obIn.readObject(); - } catch (Exception ignore) { - } finally { - if (obIn != null) { - try { - obIn.close(); - } catch (IOException ignore) { - } - } - if (fIS != null) { - try { - fIS.close(); - } catch (IOException ignore) { - } - } - } - - return playerstats; - } - - /** - * Indicates the type of data we're importing for. - */ - private enum DataType { - INVENTORY(".inventory"), - STATS(".stats"); - - private String fileExtension; - - DataType(String fileExtension) { - this.fileExtension = fileExtension; - } - } -} - +package org.mvplugins.multiverse.inventories.dataimport.worldinventories; + +import com.dumptruckman.minecraft.util.Logging; +import me.drayshak.WorldInventories.Group; +import me.drayshak.WorldInventories.WIPlayerInventory; +import me.drayshak.WorldInventories.WIPlayerStats; +import me.drayshak.WorldInventories.WorldInventories; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.World; +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.dataimport.DataImportException; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.share.Sharables; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +final class WorldInventoriesImportHelper { + + @NotNull + private final WorldInventories worldInventories; + private final WorldGroupManager worldGroupManager; + private final InventoriesConfig inventoriesConfig; + private final ProfileContainerStoreProvider profileContainerStoreProvider; + private final ProfileDataSource profileDataSource; + + WorldInventoriesImportHelper( + @NotNull WorldInventories worldInventories, + @NotNull WorldGroupManager worldGroupManager, + @NotNull InventoriesConfig inventoriesConfig, + @NotNull ProfileContainerStoreProvider profileContainerStoreProvider, + @NotNull ProfileDataSource profileDataSource) { + super(); + this.worldInventories = worldInventories; + this.worldGroupManager = worldGroupManager; + this.inventoriesConfig = inventoriesConfig; + this.profileContainerStoreProvider = profileContainerStoreProvider; + this.profileDataSource = profileDataSource; + } + + void importData() throws DataImportException { + List wiGroups; + try { + wiGroups = worldInventories.getGroups(); + } catch (Exception e) { + throw new DataImportException("Unable to import from this version of WorldInventories!") + .setCauseException(e); + } catch (Error e) { + throw new DataImportException("Unable to import from this version of WorldInventories!"); + } + if (wiGroups == null) { + throw new DataImportException("No data to import from WorldInventories!"); + } + + if (!wiGroups.isEmpty()) { + WorldGroup defaultWorldGroup = worldGroupManager.getDefaultGroup(); + if (defaultWorldGroup != null) { + worldGroupManager.removeGroup(defaultWorldGroup); + Logging.info("Removed automatically created world group in favor of imported groups."); + } + } + + this.createGroups(wiGroups); + Set noGroupWorlds = this.getWorldsWithoutGroups(); + inventoriesConfig.save(); + + OfflinePlayer[] offlinePlayers = Bukkit.getServer().getOfflinePlayers(); + Logging.info("Processing data for " + offlinePlayers.length + " players. The larger than number, the longer" + + " this process will take. Please be patient. :) Your server will freeze for the duration."); + int playerCount = 0; + for (OfflinePlayer player : offlinePlayers) { + playerCount++; + Logging.finer("(" + playerCount + "/" + offlinePlayers.length + + ")Processing WorldInventories data for player: " + player.getName()); + for (Group wiGroup : wiGroups) { + WorldGroup worldGroup = worldGroupManager.getGroup(wiGroup.getName()); + if (worldGroup == null) { + Logging.finest("Could not import player data for WorldInventories group: " + wiGroup.getName() + + " because there is no Multiverse-Inventories group by that name."); + continue; + } + this.transferData(player, wiGroup, worldGroup.getGroupProfileContainer()); + } + for (ProfileContainer container : noGroupWorlds) { + this.transferData(player, null, container); + } + } + } + + private void createGroups(List wiGroups) { + for (Group wiGroup : wiGroups) { + if (wiGroup.getWorlds().isEmpty()) { + Logging.warning("Group '" + wiGroup.getName() + "' has no worlds." + + " You may need to add these manually!"); + } + WorldGroup newGroup = worldGroupManager.newEmptyGroup(wiGroup.getName()); + for (String worldName : wiGroup.getWorlds()) { + newGroup.addWorld(worldName); + } + + try { + if (WorldInventories.doStats) { + newGroup.getShares().mergeShares(Sharables.allOf()); + } else { + newGroup.getShares().setSharing(Sharables.ALL_INVENTORY, true); + } + } catch (Exception ignore) { + Logging.warning("Group '" + wiGroup.getName() + "' unable to import fully, sharing only inventory."); + newGroup.getShares().setSharing(Sharables.ALL_INVENTORY, true); + } catch (Error e) { + Logging.warning("Group '" + wiGroup.getName() + "' unable to import fully, sharing only inventory."); + newGroup.getShares().setSharing(Sharables.ALL_INVENTORY, true); + } + worldGroupManager.updateGroup(newGroup); + Logging.info("Created Multiverse-Inventories group: " + wiGroup.getName()); + } + } + + private Set getWorldsWithoutGroups() { + Set noGroupWorlds = new LinkedHashSet<>(); + for (World world : Bukkit.getWorlds()) { + if (worldGroupManager.getGroupsForWorld(world.getName()).isEmpty()) { + Logging.fine("Added ungrouped world for importing."); + ProfileContainer container = profileContainerStoreProvider.getStore(ContainerType.WORLD) + .getContainer(world.getName()); + noGroupWorlds.add(container); + } + } + return noGroupWorlds; + } + + private void transferData(OfflinePlayer player, Group wiGroup, ProfileContainer profileContainer) { + PlayerProfile playerProfile = profileContainer.getPlayerProfileNow(ProfileTypes.SURVIVAL, player); + WIPlayerInventory wiInventory = this.loadPlayerInventory(player, wiGroup); + WIPlayerStats wiStats = this.loadPlayerStats(player, wiGroup); + if (wiInventory != null) { + playerProfile.set(Sharables.INVENTORY, wiInventory.getItems()); + playerProfile.set(Sharables.ARMOR, wiInventory.getArmour()); + } + if (wiStats != null) { + playerProfile.set(Sharables.HEALTH, (double) wiStats.getHealth()); + playerProfile.set(Sharables.SATURATION, wiStats.getSaturation()); + playerProfile.set(Sharables.EXPERIENCE, wiStats.getExp()); + playerProfile.set(Sharables.LEVEL, wiStats.getLevel()); + playerProfile.set(Sharables.EXHAUSTION, wiStats.getExhaustion()); + playerProfile.set(Sharables.FOOD_LEVEL, wiStats.getFoodLevel()); + } + profileDataSource.updatePlayerProfile(playerProfile); + Logging.finest("Player's data imported successfully for group: " + profileContainer.getContainerName()); + } + + private File getFile(OfflinePlayer player, Group group, DataType dataType) { + StringBuilder path = new StringBuilder(); + path.append(File.separator); + + // Use default group + if (group == null) { + path.append("default"); + } else { + path.append(group.getName()); + } + path.insert(0, worldInventories.getDataFolder().getAbsolutePath()); + path.append(File.separator).append(player.getName()).append(dataType.fileExtension); + + File file = new File(path.toString()); + if (!file.exists()) { + file = null; + } + return file; + } + + // Copied and modified from WorldInventories + private WIPlayerInventory loadPlayerInventory(OfflinePlayer player, Group group) { + File file = this.getFile(player, group, DataType.INVENTORY); + if (file == null) { + return null; + } + WIPlayerInventory playerInventory = null; + FileInputStream fIS = null; + ObjectInputStream obIn = null; + try { + fIS = new FileInputStream(file); + obIn = new ObjectInputStream(fIS); + playerInventory = (WIPlayerInventory) obIn.readObject(); + } catch (Exception ignore) { + } finally { + if (obIn != null) { + try { + obIn.close(); + } catch (IOException ignore) { + } + } + if (fIS != null) { + try { + fIS.close(); + } catch (IOException ignore) { + } + } + } + + return playerInventory; + } + + // Copied and modified from WorldInventories + private WIPlayerStats loadPlayerStats(OfflinePlayer player, Group group) { + File file = this.getFile(player, group, DataType.STATS); + if (file == null) { + return null; + } + WIPlayerStats playerstats = null; + FileInputStream fIS = null; + ObjectInputStream obIn = null; + try { + fIS = new FileInputStream(file); + obIn = new ObjectInputStream(fIS); + playerstats = (WIPlayerStats) obIn.readObject(); + } catch (Exception ignore) { + } finally { + if (obIn != null) { + try { + obIn.close(); + } catch (IOException ignore) { + } + } + if (fIS != null) { + try { + fIS.close(); + } catch (IOException ignore) { + } + } + } + + return playerstats; + } + + + /** + * Indicates the type of data we're importing for. + */ + private enum DataType { + INVENTORY(".inventory"), + STATS(".stats"); + + private String fileExtension; + + DataType(String fileExtension) { + this.fileExtension = fileExtension; + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImporter.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImporter.java new file mode 100644 index 00000000..42ce3963 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/WorldInventoriesImporter.java @@ -0,0 +1,64 @@ +package org.mvplugins.multiverse.inventories.dataimport.worldinventories; + +import me.drayshak.WorldInventories.WorldInventories; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.dataimport.AbstractDataImporter; +import org.mvplugins.multiverse.inventories.dataimport.DataImportException; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; + +@Service +final class WorldInventoriesImporter extends AbstractDataImporter { + + private final WorldGroupManager worldGroupManager; + private final InventoriesConfig inventoriesConfig; + private final ProfileContainerStoreProvider profileContainerStoreProvider; + private final ProfileDataSource profileDataSource; + + @Inject + WorldInventoriesImporter( + @NotNull WorldGroupManager worldGroupManager, + @NotNull InventoriesConfig inventoriesConfig, + @NotNull ProfileContainerStoreProvider profileContainerStoreProvider, + @NotNull ProfileDataSource profileDataSource) { + super(); + this.worldGroupManager = worldGroupManager; + this.inventoriesConfig = inventoriesConfig; + this.profileContainerStoreProvider = profileContainerStoreProvider; + this.profileDataSource = profileDataSource; + } + + /** + * {@inheritDoc} + */ + @Override + protected void doDataImport() throws DataImportException { + new WorldInventoriesImportHelper( + (WorldInventories) importer, + worldGroupManager, + inventoriesConfig, + profileContainerStoreProvider, + profileDataSource + ).importData(); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull String getPluginName() { + return "WorldInventories"; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Class getPluginClass() { + return WorldInventories.class; + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/migration/worldinventories/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/package-info.java similarity index 54% rename from src/main/java/com/onarandombox/multiverseinventories/migration/worldinventories/package-info.java rename to src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/package-info.java index f82850c7..adae4de0 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/migration/worldinventories/package-info.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/dataimport/worldinventories/package-info.java @@ -1,5 +1,5 @@ /** * This package contains WorldInventories classes to handle importing their data. */ -package com.onarandombox.multiverseinventories.migration.worldinventories; +package org.mvplugins.multiverse.inventories.dataimport.worldinventories; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestination.java b/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestination.java new file mode 100644 index 00000000..49d8c003 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestination.java @@ -0,0 +1,78 @@ +package org.mvplugins.multiverse.inventories.destination; + +import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.command.CommandSender; +import org.checkerframework.checker.units.qual.N; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.destination.Destination; +import org.mvplugins.multiverse.core.destination.DestinationSuggestionPacket; +import org.mvplugins.multiverse.core.locale.MVCorei18n; +import org.mvplugins.multiverse.core.utils.result.Attempt; +import org.mvplugins.multiverse.core.utils.result.FailureReason; +import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.external.acf.locales.MessageKey; +import org.mvplugins.multiverse.external.acf.locales.MessageKeyProvider; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.external.jetbrains.annotations.Nullable; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; + +import java.util.Collection; + +@Service +public final class LastLocationDestination implements Destination { + + private final WorldManager worldManager; + private final WorldGroupManager worldGroupManager; + private final ProfileContainerStoreProvider profileContainerStoreProvider; + + @Inject + LastLocationDestination( + @NotNull WorldManager worldManager, + @NotNull WorldGroupManager worldGroupManager, + @NotNull ProfileContainerStoreProvider profileContainerStoreProvider) { + this.worldManager = worldManager; + this.worldGroupManager = worldGroupManager; + this.profileContainerStoreProvider = profileContainerStoreProvider; + } + + @Override + public @NotNull String getIdentifier() { + return "ll"; + } + + @Override + public @NotNull Attempt getDestinationInstance(@NotNull String destinationParams) { + if (!worldManager.isLoadedWorld(destinationParams)) { + return Attempt.failure(InstanceFailureReason.WORLD_NOT_FOUND); + } + return Attempt.success(new LastLocationDestinationInstance(this, worldGroupManager, profileContainerStoreProvider, destinationParams)); + } + + @Override + public @NotNull Collection suggestDestinations(@NotNull CommandSender commandSender, @Nullable String destinationParams) { + return worldManager.getLoadedWorlds().stream() + .map(world -> new DestinationSuggestionPacket(this, world.getName(), world.getName())) + .toList(); + } + + public enum InstanceFailureReason implements FailureReason { + WORLD_NOT_FOUND(MVCorei18n.DESTINATION_SHARED_FAILUREREASON_WORLDNOTFOUND), + ; + + private final MessageKeyProvider messageKey; + + InstanceFailureReason(MessageKeyProvider message) { + this.messageKey = message; + } + + /** + * {@inheritDoc} + */ + @Override + public MessageKey getMessageKey() { + return messageKey.getMessageKey(); + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestinationInstance.java b/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestinationInstance.java new file mode 100644 index 00000000..6c9748d1 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/destination/LastLocationDestinationInstance.java @@ -0,0 +1,81 @@ +package org.mvplugins.multiverse.inventories.destination; + +import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; +import org.mvplugins.multiverse.core.destination.DestinationInstance; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.external.vavr.control.Option; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.share.Sharables; + +public final class LastLocationDestinationInstance extends DestinationInstance { + + private final WorldGroupManager worldGroupManager; + private final ProfileContainerStoreProvider profileContainerStoreProvider; + private final String worldName; + + LastLocationDestinationInstance( + @NotNull LastLocationDestination destination, + @NotNull WorldGroupManager worldGroupManager, + @NotNull ProfileContainerStoreProvider profileContainerStoreProvider, + @NotNull String worldName) { + super(destination); + this.worldGroupManager = worldGroupManager; + this.profileContainerStoreProvider = profileContainerStoreProvider; + this.worldName = worldName; + } + + @Override + public @NotNull Option getLocation(@NotNull Entity teleportee) { + Logging.warning("LastLocationDestination: teleportee: " + teleportee); + if (!(teleportee instanceof Player player)) { + return Option.none(); + } + + var playerWorld = player.getWorld().getName(); + if (playerWorld.equals(worldName)) { + return Option.none(); + } + + for (var group : worldGroupManager.getGroupsForWorld(worldName)) { + Logging.warning("LastLocationDestination: group: " + group); + if (!group.containsWorld(playerWorld) && group.getApplicableShares().contains(Sharables.LAST_LOCATION)) { + return Option.of(profileContainerStoreProvider.getStore(ContainerType.GROUP) + .getContainer(group.getName()) + .getPlayerProfileNow(player) + .get(Sharables.LAST_LOCATION)); + } + } + + // Means last location isn't shared by any group, and will be read directly for world profile + return Option.of(profileContainerStoreProvider.getStore(ContainerType.WORLD) + .getContainer(worldName) + .getPlayerProfileNow(player) + .get(Sharables.LAST_LOCATION)); + } + + @Override + public @NotNull Option getVelocity(@NotNull Entity teleportee) { + return Option.none(); + } + + @Override + public boolean checkTeleportSafety() { + return false; + } + + @Override + public @NotNull Option getFinerPermissionSuffix() { + return Option.of(worldName); + } + + @Override + protected @NotNull String serialise() { + return worldName; + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/event/GameModeChangeShareHandlingEvent.java b/src/main/java/org/mvplugins/multiverse/inventories/event/GameModeChangeShareHandlingEvent.java similarity index 77% rename from src/main/java/com/onarandombox/multiverseinventories/event/GameModeChangeShareHandlingEvent.java rename to src/main/java/org/mvplugins/multiverse/inventories/event/GameModeChangeShareHandlingEvent.java index b785a28b..fb94f830 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/event/GameModeChangeShareHandlingEvent.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/event/GameModeChangeShareHandlingEvent.java @@ -1,12 +1,12 @@ -package com.onarandombox.multiverseinventories.event; +package org.mvplugins.multiverse.inventories.event; -import com.onarandombox.multiverseinventories.ShareHandler; +import org.mvplugins.multiverse.inventories.handleshare.AffectedProfiles; import org.bukkit.GameMode; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; -public class GameModeChangeShareHandlingEvent extends ShareHandlingEvent implements Cancellable { +public final class GameModeChangeShareHandlingEvent extends ShareHandlingEvent implements Cancellable { private static final HandlerList HANDLERS = new HandlerList(); @@ -21,7 +21,7 @@ public static HandlerList getHandlerList() { private final GameMode fromGameMode; private final GameMode toGameMode; - public GameModeChangeShareHandlingEvent(Player player, ShareHandler.AffectedProfiles affectedProfiles, + public GameModeChangeShareHandlingEvent(Player player, AffectedProfiles affectedProfiles, GameMode fromGameMode, GameMode toGameMode) { super(player, affectedProfiles); this.fromGameMode = fromGameMode; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/event/ReadOnlyShareHandlingEvent.java b/src/main/java/org/mvplugins/multiverse/inventories/event/ReadOnlyShareHandlingEvent.java new file mode 100644 index 00000000..f756f6d5 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/event/ReadOnlyShareHandlingEvent.java @@ -0,0 +1,28 @@ +package org.mvplugins.multiverse.inventories.event; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.handleshare.AffectedProfiles; + +public final class ReadOnlyShareHandlingEvent extends ShareHandlingEvent { + + private static final HandlerList HANDLERS = new HandlerList(); + + /** + * Gets the handler list. This is required by the event system. + * @return A list of HANDLERS. + */ + public static HandlerList getHandlerList() { + return HANDLERS; + } + + public ReadOnlyShareHandlingEvent(Player player, AffectedProfiles affectedProfiles) { + super(player, affectedProfiles); + } + + @Override + public @NotNull HandlerList getHandlers() { + return HANDLERS; + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/event/ShareHandlingEvent.java b/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java similarity index 58% rename from src/main/java/com/onarandombox/multiverseinventories/event/ShareHandlingEvent.java rename to src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java index d65b1bca..882f7f5c 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/event/ShareHandlingEvent.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/event/ShareHandlingEvent.java @@ -1,30 +1,26 @@ -package com.onarandombox.multiverseinventories.event; +package org.mvplugins.multiverse.inventories.event; -import com.onarandombox.multiverseinventories.ShareHandler; -import com.onarandombox.multiverseinventories.share.PersistingProfile; +import org.mvplugins.multiverse.inventories.handleshare.AffectedProfiles; +import org.mvplugins.multiverse.inventories.handleshare.PersistingProfile; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; -import java.util.Collection; -import java.util.LinkedList; import java.util.List; /** * Called when a player has changed from one world to another. Cancellable. */ -public abstract class ShareHandlingEvent extends Event implements Cancellable { +public abstract sealed class ShareHandlingEvent extends Event implements Cancellable permits GameModeChangeShareHandlingEvent, ReadOnlyShareHandlingEvent, WorldChangeShareHandlingEvent, WriteOnlyShareHandlingEvent { private boolean cancelled; private final Player player; - private final PersistingProfile alwaysWriteProfile; private final List writeProfiles; private final List readProfiles; - ShareHandlingEvent(Player player, ShareHandler.AffectedProfiles affectedProfiles) { + ShareHandlingEvent(Player player, AffectedProfiles affectedProfiles) { this.player = player; - this.alwaysWriteProfile = affectedProfiles.getAlwaysWriteProfile(); this.writeProfiles = affectedProfiles.getWriteProfiles(); this.readProfiles = affectedProfiles.getReadProfiles(); } @@ -46,17 +42,7 @@ public void setCancelled(boolean cancel) { } /** - * Returns the profile that will always be saved to. By default, this is a profile for the world the player was in. - * - * @return The profile that will always be saved to when this event occurs. - */ - public PersistingProfile getAlwaysWriteProfile() { - return alwaysWriteProfile; - } - - /** - * @return The profiles for the world/groups the player is coming from that data will be saved to in addition to - * the profile returned by {@link #getAlwaysWriteProfile()}. + * @return The profiles for the world/groups the player is coming from that data will be saved to. */ public List getWriteProfiles() { return this.writeProfiles; diff --git a/src/main/java/com/onarandombox/multiverseinventories/event/WorldChangeShareHandlingEvent.java b/src/main/java/org/mvplugins/multiverse/inventories/event/WorldChangeShareHandlingEvent.java similarity index 70% rename from src/main/java/com/onarandombox/multiverseinventories/event/WorldChangeShareHandlingEvent.java rename to src/main/java/org/mvplugins/multiverse/inventories/event/WorldChangeShareHandlingEvent.java index 87bda1e1..a1bc66cd 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/event/WorldChangeShareHandlingEvent.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/event/WorldChangeShareHandlingEvent.java @@ -1,11 +1,11 @@ -package com.onarandombox.multiverseinventories.event; +package org.mvplugins.multiverse.inventories.event; -import com.onarandombox.multiverseinventories.ShareHandler; +import org.mvplugins.multiverse.inventories.handleshare.AffectedProfiles; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; -public class WorldChangeShareHandlingEvent extends ShareHandlingEvent implements Cancellable { +public final class WorldChangeShareHandlingEvent extends ShareHandlingEvent implements Cancellable { private static final HandlerList HANDLERS = new HandlerList(); @@ -20,8 +20,11 @@ public static HandlerList getHandlerList() { private final String fromWorld; private final String toWorld; - public WorldChangeShareHandlingEvent(Player player, ShareHandler.AffectedProfiles affectedProfiles, - String fromWorld, String toWorld) { + public WorldChangeShareHandlingEvent( + Player player, + AffectedProfiles affectedProfiles, + String fromWorld, + String toWorld) { super(player, affectedProfiles); this.fromWorld = fromWorld; this.toWorld = toWorld; @@ -48,5 +51,4 @@ public String getFromWorld() { public String getToWorld() { return this.toWorld; } - } diff --git a/src/main/java/org/mvplugins/multiverse/inventories/event/WriteOnlyShareHandlingEvent.java b/src/main/java/org/mvplugins/multiverse/inventories/event/WriteOnlyShareHandlingEvent.java new file mode 100644 index 00000000..803f63a4 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/event/WriteOnlyShareHandlingEvent.java @@ -0,0 +1,28 @@ +package org.mvplugins.multiverse.inventories.event; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.handleshare.AffectedProfiles; + +public final class WriteOnlyShareHandlingEvent extends ShareHandlingEvent { + + private static final HandlerList HANDLERS = new HandlerList(); + + /** + * Gets the handler list. This is required by the event system. + * @return A list of HANDLERS. + */ + public static HandlerList getHandlerList() { + return HANDLERS; + } + + public WriteOnlyShareHandlingEvent(Player player, AffectedProfiles affectedProfiles) { + super(player, affectedProfiles); + } + + @Override + public @NotNull HandlerList getHandlers() { + return HANDLERS; + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/event/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/event/package-info.java similarity index 69% rename from src/main/java/com/onarandombox/multiverseinventories/event/package-info.java rename to src/main/java/org/mvplugins/multiverse/inventories/event/package-info.java index c9221141..deb2da33 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/event/package-info.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/event/package-info.java @@ -1,5 +1,5 @@ /** * This package contains event classes that are thrown by Multiverse-Inventories for handling by other plugins. */ -package com.onarandombox.multiverseinventories.event; +package org.mvplugins.multiverse.inventories.event; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java new file mode 100644 index 00000000..2ab8e55d --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/AffectedProfiles.java @@ -0,0 +1,40 @@ +package org.mvplugins.multiverse.inventories.handleshare; + +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; +import org.mvplugins.multiverse.inventories.share.Shares; + +import java.util.LinkedList; +import java.util.List; + +public final class AffectedProfiles { + + private final List writeProfiles = new LinkedList<>(); + private final List readProfiles = new LinkedList<>(); + + AffectedProfiles() { + } + + /** + * @param profileKey The profile key that will need data saved to. + * @param shares What from this group needs to be saved. + */ + void addWriteProfile(ProfileKey profileKey, Shares shares) { + writeProfiles.add(new PersistingProfile(shares, profileKey)); + } + + /** + * @param profileKey The profile key that will need data loaded from. + * @param shares What from this group needs to be loaded. + */ + void addReadProfile(ProfileKey profileKey, Shares shares) { + readProfiles.add(new PersistingProfile(shares, profileKey)); + } + + public List getWriteProfiles() { + return writeProfiles; + } + + public List getReadProfiles() { + return readProfiles; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java new file mode 100644 index 00000000..fc0b1ae2 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/GameModeShareHandler.java @@ -0,0 +1,109 @@ +package org.mvplugins.multiverse.inventories.handleshare; + +import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.event.GameModeChangeShareHandlingEvent; +import org.mvplugins.multiverse.inventories.event.ShareHandlingEvent; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; +import org.mvplugins.multiverse.inventories.util.Perm; +import org.bukkit.GameMode; +import org.bukkit.entity.Player; + +import java.util.List; + +/** + * GameMode change implementation of ShareHandler. + */ +final class GameModeShareHandler extends ShareHandler { + + private final GameMode fromGameMode; + private final GameMode toGameMode; + private final ProfileType fromType; + private final ProfileType toType; + private final String world; + private final List worldGroups; + + GameModeShareHandler(MultiverseInventories inventories, Player player, + GameMode fromGameMode, GameMode toGameMode) { + super(inventories, player); + this.fromGameMode = fromGameMode; + this.toGameMode = toGameMode; + this.fromType = ProfileTypes.forGameMode(fromGameMode); + this.toType = ProfileTypes.forGameMode(toGameMode); + this.world = player.getWorld().getName(); + this.worldGroups = getAffectedWorldGroups(); + } + + private List getAffectedWorldGroups() { + return worldGroupManager.getGroupsForWorld(world); + } + + @Override + protected ShareHandlingEvent createEvent() { + return new GameModeChangeShareHandlingEvent(player, affectedProfiles, fromGameMode, toGameMode); + } + + @Override + protected void prepareProfiles() { + Logging.fine("=== " + player.getName() + " changing game mode from: " + fromType + + " to: " + toType + " for world: " + world + " ==="); + + if (isPlayerAffectedByChange()) { + addProfiles(); + } else if (inventoriesConfig.getAlwaysWriteWorldProfile()) { + // Write to world profile to ensure data is saved incase bypass is removed + affectedProfiles.addWriteProfile( + worldProfileContainerStore.getContainer(world).getProfileKey(fromType, player), + (worldGroups.isEmpty() && !inventoriesConfig.getUseOptionalsForUngroupedWorlds()) + ? Sharables.standard() + : Sharables.enabled() + ); + } + } + + private boolean isPlayerAffectedByChange() { + if (isPlayerBypassingChange()) { + logBypass(); + return false; + } + return true; + } + + private boolean isPlayerBypassingChange() { + return Perm.BYPASS_WORLD.hasBypass(player, world) + || Perm.BYPASS_GAME_MODE.hasBypass(player, toGameMode.name().toLowerCase()); + } + + private void addProfiles() { + Shares handledShares = Sharables.noneOf(); + worldGroups.forEach(worldGroup -> addProfilesForWorldGroup(handledShares,worldGroup)); + Shares unhandledShares = (worldGroups.isEmpty() && !inventoriesConfig.getUseOptionalsForUngroupedWorlds()) + ? Sharables.standardOf() : Sharables.enabledOf(); + unhandledShares.removeAll(handledShares); + if (!unhandledShares.isEmpty()) { + affectedProfiles.addReadProfile(worldProfileContainerStore.getContainer(world).getProfileKey(toType, player), unhandledShares); + } + + if (inventoriesConfig.getAlwaysWriteWorldProfile()) { + affectedProfiles.addWriteProfile(worldProfileContainerStore.getContainer(world).getProfileKey(fromType, player), + inventoriesConfig.getUseOptionalsForUngroupedWorlds() ? Sharables.enabled() : Sharables.standard()); + } else { + if (!unhandledShares.isEmpty()) { + affectedProfiles.addWriteProfile(worldProfileContainerStore.getContainer(world).getProfileKey(fromType, player), unhandledShares); + } + } + } + + private void addProfilesForWorldGroup(Shares handledShares, WorldGroup worldGroup) { + ProfileContainer container = worldGroup.getGroupProfileContainer(); + affectedProfiles.addWriteProfile(container.getProfileKey(fromType, player), worldGroup.getApplicableShares()); + affectedProfiles.addReadProfile(container.getProfileKey(toType, player), worldGroup.getApplicableShares()); + handledShares.addAll(worldGroup.getApplicableShares()); + handledShares.addAll(worldGroup.getDisabledShares()); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/PersistingProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/PersistingProfile.java new file mode 100644 index 00000000..2bf96b2a --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/PersistingProfile.java @@ -0,0 +1,43 @@ +package org.mvplugins.multiverse.inventories.handleshare; + +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; +import org.mvplugins.multiverse.inventories.share.Shares; + +/** + * Simple class for groups that are going to be saved/loaded. This is used specifically for when a user's world + * change is being handled. + */ +public final class PersistingProfile { + private final Shares shares; + private final ProfileKey profileKey; + + public PersistingProfile(Shares shares, PlayerProfile profile) { + this(shares, ProfileKey.fromPlayerProfile(profile)); + } + + public PersistingProfile(Shares shares, ProfileKey profile) { + this.shares = shares; + this.profileKey = profile; + } + + /** + * Gets the shares that will be saved/loaded for the profile. + * + * @return The shares that will be saved/loaded for the profile. This is the set of all Sharables that will be acted + * upon when passed through the ShareHandler class, or any of its subclasses. + */ + public Shares getShares() { + return this.shares; + } + + /** + * Gets the player profile for the world/group that will be saved/loaded for. + * + * @return The player profile for the world/group that will be saved/loaded for. + */ + public ProfileKey getProfileKey() { + return this.profileKey; + } +} + diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ReadOnlyShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ReadOnlyShareHandler.java new file mode 100644 index 00000000..272dff8a --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ReadOnlyShareHandler.java @@ -0,0 +1,39 @@ +package org.mvplugins.multiverse.inventories.handleshare; + +import org.bukkit.entity.Player; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.event.ReadOnlyShareHandlingEvent; +import org.mvplugins.multiverse.inventories.event.ShareHandlingEvent; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; + +import java.util.List; + +public final class ReadOnlyShareHandler extends ShareHandler { + + public ReadOnlyShareHandler(MultiverseInventories inventories, Player player) { + super(inventories, player); + } + + @Override + protected void prepareProfiles() { + List worldGroups = worldGroupManager.getGroupsForWorld(player.getWorld().getName()); + Shares unhandledShares = Sharables.enabledOf(); + for (WorldGroup worldGroup : worldGroups) { + affectedProfiles.addReadProfile(worldGroup.getGroupProfileContainer().getProfileKey(player), worldGroup.getApplicableShares()); + unhandledShares.removeAll(worldGroup.getApplicableShares()); + } + if (!unhandledShares.isEmpty()) { + affectedProfiles.addReadProfile( + worldProfileContainerStore.getContainer(player.getWorld().getName()).getProfileKey(player), + unhandledShares + ); + } + } + + @Override + protected ShareHandlingEvent createEvent() { + return new ReadOnlyShareHandlingEvent(player, affectedProfiles); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java new file mode 100644 index 00000000..0a2f2f41 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandleListener.java @@ -0,0 +1,352 @@ +package org.mvplugins.multiverse.inventories.handleshare; + +import com.dumptruckman.minecraft.util.Logging; +import com.google.common.base.Strings; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.external.vavr.control.Try; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.GlobalProfile; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityPortalEvent; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.AsyncPlayerPreLoginEvent; +import org.bukkit.event.player.AsyncPlayerPreLoginEvent.Result; +import org.bukkit.event.player.PlayerChangedWorldEvent; +import org.bukkit.event.player.PlayerGameModeChangeEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerRespawnEvent; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.event.world.WorldUnloadEvent; +import org.bukkit.inventory.InventoryHolder; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.util.FutureNow; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +/** + * Events related to handling of player profile changes. + */ +@Service +public final class ShareHandleListener implements Listener { + + private final MultiverseInventories inventories; + private final InventoriesConfig config; + private final WorldManager worldManager; + private final WorldGroupManager worldGroupManager; + private final ProfileDataSource profileDataSource; + private final ProfileContainerStoreProvider profileContainerStoreProvider; + + @Inject + ShareHandleListener( + @NotNull MultiverseInventories inventories, InventoriesConfig config, + @NotNull WorldManager worldManager, + @NotNull WorldGroupManager worldGroupManager, + @NotNull ProfileDataSource profileDataSource, + @NotNull ProfileContainerStoreProvider profileContainerStoreProvider) { + this.inventories = inventories; + this.config = config; + this.worldManager = worldManager; + this.worldGroupManager = worldGroupManager; + this.profileDataSource = profileDataSource; + this.profileContainerStoreProvider = profileContainerStoreProvider; + } + + @EventHandler(priority = EventPriority.MONITOR) + void playerPreLogin(AsyncPlayerPreLoginEvent event) { + if (event.getLoginResult() != Result.ALLOWED) { + return; + } + Logging.finer("Loading global profile for Player{name:'%s', uuid:'%s'}.", + event.getName(), event.getUniqueId()); + verifyCorrectPlayerName(event.getUniqueId(), event.getName()); + + long startTime = System.nanoTime(); + List> profileFutures = new ArrayList<>(); + config.getPreloadDataOnJoinWorlds().forEach(worldName -> profileFutures.add(profileDataSource.getPlayerProfile( + ProfileKey.of(ContainerType.WORLD, worldName, ProfileTypes.SURVIVAL, event.getUniqueId(), event.getName())))); + config.getPreloadDataOnJoinGroups().forEach(groupName -> profileFutures.add(profileDataSource.getPlayerProfile( + ProfileKey.of(ContainerType.GROUP, groupName, ProfileTypes.SURVIVAL, event.getUniqueId(), event.getName())))); + Try.run(() -> CompletableFuture.allOf(profileFutures.toArray(new CompletableFuture[0])).get(10, TimeUnit.SECONDS)) + .onSuccess(ignore -> Logging.finer("Preloaded data for Player{name:'%s', uuid:'%s'}. Time taken: %4.4f ms", + event.getName(), event.getUniqueId(), (System.nanoTime() - startTime) / 1000000.0)) + .onFailure(e -> Logging.warning("Preload data errored out: %s", e.getMessage())); + } + + /** + * Called when a player joins the server. + * + * @param event The player join event. + */ + @EventHandler + void playerJoin(final PlayerJoinEvent event) { + final Player player = event.getPlayer(); + // Just in case AsyncPlayerPreLoginEvent was still the old name + verifyCorrectPlayerName(player.getUniqueId(), player.getName()); + + final GlobalProfile globalProfile = FutureNow.get(profileDataSource.getGlobalProfile(GlobalProfileKey.of(player))); + if (globalProfile.shouldLoadOnLogin()) { + new ReadOnlyShareHandler(inventories, player).handleSharing(); + } + globalProfile.setLoadOnLogin(false); + verifyCorrectWorld(player, player.getWorld().getName(), globalProfile); + profileDataSource.updateGlobalProfile(globalProfile); + } + + private void verifyCorrectPlayerName(UUID uuid, String name) { + FutureNow.get(profileDataSource.getExistingGlobalProfile(GlobalProfileKey.of(uuid, name))).peek(globalProfile -> { + if (globalProfile.getLastKnownName().equals(name)) { + return; + } + + // Data must be migrated + Logging.info("Player %s changed name from '%s' to '%s'. Attempting to migrate playerdata...", + uuid, globalProfile.getLastKnownName(), name); + try { + profileDataSource.migratePlayerProfileName(globalProfile.getLastKnownName(), name); + } catch (IOException e) { + Logging.severe("An error occurred while trying to migrate playerdata."); + e.printStackTrace(); + } + globalProfile.setLastKnownName(name); + profileDataSource.updateGlobalProfile(globalProfile); + Logging.info("Migration complete!"); + }); + } + + /** + * Called when a player leaves the server. + * + * @param event The player quit event. + */ + @EventHandler + void playerQuit(final PlayerQuitEvent event) { + final Player player = event.getPlayer(); + final String world = event.getPlayer().getWorld().getName(); + + CompletableFuture globalProfile = profileDataSource.getGlobalProfile(GlobalProfileKey.of(player)); + globalProfile.thenAccept(p -> p.setLastWorld(world)); + + // Write last location as its possible for players to join at a different world + SingleShareWriter.of(this.inventories, player, Sharables.LAST_LOCATION).write(player.getLocation().clone()); + new WriteOnlyShareHandler(inventories, player).handleSharing(); + if (config.getApplyPlayerdataOnJoin()) { + globalProfile.thenAccept(p -> p.setLoadOnLogin(true)); + } + globalProfile.thenAccept(profileDataSource::updateGlobalProfile); + } + + private void verifyCorrectWorld(Player player, String world, GlobalProfile globalProfile) { + if (Strings.isNullOrEmpty(globalProfile.getLastWorld())) { + globalProfile.setLastWorld(world); + } else { + if (!world.equals(globalProfile.getLastWorld())) { + Logging.fine("Player did not spawn in the world they were last reported to be in!"); + new WorldChangeShareHandler(this.inventories, player, + globalProfile.getLastWorld(), world).handleSharing(); + globalProfile.setLastWorld(world); + } + } + } + + /** + * Called when a player changes game modes. + * + * @param event The game mode change event. + */ + @EventHandler(priority = EventPriority.MONITOR) + void playerGameModeChange(PlayerGameModeChangeEvent event) { + if (event.isCancelled() || !config.getEnableGamemodeShareHandling()) { + return; + } + Player player = event.getPlayer(); + SingleShareWriter.of(this.inventories, player, Sharables.LAST_LOCATION).write(player.getLocation().clone()); + new GameModeShareHandler(this.inventories, player, + player.getGameMode(), event.getNewGameMode()).handleSharing(); + } + + /** + * Called when a player changes worlds. + * + * @param event The world change event. + */ + @EventHandler(priority = EventPriority.LOW) + void playerChangedWorld(PlayerChangedWorldEvent event) { + Player player = event.getPlayer(); + World fromWorld = event.getFrom(); + World toWorld = player.getWorld(); + + // A precaution.. Will this ever be true? + if (fromWorld.equals(toWorld)) { + Logging.fine("PlayerChangedWorldEvent fired when player travelling in same world."); + return; + } + // Warn if not managed by Multiverse-Core + if (!this.worldManager.isLoadedWorld(toWorld) || !this.worldManager.isLoadedWorld(fromWorld)) { + Logging.fine("The from or to world is not managed by Multiverse-Core!"); + } + + new WorldChangeShareHandler(this.inventories, player, fromWorld.getName(), toWorld.getName()).handleSharing(); + profileDataSource.modifyGlobalProfile( + GlobalProfileKey.of(player), profile -> profile.setLastWorld(toWorld.getName())); + } + + /** + * Called when a player teleports. + * + * @param event The player teleport event. + */ + @EventHandler(priority = EventPriority.MONITOR) + void playerTeleport(PlayerTeleportEvent event) { + if (event.isCancelled() + || event.getFrom().getWorld().equals(event.getTo().getWorld()) + || !config.getActiveOptionalShares().contains(Sharables.LAST_LOCATION)) { + return; + } + + Player player = event.getPlayer(); + SingleShareWriter.of(this.inventories, player, Sharables.LAST_LOCATION).write(event.getFrom().clone()); + + // Possibly prevents item duping exploit + player.closeInventory(); + } + + /** + * Called when a player dies. + * + * @param event The player death event. + */ + @EventHandler(priority = EventPriority.MONITOR) + void playerDeath(PlayerDeathEvent event) { + Logging.finer("=== Handling PlayerDeathEvent for: " + event.getEntity().getName() + " ==="); + String deathWorld = event.getEntity().getWorld().getName(); + ProfileContainer worldProfileContainer = profileContainerStoreProvider.getStore(ContainerType.WORLD).getContainer(deathWorld); + PlayerProfile profile = worldProfileContainer.getPlayerProfileNow(event.getEntity()); + resetStatsOnDeath(event, profile); + for (WorldGroup worldGroup : worldGroupManager.getGroupsForWorld(deathWorld)) { + profile = worldGroup.getGroupProfileContainer().getPlayerProfileNow(event.getEntity()); + resetStatsOnDeath(event, profile); + } + Logging.finer("=== Finished handling PlayerDeathEvent for: " + event.getEntity().getName() + "! ==="); + } + + private void resetStatsOnDeath(PlayerDeathEvent event, PlayerProfile profile) { + profile.set(Sharables.LEVEL, event.getNewLevel()); + profile.set(Sharables.EXPERIENCE, (float) event.getNewExp()); + profile.set(Sharables.TOTAL_EXPERIENCE, event.getNewTotalExp()); + if (config.getResetLastLocationOnDeath()) { + profile.set(Sharables.LAST_LOCATION, null); + } + profileDataSource.updatePlayerProfile(profile); + } + + @EventHandler(priority = EventPriority.MONITOR) + void playerRespawn(PlayerRespawnEvent event) { + Location respawnLoc = event.getRespawnLocation(); + if (respawnLoc == null) { + // This probably only happens if a naughty plugin sets the location to null... + return; + } + final Player player = event.getPlayer(); + Bukkit.getScheduler().runTaskLater( + inventories, + () -> verifyCorrectWorld( + player, + player.getWorld().getName(), + FutureNow.get(profileDataSource.getGlobalProfile(GlobalProfileKey.of(player)))), + 2L); + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + void entityPortal(EntityPortalEvent event) { + Entity entity = event.getEntity(); + if (!(entity instanceof Item) && !(entity instanceof InventoryHolder)) { + return; + } + + World fromWorld = event.getFrom().getWorld(); + if (fromWorld == null) { + // Apparently this happens sometimes. + Logging.fine("Entity %s attempted to go from null world", entity); + return; + } + + Location toLocation = event.getTo(); + if (toLocation == null) { + // Apparently this happens sometimes. + Logging.fine("Entity %s attempted to go to null location", entity); + return; + } + World toWorld = toLocation.getWorld(); + if (toWorld == null) { + // Apparently this also happens sometimes. + Logging.fine("Entity %s attempted to go to null world", entity); + return; + } + + if (fromWorld.equals(toWorld)) { + return; + } + + List fromGroups = worldGroupManager.getGroupsForWorld(fromWorld.getName()); + List toGroups = worldGroupManager.getGroupsForWorld(toWorld.getName()); + // We only care about the groups that have the inventory sharable + fromGroups = fromGroups.stream().filter(it -> it.isSharing(Sharables.INVENTORY)).toList(); + toGroups = toGroups.stream().filter(it -> it.isSharing(Sharables.INVENTORY)).toList(); + for (WorldGroup fromGroup : fromGroups) { + if (toGroups.contains(fromGroup)) { + Logging.finest("Allowing item or inventory holding %s to go from world %s to world %s", entity, + fromWorld.getName(), toWorld.getName()); + // The from and to destinations share at least one group that has the inventory sharable. + return; + } + } + + Logging.finest("Disallowing item or inventory holding %s to go from world %s to world %s since these" + + "worlds do not share inventories", entity, fromWorld.getName(), toWorld.getName()); + event.setCancelled(true); + } + + @EventHandler + void worldUnload(WorldUnloadEvent event) { + String unloadWorldName = event.getWorld().getName(); + + Logging.finer("Clearing data for world/groups container with '%s' world.", unloadWorldName); + + ProfileContainer fromWorldProfileContainer = profileContainerStoreProvider.getStore(ContainerType.WORLD) + .getContainer(unloadWorldName); + fromWorldProfileContainer.clearContainerCache(); + + List fromGroups = worldGroupManager.getGroupsForWorld(unloadWorldName); + for (WorldGroup fromGroup : fromGroups) { + fromGroup.getGroupProfileContainer().clearContainerCache(); + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java new file mode 100644 index 00000000..e2137649 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java @@ -0,0 +1,122 @@ +package org.mvplugins.multiverse.inventories.handleshare; + +import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.event.ShareHandlingEvent; +import org.mvplugins.multiverse.inventories.profile.data.ProfileDataSnapshot; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStore; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +/** + * Abstract class for handling sharing of data between worlds and game modes. + */ +sealed abstract class ShareHandler permits GameModeShareHandler, ReadOnlyShareHandler, WorldChangeShareHandler, WriteOnlyShareHandler { + + protected final Player player; + protected final AffectedProfiles affectedProfiles; + + protected final MultiverseInventories inventories; + protected final ProfileDataSource profileDataStore; + protected final InventoriesConfig inventoriesConfig; + protected final WorldGroupManager worldGroupManager; + protected final ProfileContainerStore worldProfileContainerStore; + + ShareHandler(MultiverseInventories inventories, Player player) { + this.player = player; + this.affectedProfiles = new AffectedProfiles(); + + this.inventories = inventories; + this.profileDataStore = inventories.getServiceLocator().getService(ProfileDataSource.class); + this.inventoriesConfig = inventories.getServiceLocator().getService(InventoriesConfig.class); + this.worldGroupManager = inventories.getServiceLocator().getService(WorldGroupManager.class); + this.worldProfileContainerStore = inventories.getServiceLocator() + .getService(ProfileContainerStoreProvider.class) + .getStore(ContainerType.WORLD); + } + + /** + * Finalizes the transfer from one world to another. This handles the switching + * inventories/stats for a player and persisting the changes. + */ + public final void handleSharing() { + long startTime = System.nanoTime(); + this.prepareProfiles(); + ShareHandlingEvent event = this.createEvent(); + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) { + Logging.fine("Share handling has been cancelled by another plugin!"); + return; + } + logAffectedProfilesCount(); + ProfileDataSnapshot snapshot = getSnapshot(); + updatePlayer(); + updateProfiles(snapshot); + double timeTaken = (System.nanoTime() - startTime) / 1000000.0; + logHandlingComplete(timeTaken, event); + } + + protected abstract void prepareProfiles(); + + protected abstract ShareHandlingEvent createEvent(); + + protected void logBypass() { + Logging.fine(player.getName() + " has bypass permission for 1 or more world/groups!"); + } + + private void logAffectedProfilesCount() { + int writeProfiles = affectedProfiles.getWriteProfiles().size(); + + Logging.finer("Change affected by %d fromProfiles and %d toProfiles", writeProfiles, + affectedProfiles.getReadProfiles().size()); + } + + private ProfileDataSnapshot getSnapshot() { + ProfileDataSnapshot profileDataSnapshot = new ProfileDataSnapshot(); + Sharables.enabled().forEach(sharable -> sharable.getHandler().updateProfile(profileDataSnapshot, player)); + return profileDataSnapshot; + } + + private void updatePlayer() { + for (PersistingProfile readProfile : affectedProfiles.getReadProfiles()) { + ShareHandlingUpdater.updatePlayer(inventories, player, readProfile); + } + } + + private void updateProfiles(ProfileDataSnapshot snapshot) { + if (affectedProfiles.getWriteProfiles().isEmpty()) { + Logging.finest("No profiles to write - nothing more to do."); + return; + } + for (PersistingProfile writeProfile : affectedProfiles.getWriteProfiles()) { + updatePersistingProfile(writeProfile, snapshot); + } + } + + private void updatePersistingProfile(PersistingProfile persistingProfile, ProfileDataSnapshot snapshot) { + if (persistingProfile.getShares().isEmpty()) { + Logging.finest("No shares to write - nothing more to do."); + return; + } + profileDataStore.getPlayerProfile(persistingProfile.getProfileKey()) + .thenCompose(playerProfile -> { + Logging.finer("Persisted: " + persistingProfile.getShares() + " to " + + playerProfile.getContainerType() + ":" + playerProfile.getContainerName() + + " (" + playerProfile.getProfileType() + ")" + + " for player " + playerProfile.getPlayerName()); + playerProfile.update(snapshot, persistingProfile.getShares()); + return profileDataStore.updatePlayerProfile(playerProfile); + }); + } + + private void logHandlingComplete(double timeTaken, ShareHandlingEvent event) { + Logging.fine("=== %s complete for %s | \u001B[32mtime taken: %4.4f ms\u001B[0m ===", + player.getName(), event.getEventName(), timeTaken); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java new file mode 100644 index 00000000..9439f55c --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdater.java @@ -0,0 +1,93 @@ +package org.mvplugins.multiverse.inventories.handleshare; + +import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.external.vavr.control.Try; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.bukkit.entity.Player; +import org.mvplugins.multiverse.inventories.util.FutureNow; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public final class ShareHandlingUpdater { + + public static CompletableFuture updateProfile(final MultiverseInventories inventories, + final Player player, + final PersistingProfile profile) { + return new ShareHandlingUpdater(inventories, player, profile).updateProfile(); + } + + public static void updatePlayer(final MultiverseInventories inventories, + final Player player, + final PersistingProfile profile) { + new ShareHandlingUpdater(inventories, player, profile).updatePlayer(); + } + + private final Player player; + private final PersistingProfile profile; + private final ProfileDataSource profileDataSource; + + private ShareHandlingUpdater(MultiverseInventories inventories, Player player, PersistingProfile profile) { + this.player = player; + this.profile = profile; + this.profileDataSource = inventories.getServiceLocator().getService(ProfileDataSource.class); + } + + private CompletableFuture updateProfile() { + if (profile.getShares().isEmpty()) { + return CompletableFuture.completedFuture(null); + } + return profileDataSource.getPlayerProfile(profile.getProfileKey()) + .thenCompose(playerProfile -> { + for (Sharable sharable : profile.getShares()) { + sharable.getHandler().updateProfile(playerProfile, player); + } + Logging.finer("Persisted: " + profile.getShares() + " to " + + playerProfile.getContainerType() + ":" + playerProfile.getContainerName() + + " (" + playerProfile.getProfileType() + ")" + + " for player " + playerProfile.getPlayerName()); + return profileDataSource.updatePlayerProfile(playerProfile); + }) + .exceptionally(throwable -> { + Logging.severe("Could not persist profile for player: %s. %s", + player.getName(), throwable.getMessage()); + return null; + }); + } + + private void updatePlayer() { + if (profile.getShares().isEmpty()) { + return; + } + player.closeInventory(); + Try.of(() -> FutureNow.get(profileDataSource.getPlayerProfile(profile.getProfileKey()))) + .peek(playerProfile -> { + List> loaded = new ArrayList<>(profile.getShares().size()); + List> defaulted = new ArrayList<>(profile.getShares().size()); + + for (Sharable sharable : profile.getShares()) { + if (sharable.getHandler().updatePlayer(player, playerProfile)) { + loaded.add(sharable); + } else { + defaulted.add(sharable); + } + } + if (!loaded.isEmpty()) { + Logging.finer("Updated: " + loaded + " for " + + playerProfile.getPlayerName() + " for " + + playerProfile.getContainerType() + ":" + playerProfile.getContainerName() + + " (" + playerProfile.getProfileType() + ")"); + } + if (!defaulted.isEmpty()) { + Logging.finer("Defaulted: " + defaulted + " for " + + playerProfile.getPlayerName() + " for " + + playerProfile.getContainerType() + ":" + playerProfile.getContainerName() + + " (" + playerProfile.getProfileType() + ")"); + } + }) + .onFailure(e -> Logging.severe("Error getting playerdata: " + e.getMessage()));; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareReader.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareReader.java new file mode 100644 index 00000000..c4908d5f --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareReader.java @@ -0,0 +1,59 @@ +package org.mvplugins.multiverse.inventories.handleshare; + +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.share.Sharable; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public final class SingleShareReader { + + public static SingleShareReader of(MultiverseInventories inventories, Player player, Sharable sharable) { + return new SingleShareReader<>(inventories, player, player.getWorld().getName(), ProfileTypes.forPlayer(player), sharable); + } + + public static SingleShareReader of(MultiverseInventories inventories, OfflinePlayer player, String worldName, ProfileType profileType, Sharable sharable) { + return new SingleShareReader<>(inventories, player, worldName, profileType, sharable); + } + + private final MultiverseInventories inventories; + private final OfflinePlayer player; + private final String worldName; + private final ProfileType profileType; + private final Sharable sharable; + + public SingleShareReader(MultiverseInventories inventories, OfflinePlayer player, String worldName, ProfileType profileType, Sharable sharable) { + this.inventories = inventories; + this.player = player; + this.worldName = worldName; + this.profileType = profileType; + this.sharable = sharable; + } + + public CompletableFuture read() { + WorldGroupManager worldGroupManager = inventories.getServiceLocator().getService(WorldGroupManager.class); + List worldGroups = worldGroupManager.getGroupsForWorld(worldName); + for (WorldGroup worldGroup : worldGroups) { + if (worldGroup.isSharing(sharable)) { + return getSharableFromProfile(ContainerType.GROUP, worldGroup.getName()); + } + } + return getSharableFromProfile(ContainerType.WORLD, worldName); + } + + private CompletableFuture getSharableFromProfile(ContainerType containerType, String containerName) { + return this.inventories.getServiceLocator().getService(ProfileContainerStoreProvider.class) + .getStore(containerType) + .getContainer(containerName) + .getPlayerData(profileType, player) + .thenApply(playerProfile -> playerProfile.get(sharable)); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java new file mode 100644 index 00000000..1d0c3c9e --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SingleShareWriter.java @@ -0,0 +1,92 @@ +package org.mvplugins.multiverse.inventories.handleshare; + +import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.share.Sharable; + +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +/** + * Write a single share to the relevant world and group profiles. + * + * @param The sharable type. + */ +public final class SingleShareWriter { + + public static SingleShareWriter of(MultiverseInventories inventories, Player player, Sharable sharable) { + return new SingleShareWriter<>(inventories, player, player.getWorld().getName(), ProfileTypes.forPlayer(player), sharable); + } + + public static SingleShareWriter of(MultiverseInventories inventories, OfflinePlayer player, String worldName, ProfileType profileType, Sharable sharable) { + return new SingleShareWriter<>(inventories, player, worldName, profileType, sharable); + } + + private final MultiverseInventories inventories; + private final OfflinePlayer player; + private final String worldName; + private final ProfileType profileType; + private final Sharable sharable; + private final ProfileDataSource profileDataSource; + + private SingleShareWriter(MultiverseInventories inventories, OfflinePlayer player, String worldName, ProfileType profileType, Sharable sharable) { + this.inventories = inventories; + this.player = player; + this.worldName = worldName; + this.profileType = profileType; + this.sharable = sharable; + this.profileDataSource = inventories.getServiceLocator().getService(ProfileDataSource.class); + } + + public void write(T value) { + write(value, false); + } + + public CompletableFuture write(T value, boolean save) { + if (sharable.isOptional() && + !inventories.getServiceLocator().getService(InventoriesConfig.class).getActiveOptionalShares().contains(sharable)) { + Logging.finer("Skipping write for optional share: " + sharable); + return CompletableFuture.completedFuture(null); + } + Logging.finer("Writing single share: " + sharable.getNames()[0]); + var profileContainerStoreProvider = this.inventories.getServiceLocator().getService(ProfileContainerStoreProvider.class); + profileContainerStoreProvider.getStore(ContainerType.WORLD) + .getContainer(worldName) + .getPlayerData(profileType, this.player) + .thenAccept(profile -> writeNewValueToProfile(profile, value, save)); + + return CompletableFuture.allOf(this.inventories.getServiceLocator().getService(WorldGroupManager.class) + .getGroupsForWorld(worldName) + .stream() + .map(worldGroup -> { + if (!worldGroup.getApplicableShares().contains(sharable)) { + return CompletableFuture.completedFuture(null); + } + return worldGroup.getGroupProfileContainer().getPlayerData(profileType, this.player) + .thenCompose(profile -> writeNewValueToProfile(profile, value, save)); + }) + .toArray(CompletableFuture[]::new)); + } + + private CompletableFuture writeNewValueToProfile(PlayerProfile profile, T value, boolean save) { + if (Objects.equals(profile.get(sharable), value)) { + return CompletableFuture.completedFuture(null); + } + Logging.finest("Writing %s value: %s for profile %s", sharable, value, profile); + profile.set(sharable, value); + if (save) { + return profileDataSource.updatePlayerProfile(profile); + } + return CompletableFuture.completedFuture(null); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SpawnChangeListener.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SpawnChangeListener.java new file mode 100644 index 00000000..fcad9fae --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/SpawnChangeListener.java @@ -0,0 +1,48 @@ +package org.mvplugins.multiverse.inventories.handleshare; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerSpawnChangeEvent; +import org.bukkit.event.player.PlayerSpawnChangeEvent.Cause; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.share.Sharables; + +import static org.mvplugins.multiverse.inventories.util.MinecraftTools.findAnchorFromRespawnLocation; +import static org.mvplugins.multiverse.inventories.util.MinecraftTools.findBedFromRespawnLocation; + +/** + * Handles player spawn location changes for BED_SPAWN sharable. + */ +public final class SpawnChangeListener implements Listener { + + private final MultiverseInventories inventories; + + public SpawnChangeListener(MultiverseInventories inventories) { + this.inventories = inventories; + } + + @EventHandler(priority = EventPriority.MONITOR) + void onSpawnChange(PlayerSpawnChangeEvent event) { + if (Sharables.isIgnoringSpawnListener(event.getPlayer())) { + return; + } + Player player = event.getPlayer(); + if (event.getCause() == Cause.BED) { + updatePlayerSpawn(player, findBedFromRespawnLocation(event.getNewSpawn())); + return; + } + if (event.getCause() == Cause.RESPAWN_ANCHOR) { + updatePlayerSpawn(player, findAnchorFromRespawnLocation(event.getNewSpawn())); + return; + } + updatePlayerSpawn(player, event.getNewSpawn()); + } + + private void updatePlayerSpawn(Player player, Location location) { + SingleShareWriter.of(this.inventories, player, Sharables.BED_SPAWN) + .write(location == null ? null : location.clone(), true); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java new file mode 100644 index 00000000..c6bdd788 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeShareHandler.java @@ -0,0 +1,186 @@ +package org.mvplugins.multiverse.inventories.handleshare; + +import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.event.ShareHandlingEvent; +import org.mvplugins.multiverse.inventories.event.WorldChangeShareHandlingEvent; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; +import org.mvplugins.multiverse.inventories.util.Perm; +import org.bukkit.entity.Player; + +import java.util.List; + +/** + * WorldChange implementation of ShareHandler. + */ +final class WorldChangeShareHandler extends ShareHandler { + + private final String fromWorld; + private final String toWorld; + private final List fromWorldGroups; + private final List toWorldGroups; + + WorldChangeShareHandler(MultiverseInventories inventories, Player player, String fromWorld, String toWorld) { + super(inventories, player); + this.fromWorld = fromWorld; + this.toWorld = toWorld; + + // Get any groups we may need to save stuff to. + this.fromWorldGroups = worldGroupManager.getGroupsForWorld(fromWorld); + // Get any groups we may need to load stuff from. + this.toWorldGroups = worldGroupManager.getGroupsForWorld(toWorld); + } + + @Override + protected ShareHandlingEvent createEvent() { + return new WorldChangeShareHandlingEvent(player, affectedProfiles, fromWorld, toWorld); + } + + @Override + protected void prepareProfiles() { + Logging.fine("=== %s traveling from world: %s to world: %s ===", player.getName(), fromWorld, toWorld); + if (isPlayerAffectedByChange()) { + addWriteProfiles(); + addReadProfiles(); + } else if (inventoriesConfig.getAlwaysWriteWorldProfile()) { + // Write to world profile to ensure data is saved incase bypass is removed + affectedProfiles.addWriteProfile( + worldProfileContainerStore.getContainer(fromWorld).getProfileKey(player), + (fromWorldGroups.isEmpty() && !inventoriesConfig.getUseOptionalsForUngroupedWorlds()) + ? Sharables.standard() + : Sharables.enabled() + ); + } + } + + private boolean isPlayerAffectedByChange() { + if (isPlayerBypassingChange()) { + logBypass(); + return false; + } + return true; + } + + private boolean isPlayerBypassingChange() { + return Perm.BYPASS_WORLD.hasBypass(player, fromWorld); + } + + private void addWriteProfiles() { + new WriteProfileAggregator().conditionallyAddWriteProfiles(); + } + + private void addReadProfiles() { + new ReadProfilesAggregator().addReadProfiles(); + } + + private class ReadProfilesAggregator { + + private final Shares handledShares = Sharables.noneOf(); + + private void addReadProfiles() { + addReadProfilesFromToWorldGroups(); + useToWorldForMissingShares(); + } + + private void addReadProfilesFromToWorldGroups() { + if (toWorldGroups.isEmpty()) { + Logging.finer("No groups for toWorld."); + return; + } + toWorldGroups.forEach(this::conditionallyAddReadProfileForWorldGroup); + } + + private void conditionallyAddReadProfileForWorldGroup(WorldGroup worldGroup) { + if (!isPlayerAffectedByChange(worldGroup)) { + return; + } + if (isFromWorldNotInToWorldGroup(worldGroup)) { + addReadProfileForWorldGroup(worldGroup); + } + handledShares.addAll(worldGroup.getApplicableShares()); + handledShares.addAll(worldGroup.getDisabledShares()); + } + + private boolean isPlayerAffectedByChange(WorldGroup worldGroup) { + if (isPlayerBypassingChange(worldGroup)) { + logBypass(); + return false; + } + return true; + } + + private boolean isPlayerBypassingChange(WorldGroup worldGroup) { + return Perm.BYPASS_GROUP.hasBypass(player, worldGroup.getName()); + } + + private boolean isFromWorldNotInToWorldGroup(WorldGroup worldGroup) { + if (inventoriesConfig.getDefaultUngroupedWorlds() + && !worldGroupManager.hasConfiguredGroup(fromWorld) + && worldGroup.equals(worldGroupManager.getDefaultGroup())) { + return false; + } + return !worldGroup.containsWorld(fromWorld); + } + + private void addReadProfileForWorldGroup(WorldGroup worldGroup) { + Shares applicableShares = Sharables.fromShares(worldGroup.getApplicableShares()); + if (!inventoriesConfig.getApplyLastLocationForAllTeleports()) { + Logging.finer("Removing lastLocation from applicableShares as it is not applied for all teleports"); + applicableShares.remove(Sharables.LAST_LOCATION); + } + affectedProfiles.addReadProfile(worldGroup.getGroupProfileContainer().getProfileKey(player), applicableShares); + } + + private void useToWorldForMissingShares() { + // We need to fill in any sharables that are not going to be transferred with what's saved in the world file. + Shares unhandledShares = (toWorldGroups.isEmpty() && !inventoriesConfig.getUseOptionalsForUngroupedWorlds()) + ? Sharables.standardOf() : Sharables.enabledOf(); + unhandledShares.removeAll(handledShares); + if (!inventoriesConfig.getApplyLastLocationForAllTeleports()) { + Logging.finer("Removing lastLocation from unhandledShares as it is not applied for all teleports"); + unhandledShares.remove(Sharables.LAST_LOCATION); + } + if (unhandledShares.isEmpty()) { + return; + } + Logging.finer("%s are left unhandled, defaulting to toWorld", unhandledShares); + affectedProfiles.addReadProfile( + worldProfileContainerStore.getContainer(toWorld).getProfileKey(player), + unhandledShares + ); + } + } + + private class WriteProfileAggregator { + + private final Shares handledShares = Sharables.noneOf(); + + private void conditionallyAddWriteProfiles() { + fromWorldGroups.forEach(this::conditionallyAddWriteProfileForGroup); + Shares sharesToWrite = inventoriesConfig.getAlwaysWriteWorldProfile() + ? Sharables.enabled() + : Sharables.enabledOf().setSharing(handledShares, false); + if (!sharesToWrite.isEmpty()) { + affectedProfiles.addWriteProfile( + worldProfileContainerStore.getContainer(fromWorld).getProfileKey(player), + sharesToWrite); + } + } + + private void conditionallyAddWriteProfileForGroup(WorldGroup worldGroup) { + if (!worldGroup.containsWorld(toWorld)) { + addWriteProfileForGroup(worldGroup); + } + handledShares.addAll(worldGroup.getApplicableShares()); + handledShares.addAll(worldGroup.getDisabledShares()); + } + + void addWriteProfileForGroup(WorldGroup worldGroup) { + ProfileContainer container = worldGroup.getGroupProfileContainer(); + affectedProfiles.addWriteProfile(container.getProfileKey(player), worldGroup.getApplicableShares()); + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WriteOnlyShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WriteOnlyShareHandler.java new file mode 100644 index 00000000..9eb66032 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/WriteOnlyShareHandler.java @@ -0,0 +1,57 @@ +package org.mvplugins.multiverse.inventories.handleshare; + +import org.bukkit.entity.Player; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.event.ShareHandlingEvent; +import org.mvplugins.multiverse.inventories.event.WriteOnlyShareHandlingEvent; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; + +import java.util.List; + +public final class WriteOnlyShareHandler extends ShareHandler { + + private final String worldName; + private final ProfileType profileType; + + public WriteOnlyShareHandler(MultiverseInventories inventories, Player player) { + this(inventories, player, player.getWorld().getName(), ProfileTypes.forPlayer(player)); + } + + public WriteOnlyShareHandler(MultiverseInventories inventories, Player player, String worldName, ProfileType profileType) { + super(inventories, player); + this.worldName = worldName; + this.profileType = profileType; + } + + @Override + protected void prepareProfiles() { + List worldGroups = worldGroupManager.getGroupsForWorld(worldName); + + Shares unhandledShares = Sharables.enabledOf(); + for (WorldGroup worldGroup : worldGroups) { + affectedProfiles.addWriteProfile( + worldGroup.getGroupProfileContainer().getProfileKey(profileType, player), + worldGroup.getApplicableShares() + ); + unhandledShares.removeAll(worldGroup.getApplicableShares()); + } + Shares sharesToWrite = inventoriesConfig.getAlwaysWriteWorldProfile() + ? Sharables.enabled() + : unhandledShares; + if (!sharesToWrite.isEmpty()) { + affectedProfiles.addWriteProfile( + worldProfileContainerStore.getContainer(worldName).getProfileKey(profileType, player), + sharesToWrite + ); + } + } + + @Override + protected ShareHandlingEvent createEvent() { + return new WriteOnlyShareHandlingEvent(player, affectedProfiles); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/package-info.java new file mode 100644 index 00000000..9d808b6e --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/package-info.java @@ -0,0 +1 @@ +package org.mvplugins.multiverse.inventories.handleshare; \ No newline at end of file diff --git a/src/main/java/com/onarandombox/multiverseinventories/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/package-info.java similarity index 54% rename from src/main/java/com/onarandombox/multiverseinventories/package-info.java rename to src/main/java/org/mvplugins/multiverse/inventories/package-info.java index e36f6040..e0176883 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/package-info.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/package-info.java @@ -1,5 +1,5 @@ /** * The main package for Multiverse-Inventories. */ -package com.onarandombox.multiverseinventories; +package org.mvplugins.multiverse.inventories; diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/AsyncFileIO.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/AsyncFileIO.java new file mode 100644 index 00000000..267aab66 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/AsyncFileIO.java @@ -0,0 +1,89 @@ +package org.mvplugins.multiverse.inventories.profile; + +import com.dumptruckman.minecraft.util.Logging; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.vavr.control.Try; + +import java.io.File; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Supplier; + +@Service +final class AsyncFileIO { + + private final ExecutorService fileIOExecutorService = Executors.newWorkStealingPool(); + private final Map fileLocks = new ConcurrentHashMap<>(); + + CompletableFuture queueAction(Runnable action) { + CompletableFuture future = new CompletableFuture<>(); + fileIOExecutorService.submit(() -> { + Try.runRunnable(action) + .onFailure(future::completeExceptionally) + .onSuccess(ignore -> future.complete(null)); + }); + return future; + } + + CompletableFuture queueCallable(Supplier supplier) { + CompletableFuture future = new CompletableFuture<>(); + fileIOExecutorService.submit(() -> { + Try.ofSupplier(supplier) + .onFailure(future::completeExceptionally) + .onSuccess(future::complete); + }); + return future; + } + + CompletableFuture queueFileAction(File file, Runnable action) { + CountDownLatch thisLatch = new CountDownLatch(1); + CountDownLatch toWaitLatch = fileLocks.put(file, thisLatch); + CompletableFuture future = new CompletableFuture<>(); + fileIOExecutorService.submit(() -> { + waitForLock(file, toWaitLatch); + Try tryResult = Try.runRunnable(action); + fileLocks.remove(file); + thisLatch.countDown(); + tryResult.onFailure(future::completeExceptionally).onSuccess(ignore -> future.complete(null)); + }); + return future; + } + + CompletableFuture queueFileCallable(File file, Supplier supplier) { + CountDownLatch thisLatch = new CountDownLatch(1); + CountDownLatch toWaitLatch = fileLocks.put(file, thisLatch); + CompletableFuture future = new CompletableFuture<>(); + fileIOExecutorService.submit(() -> { + waitForLock(file, toWaitLatch); + Try tryResult = Try.ofSupplier(supplier); + fileLocks.remove(file); + thisLatch.countDown(); + tryResult.onFailure(future::completeExceptionally).onSuccess(future::complete); + }); + return future; + } + + private void waitForLock(File file, CountDownLatch toWaitLatch) { + if (toWaitLatch != null && toWaitLatch.getCount() > 0) { + try { + Logging.finest("Waiting for lock on " + file); + toWaitLatch.await(10, TimeUnit.SECONDS); + Logging.finest("Aquired lock on " + file); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + + ExecutorService getExecutor() { + return fileIOExecutorService; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java new file mode 100644 index 00000000..1b20796a --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/FlatFileProfileDataSource.java @@ -0,0 +1,409 @@ +package org.mvplugins.multiverse.inventories.profile; + +import com.dumptruckman.bukkit.configuration.json.JsonConfiguration; +import com.dumptruckman.minecraft.util.Logging; +import com.google.common.base.Strings; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.exceptions.MultiverseException; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.vavr.control.Option; +import org.mvplugins.multiverse.external.vavr.control.Try; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileFileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +@Service +final class FlatFileProfileDataSource implements ProfileDataSource { + + private final AsyncFileIO asyncFileIO; + private final ProfileFilesLocator profileFilesLocator; + private final ProfileCacheManager profileCacheManager; + private final PlayerNamesMapper playerNamesMapper; + + @Inject + FlatFileProfileDataSource( + @NotNull AsyncFileIO asyncFileIO, + @NotNull ProfileFilesLocator profileFilesLocator, + @NotNull ProfileCacheManager profileCacheManager, + @NotNull PlayerNamesMapper playerNamesMapper + ) { + this.asyncFileIO = asyncFileIO; + this.profileFilesLocator = profileFilesLocator; + this.profileCacheManager = profileCacheManager; + this.playerNamesMapper = playerNamesMapper; + } + + private FileConfiguration loadFileToJsonConfiguration(File file) { + JsonConfiguration jsonConfiguration = new JsonConfiguration(); + jsonConfiguration.options().continueOnSerializationError(false); + Try.run(() -> jsonConfiguration.load(file)).getOrElseThrow(e -> { + Logging.severe("Could not load file %s : %s", file, e.getMessage()); + e.printStackTrace(); + throw new RuntimeException(e); + }); + return jsonConfiguration; + } + + private FileConfiguration getOrLoadPlayerProfileFile(ProfileFileKey profileKey, File playerFile) { + ProfileKey fileProfileKey = profileKey.forProfileType(null); + return Try.of(() -> + profileCacheManager.getOrLoadPlayerFile(fileProfileKey, (key) -> playerFile.exists() + ? loadFileToJsonConfiguration(playerFile) + : new JsonConfiguration()) + ).getOrElseThrow(e -> { + Logging.severe("Could not load profile data for player: " + fileProfileKey); + return new RuntimeException(e); + }); + } + + /** + * {@inheritDoc} + */ + @Override + public CompletableFuture getPlayerProfile(ProfileKey profileKey) { + try { + if (Strings.isNullOrEmpty(profileKey.getPlayerName())) { + return CompletableFuture.failedFuture(new IllegalArgumentException("Player name cannot be null or empty. " + profileKey)); + } + return profileCacheManager.getOrLoadPlayerProfile(profileKey, (key, executor) -> { + File playerFile = profileFilesLocator.getPlayerProfileFile(profileKey); + if (!playerFile.exists()) { + Logging.fine("Not found on disk: %s", playerFile); + return CompletableFuture.completedFuture(PlayerProfile.newProfile(key)); + } + Logging.finer("%s not cached. loading from disk...", profileKey); + return asyncFileIO.queueFileCallable(playerFile, () -> getPlayerProfileFromDisk(key, playerFile)); + }); + } catch (Exception e) { + Logging.severe("Could not get data for player: " + profileKey.getPlayerName() + + " for " + profileKey.getContainerType().toString() + ": " + profileKey.getDataName()); + throw new RuntimeException(e); + } + } + + private PlayerProfile getPlayerProfileFromDisk(ProfileKey key, File playerFile) { + FileConfiguration playerData = getOrLoadPlayerProfileFile(key, playerFile); + ConfigurationSection section = playerData.getConfigurationSection(key.getProfileType().getName()); + if (section == null || section.getKeys(false).isEmpty()) { + return PlayerProfile.newProfile(key); + } + return PlayerProfileJsonSerializer.deserialize(key, convertSection(section)); + } + + private Map convertSection(ConfigurationSection section) { + Set keys = section.getKeys(false); + Map resultMap = new HashMap<>(keys.size()); + for (String key : keys) { + Object obj = section.get(key); + if (obj instanceof ConfigurationSection) { + resultMap.put(key, convertSection((ConfigurationSection) obj)); + } else { + resultMap.put(key, obj); + } + } + return resultMap; + } + + /** + * {@inheritDoc} + */ + @Override + public CompletableFuture updatePlayerProfile(PlayerProfile playerProfile) { + ProfileKey profileKey = ProfileKey.fromPlayerProfile(playerProfile); + File playerFile = profileFilesLocator.getPlayerProfileFile(profileKey); + return asyncFileIO.queueFileAction( + playerFile, + () -> savePlayerProfileToDisk(profileKey, playerFile, playerProfile.clone()) + ); + } + + private void savePlayerProfileToDisk(ProfileKey profileKey, File playerFile, PlayerProfile playerProfile) { + FileConfiguration playerData = getOrLoadPlayerProfileFile(profileKey, playerFile); + Map serializedData = PlayerProfileJsonSerializer.serialize(playerProfile); + if (serializedData.isEmpty()) { + return; + } + playerData.createSection(playerProfile.getProfileType().getName(), serializedData); + Try.run(() -> playerData.save(playerFile)).onFailure(e -> { + Logging.severe("Could not save data for player: " + playerProfile.getPlayerName() + + " for " + playerProfile.getContainerType() + ": " + playerProfile.getContainerName()); + e.printStackTrace(); + }); + } + + /** + * {@inheritDoc} + */ + @Override + public CompletableFuture deletePlayerProfile(ProfileKey profileKey) { + if (Strings.isNullOrEmpty(profileKey.getPlayerName())) { + return CompletableFuture.failedFuture(new IllegalArgumentException("Player name cannot be null or empty. " + profileKey)); + } + File playerFile = profileFilesLocator.getPlayerProfileFile(profileKey); + profileCacheManager.getCachedPlayerProfile(profileKey).peek(profile -> profile.getData().clear()); + return asyncFileIO.queueFileAction(playerFile, () -> + deletePlayerProfileFromDisk(profileKey, playerFile, new ProfileType[]{profileKey.getProfileType()})); + } + + /** + * {@inheritDoc} + */ + @Override + public CompletableFuture deletePlayerProfiles(ProfileFileKey profileKey, ProfileType[] profileTypes) { + if (Strings.isNullOrEmpty(profileKey.getPlayerName())) { + return CompletableFuture.failedFuture(new IllegalArgumentException("Player name cannot be null or empty. " + profileKey)); + } + if (ProfileTypes.isAll(profileTypes)) { + Logging.finer("Deleting profile: " + profileKey + " for all profile-types"); + return deletePlayerFile(profileKey); + } + for (var profileType : profileTypes) { + profileCacheManager.getCachedPlayerProfile(profileKey.forProfileType(profileType)) + .peek(profile -> profile.getData().clear()); + } + File playerFile = profileFilesLocator.getPlayerProfileFile(profileKey); + return asyncFileIO.queueFileAction(playerFile, () -> + deletePlayerProfileFromDisk(profileKey, playerFile, profileTypes)); + } + + private void deletePlayerProfileFromDisk(ProfileFileKey profileKey, File playerFile, ProfileType[] profileTypes) { + try { + FileConfiguration playerData = getOrLoadPlayerProfileFile(profileKey, playerFile); + for (var profileType : profileTypes) { + playerData.set(profileType.getName(), null); + } + playerData.save(playerFile); + } catch (IOException e) { + Logging.severe("Could not delete data for player: " + profileKey.getPlayerName() + + " for " + profileKey.getContainerType() + ": " + profileKey.getDataName()); + Logging.severe(e.getMessage()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public CompletableFuture deletePlayerFile(ProfileFileKey profileKey) { + for (var type : ProfileTypes.getTypes()) { + profileCacheManager.getCachedPlayerProfile(profileKey.forProfileType(type)) + .peek(profile -> profile.getData().clear()); + } + File playerFile = profileFilesLocator.getPlayerProfileFile(profileKey); + if (!playerFile.exists()) { + Logging.finer("Attempted to delete file that did not exist for player " + profileKey.getPlayerName() + + " in " + profileKey.getContainerType() + " " + profileKey.getDataName()); + return CompletableFuture.completedFuture(null); + } + return asyncFileIO.queueFileAction(playerFile, () -> { + if (!playerFile.delete()) { + Logging.warning("Could not delete file for player " + profileKey.getPlayerName() + + " in " + profileKey.getContainerType() + " " + profileKey.getDataName()); + } + }); + } + + /** + * {@inheritDoc} + */ + @Override + public void migratePlayerProfileName(String oldName, String newName) { + profileCacheManager.clearPlayerCache(oldName); + + List worldFolders = profileFilesLocator.listProfileContainerFolders(ContainerType.WORLD); + List groupFolders = profileFilesLocator.listProfileContainerFolders(ContainerType.GROUP); + + migrateForContainerType(worldFolders, ContainerType.WORLD, oldName, newName); + migrateForContainerType(groupFolders, ContainerType.GROUP, oldName, newName); + } + + private void migrateForContainerType(List folders, ContainerType containerType, String oldName, String newName) { + for (File folder : folders) { + File oldNameFile = profileFilesLocator.getPlayerProfileFile(containerType, folder.getName(), oldName); + File newNameFile = profileFilesLocator.getPlayerProfileFile(containerType, folder.getName(), newName); + if (!oldNameFile.exists()) { + Logging.fine("No old data for player %s in %s %s to migrate.", + oldName, containerType.name(), folder.getName()); + continue; + } + if (newNameFile.exists()) { + Logging.warning("Data already exists for player %s in %s %s. Not migrating.", + newName, containerType.name(), folder.getName()); + continue; + } + if (!oldNameFile.renameTo(newNameFile)) { + Logging.warning("Could not rename old data file for player %s in %s %s to %s.", + oldName, containerType.name(), folder.getName(), newName); + continue; + } + Logging.fine("Migrated data for player %s in %s %s to %s.", + oldName, containerType.name(), folder.getName(), newName); + } + } + + /** + * {@inheritDoc} + */ + @Override + public CompletableFuture getGlobalProfile(GlobalProfileKey key) { + File globalFile = profileFilesLocator.getGlobalFile(key.getPlayerUUID()); + return profileCacheManager.getOrLoadGlobalProfile(key.getPlayerUUID(), (uuid, executor) -> { + Logging.finer("Global profile for player %s (%s) not in cached. Loading...", uuid, key.getPlayerName()); + migrateGlobalProfileToUUID(uuid, key.getPlayerName()); + if (!globalFile.exists()) { + GlobalProfile globalProfile = new GlobalProfile(key.getPlayerUUID(), globalFile.toPath()); + globalProfile.setLastKnownName(key.getPlayerName()); + return CompletableFuture.completedFuture(globalProfile); + } + return asyncFileIO.queueFileCallable(globalFile, () -> getGlobalProfileFromDisk(key.getPlayerUUID(), globalFile)); + }); + } + + /** + * {@inheritDoc} + */ + @Override + public CompletableFuture> getExistingGlobalProfile(GlobalProfileKey key) { + File uuidFile = profileFilesLocator.getGlobalFile(key.getPlayerUUID()); + if (!uuidFile.exists()) { + return CompletableFuture.completedFuture(Option.none()); + } + return getGlobalProfile(key).thenApply(Option::of); + } + + private void migrateGlobalProfileToUUID(UUID playerUUID, String playerName) { + File legacyFile = profileFilesLocator.getGlobalFile(playerName); + if (!legacyFile.exists()) { + return; + } + if (!legacyFile.renameTo(profileFilesLocator.getGlobalFile(playerUUID.toString()))) { + Logging.warning("Could not properly migrate player global data file for " + playerName); + } + } + + private GlobalProfile getGlobalProfileFromDisk(UUID playerUUID, File globalFile) { + return new GlobalProfile(playerUUID, globalFile.toPath()); + } + + /** + * {@inheritDoc} + */ + @Override + public CompletableFuture modifyGlobalProfile(GlobalProfileKey key, Consumer consumer) { + return getGlobalProfile(key).thenCompose(globalProfile -> modifyGlobalProfile(globalProfile, consumer)); + } + + private CompletableFuture modifyGlobalProfile(GlobalProfile globalProfile, Consumer consumer) { + consumer.accept(globalProfile); + return updateGlobalProfile(globalProfile); + } + + /** + * {@inheritDoc} + */ + @Override + public CompletableFuture updateGlobalProfile(GlobalProfile globalProfile) { + File globalFile = profileFilesLocator.getGlobalFile(globalProfile.getPlayerUUID().toString()); + boolean didPlayerNameChange = playerNamesMapper.setPlayerName(globalProfile.getPlayerUUID(), globalProfile.getLastKnownName()); + return asyncFileIO.queueFileAction(globalFile, () -> processGlobalProfileWrite(globalProfile)) + .thenCompose(ignore -> didPlayerNameChange + ? playerNamesMapper.savePlayerNames() + : CompletableFuture.completedFuture(null)); + } + + private void processGlobalProfileWrite(GlobalProfile globalProfile) { + globalProfile.save().onFailure(throwable -> { + Logging.severe("Could not save global data for player: " + globalProfile); + Logging.severe(throwable.getMessage()); + }); + } + + /** + * {@inheritDoc} + */ + @Override + public CompletableFuture deleteGlobalProfile(GlobalProfileKey key, boolean clearPlayerFiles) { + return getExistingGlobalProfile(key) + .thenCompose(globalProfile -> { + if (globalProfile.isEmpty()) { + return CompletableFuture.failedFuture(new MultiverseException("Invalid global profile for player: " + key)); + } + return deleteGlobalProfileFromDisk(globalProfile.get()); + }) + .thenCompose(ignore -> clearPlayerFiles + ? clearAllPlayerProfileFiles(key.getPlayerUUID(), key.getPlayerName()) + : CompletableFuture.completedFuture(null)); + } + + private CompletableFuture deleteGlobalProfileFromDisk(GlobalProfile globalProfile) { + File globalFile = profileFilesLocator.getGlobalFile(globalProfile.getPlayerUUID().toString()); + return asyncFileIO.queueFileAction(globalFile, () -> { + if (!globalFile.delete()) { + throw new RuntimeException("Could not delete global profile file: " + globalFile); + } + }); + } + + private CompletableFuture clearAllPlayerProfileFiles(UUID playerUUID, String playerName) { + return CompletableFuture.allOf(Arrays.stream(ContainerType.values()) + .flatMap(containerType -> listContainerDataNames(containerType) + .stream() + .map(containerName -> deletePlayerFile(ProfileFileKey.of( + containerType, + containerName, + playerUUID, + playerName)))) + .toArray(CompletableFuture[]::new)); + } + + /** + * {@inheritDoc} + */ + @Override + public List listContainerDataNames(ContainerType containerType) { + return profileFilesLocator.listProfileContainerFolders(containerType) + .stream() + .map(File::getName) + .toList(); + } + + /** + * {@inheritDoc} + */ + @Override + public List listPlayerProfileNames(ContainerType containerType, String containerName) { + return profileFilesLocator.listPlayerProfileFiles(containerType, containerName) + .stream() + .map(file -> com.google.common.io.Files.getNameWithoutExtension(file.getName())) + .toList(); + } + + /** + * {@inheritDoc} + */ + @Override + public List listGlobalProfileUUIDs() { + return profileFilesLocator.listGlobalFiles() + .stream() + .map(file -> UUID.fromString(com.google.common.io.Files.getNameWithoutExtension(file.getName()))) + .toList(); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java new file mode 100644 index 00000000..0c2ab559 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/GlobalProfile.java @@ -0,0 +1,153 @@ +package org.mvplugins.multiverse.inventories.profile; + +import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.OfflinePlayer; +import org.bukkit.configuration.ConfigurationSection; +import org.mvplugins.multiverse.core.config.handle.StringPropertyHandle; +import org.mvplugins.multiverse.core.config.node.ConfigNode; +import org.mvplugins.multiverse.core.config.node.Node; +import org.mvplugins.multiverse.core.config.node.NodeGroup; +import org.mvplugins.multiverse.external.vavr.control.Try; +import org.mvplugins.multiverse.inventories.config.handle.JsonConfigurationHandle; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; +import org.mvplugins.multiverse.inventories.util.DataStrings; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * The global profile for a player which contains meta-data for the player. + */ +public final class GlobalProfile { + + private final UUID uuid; + private final Nodes nodes; + private final JsonConfigurationHandle handle; + private final StringPropertyHandle stringPropertyHandle; + + GlobalProfile(UUID uuid, Path configPath) { + this.uuid = uuid; + this.nodes = new Nodes(); + this.handle = JsonConfigurationHandle.builder(configPath, nodes.nodes).build(); + this.stringPropertyHandle = new StringPropertyHandle(handle); + load(); + } + + Try load() { + return handle.load().onFailure(e -> { + Logging.severe("Failed to load global profile for player %s: %s", uuid, e.getMessage()); + }); + } + + Try save() { + return handle.save().onFailure(e -> { + Logging.severe("Failed to save global profile for player %s: %s", uuid, e.getMessage()); + }); + } + + public StringPropertyHandle getStringPropertyHandle() { + return stringPropertyHandle; + } + + /** + * Returns the UUID of the player. + * + * @return the UUID of the player. + */ + public UUID getPlayerUUID() { + return uuid; + } + + /** + * Returns the last name the player was known to have. + * + * @return the last name the player was known to have. + */ + public String getLastKnownName() { + return handle.get(nodes.lastKnownName); + } + + /** + * Sets the last name that the player was seen having. + *

This should be updated when a player's name is changed through Mojang but only after their data has been + * migrated to the new name.

+ * + * @param lastKnownName the last known name for the player. + */ + public Try setLastKnownName(String lastKnownName) { + return handle.set(nodes.lastKnownName, lastKnownName); + } + + /** + * Returns the name of last world the player was in. + * + * @return The last world the player was in or null if not set. + */ + public String getLastWorld() { + return handle.get(nodes.lastWorld); + } + + /** + * Sets the last world the player was known to be in. This is done automatically on world change. + * + * @param world The world the player is in. + */ + public Try setLastWorld(String world) { + return handle.set(nodes.lastWorld, world); + } + + /** + * Says whether the player data for the player's logout world should be loaded when the player logs in. + * The default value is false. + * + * @return true if player data should be loaded when they log in. + */ + public boolean shouldLoadOnLogin() { + return handle.get(nodes.loadOnLogin); + } + + /** + * Sets whether the player data for the player's logout world should be loaded when the player logs in. + * + * @param loadOnLogin true if player data should be loaded when they log in. + */ + public Try setLoadOnLogin(boolean loadOnLogin) { + return handle.set(nodes.loadOnLogin, loadOnLogin); + } + + @Override + public String toString() { + return "GlobalProfile{" + + "uuid=" + uuid + + ", lastWorld='" + getLastWorld() + '\'' + + ", lastKnownName='" + getLastKnownName() + '\'' + + ", loadOnLogin=" + shouldLoadOnLogin() + + '}'; + } + + private static final class Nodes { + private final NodeGroup nodes = new NodeGroup(); + + private N node(N node) { + nodes.add(node); + return node; + } + + private final ConfigNode lastWorld = node(ConfigNode.builder("playerData.lastWorld", String.class) + .defaultValue("") + .name("last-world") + .build()); + + private final ConfigNode lastKnownName = node(ConfigNode.builder("playerData.lastKnownName", String.class) + .defaultValue("") + .name("last-known-name") + .build()); + + private final ConfigNode loadOnLogin = node(ConfigNode.builder("playerData.loadOnLogin", Boolean.class) + .defaultValue(false) + .name("load-on-login") + .build()); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerNamesMapper.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerNamesMapper.java new file mode 100644 index 00000000..f6397134 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerNamesMapper.java @@ -0,0 +1,162 @@ +package org.mvplugins.multiverse.inventories.profile; + +import com.dumptruckman.minecraft.util.Logging; +import com.google.common.base.Strings; +import net.minidev.json.JSONObject; +import net.minidev.json.JSONValue; +import net.minidev.json.parser.JSONParser; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jakarta.inject.Provider; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.external.vavr.control.Option; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; + +@Service +public final class PlayerNamesMapper { + + private static PlayerNamesMapper instance; + + public static PlayerNamesMapper getInstance() { + if (instance == null) { + throw new IllegalStateException("Player names mapper has not been initialized yet."); + } + return instance; + } + + private static final String FILENAME = "playernames.json"; + + private final AsyncFileIO asyncFileIO; + private final Provider profileDataSourceProvider; + private final Provider profileCacheManagerProvider; + + private final File playerNamesFile; + private final Map playerNamesMap; + private final Map playerUUIDMap; + + private Map playerNamesJson; + + @Inject + private PlayerNamesMapper( + @NotNull MultiverseInventories inventories, + @NotNull AsyncFileIO asyncFileIO, + @NotNull Provider profileDataSourceProvider, + @NotNull Provider profileCacheManagerProvider + ) { + this.asyncFileIO = asyncFileIO; + this.profileDataSourceProvider = profileDataSourceProvider; + this.profileCacheManagerProvider = profileCacheManagerProvider; + + this.playerNamesFile = new File(inventories.getDataFolder(), FILENAME); + this.playerNamesMap = new ConcurrentHashMap<>(); + this.playerUUIDMap = new ConcurrentHashMap<>(); + + instance = this; + } + + public void loadMap() { + Logging.config("Loading player names map..."); + playerNamesMap.clear(); + playerUUIDMap.clear(); + if (playerNamesFile.exists()) { + loadFromPlayerNamesFile(); + } else { + buildPlayerNamesMap(); + } + } + + private void loadFromPlayerNamesFile() { + try (FileReader fileReader = new FileReader(playerNamesFile)) { + playerNamesJson = new ConcurrentHashMap<>((JSONObject) new JSONParser(JSONParser.DEFAULT_PERMISSIVE_MODE).parse(fileReader)); + if (playerNamesJson.isEmpty()) { + buildPlayerNamesMap(); + return; + } + playerNamesJson.forEach((String uuid, Object name) -> { + UUID playerUUID = UUID.fromString(uuid); + String playerName = String.valueOf(name); + GlobalProfileKey globalProfileKey = GlobalProfileKey.of(playerUUID, playerName); + playerNamesMap.put(playerName, globalProfileKey); + playerUUIDMap.put(playerUUID, globalProfileKey); + }); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void buildPlayerNamesMap() { + Logging.info("Generating player names map... This may take a while."); + playerNamesJson = new ConcurrentHashMap<>(); + + ProfileDataSource profileDataSource = profileDataSourceProvider.get(); + CompletableFuture[] futures = profileDataSource.listGlobalProfileUUIDs().stream() + .map(uuid -> profileDataSource.getGlobalProfile(GlobalProfileKey.of(uuid, "")) + .thenAccept(globalProfile -> setPlayerName(uuid, globalProfile.getLastKnownName()))) + .toArray(CompletableFuture[]::new); + CompletableFuture.allOf(futures).thenCompose(ignore -> savePlayerNames()).join(); + profileCacheManagerProvider.get().clearAllGlobalProfileCaches(); + Logging.info("Generated player names map."); + } + + boolean setPlayerName(UUID uuid, String name) { + if (playerNamesJson == null) { + throw new IllegalStateException("Player names mapper has not been loaded yet."); + } + if (Strings.isNullOrEmpty(name)) { + return false; + } + if (getKey(name).filter(g -> g.getPlayerUUID().equals(uuid)).isDefined()) { + return false; + } + + Logging.finer("Setting player name mapping for %s to %s", uuid, name); + GlobalProfileKey globalProfileKey = GlobalProfileKey.of(uuid, name); + + // Handle remove of old playername + Object oldName = playerNamesJson.put(uuid.toString(), name); + playerNamesMap.remove(String.valueOf(oldName)); + + playerNamesMap.put(name, globalProfileKey); + playerUUIDMap.put(uuid, globalProfileKey); + return true; + } + + CompletableFuture savePlayerNames() { + if (playerNamesJson == null) { + throw new IllegalStateException("Player names mapper has not been loaded yet."); + } + return asyncFileIO.queueFileAction(playerNamesFile, () -> { + Logging.finer("Saving player names map..."); + try (FileWriter fileWriter = new FileWriter(playerNamesFile)) { + fileWriter.write(JSONValue.toJSONString(playerNamesJson)); + Logging.finer("Saving player names map... Done!"); + } catch (Exception e) { + Logging.severe("Could not save player names map."); + e.printStackTrace(); + } + }); + } + + public Option getKey(String playerName) { + return Option.of(playerNamesMap.get(playerName)); + } + + public Option getKey(UUID playerUUID) { + return Option.of(playerUUIDMap.get(playerUUID)); + } + + public List getKeys() { + return playerNamesMap.values().stream().toList(); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfileJsonSerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfileJsonSerializer.java new file mode 100644 index 00000000..73c5d075 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/PlayerProfileJsonSerializer.java @@ -0,0 +1,124 @@ +package org.mvplugins.multiverse.inventories.profile; + +import com.dumptruckman.minecraft.util.Logging; +import net.minidev.json.JSONObject; +import net.minidev.json.parser.JSONParser; +import net.minidev.json.parser.ParseException; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; +import org.mvplugins.multiverse.inventories.share.ProfileEntry; +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.util.DataStrings; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.logging.Level; + +final class PlayerProfileJsonSerializer { + + static Map serialize(PlayerProfile playerProfile) { + Map playerData = new LinkedHashMap<>(); + JSONObject jsonStats = new JSONObject(); + + for (var entry : playerProfile.getData().entrySet()) { + Sharable sharable = entry.getKey(); + Object sharableValue = entry.getValue(); + if (sharableValue == null) { + continue; + } + + var serializer = sharable.getSerializer(); + var profileEntry = sharable.getProfileEntry(); + if (serializer == null || profileEntry == null) { + continue; + } + + String fileTag = profileEntry.fileTag(); + Object serializedValue = serializer.serialize(sharableValue); + if (profileEntry.isStat()) { + jsonStats.put(fileTag, serializedValue); + } else { + playerData.put(fileTag, serializedValue); + } + } + + if (!jsonStats.isEmpty()) { + playerData.put(DataStrings.PLAYER_STATS, jsonStats); + } + + return playerData; + } + + static PlayerProfile deserialize(ProfileKey pKey, Map playerData) { + PlayerProfile profile = PlayerProfile.newProfile(pKey); + for (Object keyObj : playerData.keySet()) { + String key = keyObj.toString(); + final Object value = playerData.get(key); + if (value == null) { + Logging.fine("Player data '" + key + "' is null for: " + pKey.getPlayerName()); + continue; + } + + if (key.equalsIgnoreCase(DataStrings.PLAYER_STATS)) { + if (value instanceof String) { + parseJsonPlayerStatsIntoProfile((String) value, profile); + continue; + } + if (value instanceof Map) { + parsePlayerStatsIntoProfile((Map) value, profile); + } else { + Logging.warning("Could not parse stats for " + pKey.getPlayerName()); + } + continue; + } + + try { + Sharable sharable = ProfileEntry.lookup(false, key); + if (sharable == null) { + Logging.fine("Player fileTag '" + key + "' is unrecognized!"); + continue; + } + profile.set(sharable, sharable.getSerializer().deserialize(playerData.get(key))); + } catch (Exception e) { + Logging.fine("Could not parse fileTag: '" + key + "' with value '" + playerData.get(key) + "'"); + Logging.getLogger().log(Level.FINE, "Exception: ", e); + e.printStackTrace(); + } + } + Logging.finer("Created player profile from map for '" + pKey.getPlayerName() + "'."); + return profile; + } + + private static void parsePlayerStatsIntoProfile(Map stats, PlayerProfile profile) { + for (Object key : stats.keySet()) { + Sharable sharable = ProfileEntry.lookup(true, key.toString()); + if (sharable != null) { + profile.set(sharable, sharable.getSerializer().deserialize(stats.get(key).toString())); + } else { + Logging.warning("Could not parse stat: '" + key + "' for player '" + + profile.getPlayerName() + "' for " + profile.getContainerType() + " '" + + profile.getContainerName() + "'"); + } + } + } + + private static void parseJsonPlayerStatsIntoProfile(String stats, PlayerProfile profile) { + if (stats.isEmpty()) { + return; + } + JSONObject jsonStats = null; + try { + jsonStats = (JSONObject) new JSONParser(JSONParser.USE_INTEGER_STORAGE | JSONParser.ACCEPT_TAILLING_SPACE).parse(stats); + } catch (ParseException | ClassCastException e) { + Logging.warning("Could not parse stats for player'" + profile.getPlayerName() + "' for " + + profile.getContainerType() + " '" + profile.getContainerName() + "': " + e.getMessage()); + } + if (jsonStats == null) { + Logging.warning("Could not parse stats for player'" + profile.getPlayerName() + "' for " + + profile.getContainerType() + " '" + profile.getContainerName() + "'"); + return; + } + parsePlayerStatsIntoProfile(jsonStats, profile); + } + +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileCacheManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileCacheManager.java new file mode 100644 index 00000000..1c2b5814 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileCacheManager.java @@ -0,0 +1,113 @@ +package org.mvplugins.multiverse.inventories.profile; + +import com.github.benmanes.caffeine.cache.AsyncCache; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.stats.CacheStats; +import com.google.common.collect.Sets; +import org.bukkit.configuration.file.FileConfiguration; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.vavr.control.Option; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Predicate; + +@Service +public final class ProfileCacheManager { + + private final Cache playerFileCache; + private final AsyncCache playerProfileCache; + private final AsyncCache globalProfileCache; + + @Inject + ProfileCacheManager(@NotNull InventoriesConfig inventoriesConfig, @NotNull AsyncFileIO asyncFileIO) { + this.playerFileCache = Caffeine.newBuilder() + .expireAfterAccess(inventoriesConfig.getPlayerFileCacheExpiry(), TimeUnit.MINUTES) + .maximumSize(inventoriesConfig.getPlayerFileCacheSize()) + .recordStats() + .build(); + + this.playerProfileCache = Caffeine.newBuilder() + .expireAfterAccess(inventoriesConfig.getPlayerProfileCacheExpiry(), TimeUnit.MINUTES) + .maximumSize(inventoriesConfig.getPlayerProfileCacheSize()) + .executor(asyncFileIO.getExecutor()) + .recordStats() + .buildAsync(); + + this.globalProfileCache = Caffeine.newBuilder() + .expireAfterAccess(inventoriesConfig.getGlobalProfileCacheExpiry(), TimeUnit.MINUTES) + .maximumSize(inventoriesConfig.getGlobalProfileCacheSize()) + .executor(asyncFileIO.getExecutor()) + .recordStats() + .buildAsync(); + } + + FileConfiguration getOrLoadPlayerFile(ProfileKey key, Function mappingFunction) { + return playerFileCache.get(key, mappingFunction); + } + + CompletableFuture getOrLoadPlayerProfile(ProfileKey key, BiFunction> mappingFunction) { + return playerProfileCache.get(key, mappingFunction); + } + + CompletableFuture getOrLoadGlobalProfile(UUID uuid, BiFunction> mappingFunction) { + return globalProfileCache.get(uuid, mappingFunction); + } + + Option getCachedPlayerProfile(ProfileKey key) { + return Option.of(playerProfileCache.synchronous().getIfPresent(key)); + } + + public void clearPlayerCache(String playerName) { + clearPlayerProfileCache(key -> key.getPlayerName().equals(playerName)); + } + + public void clearPlayerCache(UUID playerUUID) { + clearPlayerProfileCache(key -> key.getPlayerUUID().equals(playerUUID)); + clearGlobalProfileCache(key -> key.equals(playerUUID)); + } + + public void clearPlayerProfileCache(Predicate predicate) { + playerFileCache.invalidateAll(Sets.filter(playerFileCache.asMap().keySet(), predicate::test)); + playerProfileCache.synchronous().invalidateAll(Sets.filter(playerProfileCache.asMap().keySet(), predicate::test)); + } + + public void clearAllPlayerProfileCaches() { + playerFileCache.invalidateAll(); + playerProfileCache.synchronous().invalidateAll(); + } + + public void clearGlobalProfileCache(Predicate predicate) { + globalProfileCache.synchronous().invalidateAll(Sets.filter(globalProfileCache.asMap().keySet(), predicate::test)); + } + + public void clearAllGlobalProfileCaches() { + globalProfileCache.synchronous().invalidateAll(); + } + + public void clearAllCache() { + playerFileCache.invalidateAll(); + globalProfileCache.synchronous().invalidateAll(); + playerProfileCache.synchronous().invalidateAll(); + } + + public Map getCacheStats() { + Map stats = new HashMap<>(); + stats.put("playerFileCache", playerFileCache.stats()); + stats.put("globalProfileCache", globalProfileCache.synchronous().stats()); + stats.put("profileCache", playerProfileCache.synchronous().stats()); + return stats; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java new file mode 100644 index 00000000..c77bbe1d --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSource.java @@ -0,0 +1,146 @@ +package org.mvplugins.multiverse.inventories.profile; + +import org.jvnet.hk2.annotations.Contract; +import org.mvplugins.multiverse.external.vavr.control.Option; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileFileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; + +import java.io.IOException; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +/** + * A source for updating and retrieving player profiles via persistence. + */ +@Contract +public sealed interface ProfileDataSource permits FlatFileProfileDataSource { + + /** + * Retrieves a PlayerProfile from the disk. If no data was found, a new PlayerProfile will be created. + * + * @param profileKey The key of the profile to retrieve. + * @return The player profile data. + */ + CompletableFuture getPlayerProfile(ProfileKey profileKey); + + /** + * Updates the persisted data for a player for a specific profile to disk. + * + * @param playerProfile The profile for the player that is being updated. + * @return Future that completes when the profile has been updated. + */ + CompletableFuture updatePlayerProfile(PlayerProfile playerProfile); + + /** + * Clears all data of the specified profile, and deletes profile data from disk. + *
+ * This action is irreversible. + * + * @param profileKey The key of the profile to delete + * @return Future that completes when the profile has been deleted. + */ + CompletableFuture deletePlayerProfile(ProfileKey profileKey); + + /** + * Clears all data for multiple {@link ProfileType} of the specified profile, and deletes profile data from disk. + * If all profiles are deleted, the profile file itself will be deleted. + *
+ * This action is irreversible. + * + * @param profileKey The key of the profile to delete + * @param profileTypes The list of profile types to delete + * @return Future that completes when the profile has been deleted. + */ + CompletableFuture deletePlayerProfiles(ProfileFileKey profileKey, ProfileType[] profileTypes); + + /** + * Deletes the profile file for a player's profile from disk. Essentially same as + * {@link #deletePlayerProfiles(ProfileFileKey, ProfileType[])} with all profile types. + *
+ * This action is irreversible. + * + * @param profileKey The key of the profile to delete + * @return Future that completes when the profile has been deleted. + */ + CompletableFuture deletePlayerFile(ProfileFileKey profileKey); + + /** + * Copies all the data belonging to oldName to newName and removes the old data. + * + * @param oldName the previous name of the player. + * @param newName the new name of the player. + * @throws IOException Thrown if something goes wrong while migrating the files. + */ + void migratePlayerProfileName(String oldName, String newName) throws IOException; + + /** + * Retrieves the global profile for a player which contains meta-data for the player. + * + * @param key The key of the player. + * @return The global profile for the specified player asynchronously. + */ + CompletableFuture getGlobalProfile(GlobalProfileKey key); + + /** + * Retrieves the global profile for a player which contains meta-data for the player if it exists. + * + * @param key The key of the player. + * @return The global profile for the specified player or {@link Option#none} if it does not exist asynchronously. + */ + CompletableFuture> getExistingGlobalProfile(GlobalProfileKey key); + + /** + * Modifies the global profile for a player and automatically saves it. + * + * @param key The key of the player. + * @return A CompletableFuture that completes when the global profile has been saved. + */ + CompletableFuture modifyGlobalProfile(GlobalProfileKey key, Consumer consumer); + + /** + * Update the file for a player's global profile to disk. + * + * @param globalProfile The GlobalProfile object to update the file for. + * @return A CompletableFuture that completes when the global profile has been updated. + */ + CompletableFuture updateGlobalProfile(GlobalProfile globalProfile); + + /** + * Deletes the file for a player's global profile from disk. Optionally clears the player's profile data files as well. + * + * @param key The key of the player. + * @param clearPlayerFiles Whether to clear the player's profile data files as well. + * @return A CompletableFuture that completes when the global profile has been deleted. + */ + CompletableFuture deleteGlobalProfile(GlobalProfileKey key, boolean clearPlayerFiles); + + /** + * Lists the names of all available data containers of the specified type. + * + * @param containerType The type of the container (e.g., WORLD, GROUP) whose data names are to be listed. + * @return A collection of strings representing the names of the data containers. + */ + List listContainerDataNames(ContainerType containerType); + + /** + * Lists the names of all available player profiles within the given container type and container name. + * + * @param containerType The type of the container (e.g., WORLD, GROUP) whose player profiles are to be listed. + * @param containerName The name of the container whose player profiles are to be listed. + * @return A collection of strings representing the names of all available player profiles. + */ + List listPlayerProfileNames(ContainerType containerType, String containerName); + + /** + * Retrieves a collection of UUIDs of all players who have a global profile. + * + * @return A collection of UUIDs of all players who have a global profile. + */ + List listGlobalProfileUUIDs(); +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFilesLocator.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFilesLocator.java new file mode 100644 index 00000000..8cbfc874 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/ProfileFilesLocator.java @@ -0,0 +1,144 @@ +package org.mvplugins.multiverse.inventories.profile; + +import com.dumptruckman.minecraft.util.Logging; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.vavr.control.Option; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileFileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +@Service +final class ProfileFilesLocator { + + private static final String JSON = ".json"; + + private final File worldFolder; + private final File groupFolder; + private final File globalFolder; + + @Inject + ProfileFilesLocator(@NotNull MultiverseInventories plugin) throws IOException { + // Make the data folders + plugin.getDataFolder().mkdirs(); + + // Check if the data file exists. If not, create it. + this.worldFolder = new File(plugin.getDataFolder(), "worlds"); + if (!this.worldFolder.exists()) { + if (!this.worldFolder.mkdirs()) { + throw new IOException("Could not create world folder!"); + } + } + this.groupFolder = new File(plugin.getDataFolder(), "groups"); + if (!this.groupFolder.exists()) { + if (!this.groupFolder.mkdirs()) { + throw new IOException("Could not create group folder!"); + } + } + this.globalFolder = new File(plugin.getDataFolder(), "players"); + if (!this.globalFolder.exists()) { + if (!this.globalFolder.mkdirs()) { + throw new IOException("Could not create player folder!"); + } + } + } + + File getWorldFolder() { + return worldFolder; + } + + File getGroupFolder() { + return groupFolder; + } + + File getContainerFolder(ContainerType type) { + return switch (type) { + case GROUP -> this.groupFolder; + case WORLD -> this.worldFolder; + }; + } + + List listProfileContainerFolders(ContainerType type) { + return Option.of(getContainerFolder(type).listFiles()) + .map(filesList -> Arrays.stream(filesList) + .filter(File::isDirectory) + .toList()) + .getOrElse(Collections::emptyList); + } + + File getProfileContainerFolder(ContainerType type, String folderName) { + File folder = new File(getContainerFolder(type), folderName); + if (!folder.exists() && !folder.mkdirs()) { + Logging.severe("Could not create profile container folder!"); + } + return folder; + } + + List listPlayerProfileFiles(ContainerType type, String dataName) { + return Option.of(getProfileContainerFolder(type, dataName).listFiles()) + .map(filesList -> Arrays.stream(filesList) + .filter(File::isFile) + .toList()) + .getOrElse(Collections::emptyList); + } + + /** + * Retrieves the data file for a player based on a given world/group name. + * + * @param profileKey The profile target to get the file + * @return The data file for a player. + */ + File getPlayerProfileFile(ProfileFileKey profileKey) { + return getPlayerProfileFile(profileKey.getContainerType(), profileKey.getDataName(), profileKey.getPlayerName()); + } + + /** + * Retrieves the data file for a player based on a given world/group name. + * + * @param type Indicates whether data is for group or world. + * @param dataName The name of the group or world. + * @param playerName The name of the player. + * @return The data file for a player. + */ + File getPlayerProfileFile(ContainerType type, String dataName, String playerName) { + File jsonPlayerFile = new File(getProfileContainerFolder(type, dataName), playerName + JSON); + Logging.finer("got data file: %s. Type: %s, DataName: %s, PlayerName: %s", + jsonPlayerFile.getPath(), type, dataName, playerName); + return jsonPlayerFile; + } + + File getGlobalFolder() { + return this.globalFolder; + } + + List listGlobalFiles() { + return Option.of(this.globalFolder.listFiles()) + .map(filesList -> Arrays.stream(filesList) + .filter(File::isFile) + .toList()) + .getOrElse(Collections::emptyList); + } + + File getGlobalFile(UUID playerUUID) { + return getGlobalFile(playerUUID.toString()); + } + + /** + * Retrieves the data file for a player for their global data. + * + * @param playerIdentifier The name of the file (player name or UUID) without extension. + * @return The data file for a player. + */ + File getGlobalFile(String playerIdentifier) { + return new File(globalFolder, playerIdentifier + JSON); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditAction.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditAction.java new file mode 100644 index 00000000..9f840a7f --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditAction.java @@ -0,0 +1,70 @@ +package org.mvplugins.multiverse.inventories.profile.bulkedit; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.ApiStatus; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +@ApiStatus.Experimental +public sealed abstract class BulkEditAction permits GlobalProfileClearAction, PlayerFileAction, PlayerProfileAction { + + protected final MultiverseInventories inventories; + protected final ProfileDataSource profileDataSource; + protected final GlobalProfileKey[] globalProfileKeys; + + BulkEditAction(MultiverseInventories inventories, GlobalProfileKey[] globalProfileKeys) { + this.inventories = inventories; + this.profileDataSource = inventories.getServiceLocator().getService(ProfileDataSource.class); + this.globalProfileKeys = globalProfileKeys; + } + + public CompletableFuture execute() { + BulkEditResult bulkEditResult = new BulkEditResult(); + List targetKeys = aggregateKeys(); + Set onlinePlayers = new HashSet<>(); + return CompletableFuture.allOf(targetKeys.stream() + .map(key -> { + Player player = key.getOnlinePlayer(); + if (player != null && isOnlinePlayerAffected(key, player)) { + onlinePlayers.add(player); + } + return performAction(key) + .thenRun(bulkEditResult::incrementSuccess) + .exceptionally(throwable -> { + bulkEditResult.incrementFailure(); + throwable.printStackTrace(); + return null; + }); + }) + .toArray(CompletableFuture[]::new)) + .thenRun(() -> + Bukkit.getScheduler().runTask(inventories, () -> + onlinePlayers.forEach(this::updateOnlinePlayerNow))) + .thenApply(ignore -> bulkEditResult); + } + + protected abstract List aggregateKeys(); + + protected abstract CompletableFuture performAction(K key); + + protected boolean isOnlinePlayerAffected(K key, Player player) { + return key.getPlayerUUID().equals(player.getUniqueId()); + } + + protected abstract void updateOnlinePlayerNow(Player player); + + public Map> getActionSummary() { + return Map.of("Players", Arrays.stream(globalProfileKeys) + .map(GlobalProfileKey::getPlayerName) + .toList()); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditCreator.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditCreator.java new file mode 100644 index 00000000..851bd8af --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditCreator.java @@ -0,0 +1,33 @@ +package org.mvplugins.multiverse.inventories.profile.bulkedit; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; +import org.mvplugins.multiverse.inventories.share.Sharable; + +@Service +@ApiStatus.Experimental +public final class BulkEditCreator { + + private final MultiverseInventories inventories; + + @Inject + BulkEditCreator(@NotNull MultiverseInventories inventories) { + this.inventories = inventories; + } + + public BulkEditAction globalProfileClear(GlobalProfileKey[] globalProfileKeys, boolean clearPlayerProfiles) { + return new GlobalProfileClearAction(inventories, globalProfileKeys, clearPlayerProfiles); + } + + public BulkEditAction playerProfileClear(PlayerProfilesPayload bulkProfilesPayload) { + return new PlayerProfileClearAction(inventories, bulkProfilesPayload); + } + + public BulkEditAction playerProfileDeleteSharable(PlayerProfilesPayload bulkProfilesPayload, Sharable sharable) { + return new PlayerProfileDeleteAction(inventories, sharable, bulkProfilesPayload); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditResult.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditResult.java new file mode 100644 index 00000000..3985aeaa --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/BulkEditResult.java @@ -0,0 +1,40 @@ +package org.mvplugins.multiverse.inventories.profile.bulkedit; + +import org.jetbrains.annotations.ApiStatus; + +import java.util.concurrent.atomic.AtomicInteger; + +@ApiStatus.Experimental +public final class BulkEditResult { + + private final long startTime = System.nanoTime(); + private final AtomicInteger successCount = new AtomicInteger(0); + private final AtomicInteger failureCount = new AtomicInteger(0); + + BulkEditResult() { } + + void incrementSuccess() { + successCount.incrementAndGet(); + } + + void incrementFailure() { + failureCount.incrementAndGet(); + } + + public int getSuccessCount() { + return successCount.get(); + } + + public int getFailureCount() { + return failureCount.get(); + } + + /** + * In milliseconds + * + * @return Gets the time taken + */ + public double getTimeTaken() { + return (double) (System.nanoTime() - startTime) / 1_000_000.0; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/GlobalProfileClearAction.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/GlobalProfileClearAction.java new file mode 100644 index 00000000..363a2c62 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/GlobalProfileClearAction.java @@ -0,0 +1,39 @@ +package org.mvplugins.multiverse.inventories.profile.bulkedit; + +import org.bukkit.entity.Player; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.handleshare.ReadOnlyShareHandler; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +final class GlobalProfileClearAction extends BulkEditAction { + + private final boolean clearPlayerProfile; + + GlobalProfileClearAction(MultiverseInventories inventories, GlobalProfileKey[] globalProfileKeys, boolean clearPlayerProfiles) { + super(inventories, globalProfileKeys); + this.clearPlayerProfile = clearPlayerProfiles; + } + + @Override + protected List aggregateKeys() { + return List.of(globalProfileKeys); + } + + @Override + protected CompletableFuture performAction(GlobalProfileKey key) { + return profileDataSource.deleteGlobalProfile(key, clearPlayerProfile); + } + + @Override + protected boolean isOnlinePlayerAffected(GlobalProfileKey key, Player player) { + return super.isOnlinePlayerAffected(key, player) && clearPlayerProfile; + } + + @Override + protected void updateOnlinePlayerNow(Player player) { + new ReadOnlyShareHandler(inventories, player).handleSharing(); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerFileAction.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerFileAction.java new file mode 100644 index 00000000..94a24e18 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerFileAction.java @@ -0,0 +1,30 @@ +package org.mvplugins.multiverse.inventories.profile.bulkedit; + +import org.jetbrains.annotations.ApiStatus; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.key.ProfileFileKey; + +import java.util.List; +import java.util.Map; + +abstract sealed class PlayerFileAction extends BulkEditAction permits PlayerProfileClearAction { + + private final PlayerProfilesAggregator profilesAggregator; + protected final PlayerProfilesPayload bulkProfilesPayload; + + PlayerFileAction(MultiverseInventories inventories, PlayerProfilesPayload bulkProfilesPayload) { + super(inventories, bulkProfilesPayload.globalProfileKeys()); + this.profilesAggregator = inventories.getServiceLocator().getService(PlayerProfilesAggregator.class); + this.bulkProfilesPayload = bulkProfilesPayload; + } + + @Override + protected List aggregateKeys() { + return profilesAggregator.getProfileFileKeys(bulkProfilesPayload); + } + + @Override + public Map> getActionSummary() { + return bulkProfilesPayload.getSummary(); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfileAction.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfileAction.java new file mode 100644 index 00000000..86825eb6 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfileAction.java @@ -0,0 +1,33 @@ +package org.mvplugins.multiverse.inventories.profile.bulkedit; + +import org.jetbrains.annotations.ApiStatus; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; + +import java.util.List; +import java.util.Map; + +abstract sealed class PlayerProfileAction extends BulkEditAction permits PlayerProfileDeleteAction { + + private final PlayerProfilesAggregator profilesAggregator; + private final PlayerProfilesPayload bulkProfilesPayload; + + protected PlayerProfileAction( + MultiverseInventories inventories, + PlayerProfilesPayload bulkProfilesPayload + ) { + super(inventories, bulkProfilesPayload.globalProfileKeys()); + this.profilesAggregator = inventories.getServiceLocator().getService(PlayerProfilesAggregator.class); + this.bulkProfilesPayload = bulkProfilesPayload; + } + + @Override + protected List aggregateKeys() { + return profilesAggregator.getPlayerProfileKeys(bulkProfilesPayload); + } + + @Override + public Map> getActionSummary() { + return bulkProfilesPayload.getSummary(); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfileClearAction.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfileClearAction.java new file mode 100644 index 00000000..45d44acb --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfileClearAction.java @@ -0,0 +1,67 @@ +package org.mvplugins.multiverse.inventories.profile.bulkedit; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.ApiStatus; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.handleshare.ReadOnlyShareHandler; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileFileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +final class PlayerProfileClearAction extends PlayerFileAction { + + private final WorldGroupManager worldGroupManager; + private final Set profileTypesSet; + + public PlayerProfileClearAction(MultiverseInventories inventories, PlayerProfilesPayload bulkProfilesPayload) { + super(inventories, bulkProfilesPayload); + this.worldGroupManager = inventories.getServiceLocator().getService(WorldGroupManager.class); + this.profileTypesSet = Set.of(bulkProfilesPayload.profileTypes()); + } + + @Override + protected CompletableFuture performAction(ProfileFileKey key) { + return profileDataSource.deletePlayerProfiles(key, bulkProfilesPayload.profileTypes()) + .thenCompose(ignore -> profileDataSource.modifyGlobalProfile( + key, + profile -> profile.setLoadOnLogin(true) + )); + } + + @Override + protected boolean isOnlinePlayerAffected(ProfileFileKey key, Player player) { + if (!profileTypesSet.contains(ProfileTypes.forPlayer(player))) { + return false; + } + + // Gets groups that share this sharable + List groups = worldGroupManager.getGroupsForWorld(player.getWorld().getName()); + + Shares unhandledSharables = Sharables.enabledOf(); + for (WorldGroup worldGroup : groups) { + unhandledSharables.removeAll(worldGroup.getApplicableShares()); + } + + if (!unhandledSharables.isEmpty()) { + return key.getContainerType() == ContainerType.WORLD && player.getWorld().getName().equals(key.getDataName()); + } + + // Using group for sharable + return key.getContainerType() == ContainerType.GROUP && groups.stream() + .anyMatch(group -> group.getName().equals(key.getDataName())); + } + + @Override + protected void updateOnlinePlayerNow(Player player) { + new ReadOnlyShareHandler(inventories, player).handleSharing(); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfileDeleteAction.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfileDeleteAction.java new file mode 100644 index 00000000..79c44a44 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfileDeleteAction.java @@ -0,0 +1,74 @@ +package org.mvplugins.multiverse.inventories.profile.bulkedit; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.ApiStatus; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.handleshare.SingleShareReader; +import org.mvplugins.multiverse.inventories.profile.data.ProfileData; +import org.mvplugins.multiverse.inventories.profile.data.SingleSharableData; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.util.FutureNow; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +final class PlayerProfileDeleteAction extends PlayerProfileAction { + + + private final WorldGroupManager worldGroupManager; + private final Sharable sharable; + + public PlayerProfileDeleteAction( + MultiverseInventories inventories, + Sharable sharable, + PlayerProfilesPayload bulkProfilesPayload + ) { + super(inventories, bulkProfilesPayload); + this.worldGroupManager = inventories.getServiceLocator().getService(WorldGroupManager.class); + this.sharable = sharable; + } + + @Override + protected CompletableFuture performAction(ProfileKey key) { + return profileDataSource.getPlayerProfile(key) + .thenCompose(playerProfile -> { + playerProfile.set(sharable, null); + return profileDataSource.updatePlayerProfile(playerProfile); + }) + .thenCompose(ignore -> profileDataSource.modifyGlobalProfile(key, profile -> { + profile.setLoadOnLogin(true); + })); + } + + @Override + protected boolean isOnlinePlayerAffected(ProfileKey key, Player player) { + if (!ProfileTypes.forPlayer(player).equals(key.getProfileType())) { + return false; + } + + // Gets groups that share this sharable + List groups = worldGroupManager.getGroupsForWorld(player.getWorld().getName()).stream() + .filter(group -> group.isSharing(sharable)) + .toList(); + + if (groups.isEmpty()) { + // Using world itself for sharable + return key.getContainerType() == ContainerType.WORLD && player.getWorld().getName().equals(key.getDataName()); + } + + // Using group for sharable + return key.getContainerType() == ContainerType.GROUP && groups.stream() + .anyMatch(group -> group.getName().equals(key.getDataName())); + } + + @Override + protected void updateOnlinePlayerNow(Player player) { + ProfileData sharableData = new SingleSharableData<>(sharable, FutureNow.get(SingleShareReader.of(inventories, player, sharable).read())); + sharable.getHandler().updatePlayer(player, sharableData); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfilesAggregator.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfilesAggregator.java new file mode 100644 index 00000000..6a2b856b --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfilesAggregator.java @@ -0,0 +1,89 @@ +package org.mvplugins.multiverse.inventories.profile.bulkedit; + +import com.google.common.collect.Sets; +import org.jetbrains.annotations.ApiStatus; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.profile.key.ContainerKey; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileFileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +@Service +final class PlayerProfilesAggregator { + + private final WorldGroupManager worldGroupManager; + + @Inject + PlayerProfilesAggregator(WorldGroupManager worldGroupManager) { + this.worldGroupManager = worldGroupManager; + } + + List getProfileFileKeys(PlayerProfilesPayload payload) { + var containerKeys = payload.includeGroupsWorlds() + ? includeGroupsWorlds(payload.containerKeys()) + : payload.containerKeys(); + + List profileFileKeys = new ArrayList<>( + payload.globalProfileKeys().length * containerKeys.length); + + for (GlobalProfileKey globalProfileKey : payload.globalProfileKeys()) { + for (ContainerKey containerKey : containerKeys) { + profileFileKeys.add(ProfileFileKey.of( + containerKey.getContainerType(), + containerKey.getDataName(), + globalProfileKey.getPlayerUUID(), + globalProfileKey.getPlayerName())); + } + } + return profileFileKeys; + } + + List getPlayerProfileKeys(PlayerProfilesPayload payload) { + var containerKeys = payload.includeGroupsWorlds() + ? includeGroupsWorlds(payload.containerKeys()) + : payload.containerKeys(); + + List profileKeys = new ArrayList<>( + payload.globalProfileKeys().length * containerKeys.length * payload.profileTypes().length); + + for (GlobalProfileKey globalProfileKey : payload.globalProfileKeys()) { + for (ContainerKey containerKey : containerKeys) { + for (ProfileType profileType : payload.profileTypes()) { + profileKeys.add(ProfileKey.of( + containerKey.getContainerType(), + containerKey.getDataName(), + profileType, + globalProfileKey.getPlayerUUID(), + globalProfileKey.getPlayerName())); + } + } + } + return profileKeys; + } + + private ContainerKey[] includeGroupsWorlds(ContainerKey[] containerKeys) { + Set containerKeyList = Sets.newHashSet(containerKeys); + for (ContainerKey containerKey : containerKeys) { + if (containerKey.getContainerType() != ContainerType.GROUP) { + continue; + } + WorldGroup group = worldGroupManager.getGroup(containerKey.getDataName()); + if (group == null) { + continue; + } + containerKeyList.addAll(group.getWorlds().stream() + .map(worldName -> ContainerKey.create(ContainerType.WORLD, worldName)) + .toList()); + } + return containerKeyList.toArray(ContainerKey[]::new); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfilesPayload.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfilesPayload.java new file mode 100644 index 00000000..1ee22426 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/PlayerProfilesPayload.java @@ -0,0 +1,39 @@ +package org.mvplugins.multiverse.inventories.profile.bulkedit; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.profile.key.ContainerKey; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +@ApiStatus.Experimental +public record PlayerProfilesPayload(@NotNull GlobalProfileKey[] globalProfileKeys, + @NotNull ContainerKey[] containerKeys, + @NotNull ProfileType[] profileTypes, + boolean includeGroupsWorlds) { + + public Map> getSummary() { + return Map.of( + "Players", Arrays.stream(globalProfileKeys) + .map(GlobalProfileKey::getPlayerName) + .toList(), + "Worlds", Arrays.stream(containerKeys) + .filter(c -> c.getContainerType() == ContainerType.WORLD) + .map(ContainerKey::getDataName) + .toList(), + "Groups", Arrays.stream(containerKeys) + .filter(c -> c.getContainerType() == ContainerType.GROUP) + .map(ContainerKey::getDataName) + .toList(), + "Profile Types", Arrays.stream(profileTypes) + .map(ProfileType::getName) + .map(String::toLowerCase) + .toList() + ); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/package-info.java new file mode 100644 index 00000000..bae46c1a --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/bulkedit/package-info.java @@ -0,0 +1,4 @@ +@ApiStatus.Experimental +package org.mvplugins.multiverse.inventories.profile.bulkedit; + +import org.jetbrains.annotations.ApiStatus; \ No newline at end of file diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java new file mode 100644 index 00000000..3ed7eccf --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainer.java @@ -0,0 +1,133 @@ +package org.mvplugins.multiverse.inventories.profile.container; + +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.ProfileCacheManager; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.key.ProfileFileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.mvplugins.multiverse.inventories.util.FutureNow; + +import java.util.Collection; +import java.util.concurrent.CompletableFuture; + +/** + * A container for player profiles in a given world or world group (based on {@link #getContainerType()}), + *
+ * Players may have separate profiles per game mode within this container if game mode profiles are enabled. + */ +public final class ProfileContainer { + + private final String name; + private final ContainerType type; + private final ProfileDataSource profileDataSource; + private final ProfileCacheManager profileCacheManager; + + ProfileContainer(MultiverseInventories inventories, String name, ContainerType type) { + this.name = name; + this.type = type; + this.profileDataSource = inventories.getServiceLocator().getService(ProfileDataSource.class); + this.profileCacheManager = inventories.getServiceLocator().getService(ProfileCacheManager.class); + } + + public Collection listPlayerProfileNames() { + return profileDataSource.listPlayerProfileNames(getContainerType(), getContainerName()); + } + + public ProfileKey getProfileKey(Player player) { + return getProfileKey(ProfileTypes.forPlayer(player), player); + } + + public ProfileKey getProfileKey(ProfileType profileType, OfflinePlayer player) { + return ProfileKey.of(getContainerType(), getContainerName(), profileType, player); + } + + public CompletableFuture getPlayerData(Player player) { + return getPlayerData(ProfileTypes.forPlayer(player), player); + } + + public CompletableFuture getPlayerData(ProfileType profileType, OfflinePlayer player) { + return profileDataSource.getPlayerProfile(getProfileKey(profileType, player)); + } + + /** + * Retrieves the profile for the given player. + *

If game mode profiles are enabled, the profile for their current game mode will be returned, otherwise their + * survival profile will be returned.

+ * + * @param player Player to get profile for. + * @return The profile for the given player. + */ + public PlayerProfile getPlayerProfileNow(Player player) { + return getPlayerProfileNow(ProfileTypes.forPlayer(player), player); + } + + /** + * Retrieves the profile of the given type for the given player. + * + * @param profileType The type of profile to get data for, typically Survival or Creative. + * @param player Player to get profile for. + * @return The profile of the given type for the given player. + */ + public PlayerProfile getPlayerProfileNow(ProfileType profileType, OfflinePlayer player) { + return FutureNow.get(profileDataSource.getPlayerProfile(ProfileKey.of( + getContainerType(), + getContainerName(), + profileType, + player))); + } + + /** + * Removes all of the profile data for a given player in this profile container. + * + * @param player Player to remove data for. + * @return + */ + public CompletableFuture deletePlayerFile(OfflinePlayer player) { + return profileDataSource.deletePlayerFile(ProfileFileKey.of(type, name, player)); + } + + /** + * Removes the profile data for a specific type of profile in this profile container. + * + * @param profileType The type of profile to remove data for. + * @param player Player to remove data for. + * @return + */ + public CompletableFuture deletePlayerProfile(ProfileType profileType, OfflinePlayer player) { + return profileDataSource.deletePlayerProfile(ProfileKey.of(type, name, profileType, player)); + } + + /** + * Returns the name of this profile container which is primarily used for persistence purposes. + *

The name reflects the world name if this is a world profile container, or the arbitrary group name if + * this is a world group profile container.

+ * + * @return The name to use to look up Data. + */ + public String getContainerName() { + return name; + } + + /** + * Returns the container type for this container. + * + * @return the container type. + */ + public ContainerType getContainerType() { + return type; + } + + /** + * Clears all cached data in the container. + */ + public void clearContainerCache() { + profileCacheManager.clearPlayerProfileCache(key -> + key.getContainerType().equals(type) && key.getDataName().equals(name)); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStore.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStore.java new file mode 100644 index 00000000..b99ae70b --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStore.java @@ -0,0 +1,56 @@ +package org.mvplugins.multiverse.inventories.profile.container; + +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; + +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; + +/** + * A utility for storing and retrieving profile containers. + */ +public final class ProfileContainerStore { + + private final Map containers = new WeakHashMap<>(); + + private final MultiverseInventories inventories; + private final ContainerType containerType; + private final ProfileDataSource profileDataSource; + + ProfileContainerStore(MultiverseInventories inventories, ContainerType containerType) { + this.inventories = inventories; + this.containerType = containerType; + this.profileDataSource = inventories.getServiceLocator().getService(ProfileDataSource.class); + } + + public List listContainerDataNames() { + return profileDataSource.listContainerDataNames(containerType); + } + + /** + * Returns the profile container for the given name. + * + * @param containerName Name of the profile container to retrieve. + * @return the profile container for given name. + */ + public ProfileContainer getContainer(String containerName) { + ProfileContainer container = this.containers.get(containerName.toLowerCase()); + if (container == null) { + container = new ProfileContainer(inventories, containerName, containerType); + addContainer(container); + } + return container; + } + + /** + * Adds a profile container to the store. + * + * @param container profile container to add. + */ + private void addContainer(ProfileContainer container) { + this.containers.put(container.getContainerName().toLowerCase(), container); + } +} + diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStoreProvider.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStoreProvider.java new file mode 100644 index 00000000..630db106 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/container/ProfileContainerStoreProvider.java @@ -0,0 +1,40 @@ +package org.mvplugins.multiverse.inventories.profile.container; + +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; + +import java.util.EnumMap; +import java.util.Map; + +/** + * A provider for ProfileContainerStore instances based on ContainerType. + */ +@Service +public class ProfileContainerStoreProvider { + + private final Map stores; + private final MultiverseInventories inventories; + + @Inject + ProfileContainerStoreProvider(@NotNull MultiverseInventories inventories) { + this.inventories = inventories; + stores = new EnumMap<>(ContainerType.class); + } + + /** + * Gets the store for a given container type. + * + * @param type the container type + * @return the store + */ + public ProfileContainerStore getStore(ContainerType type) { + return stores.computeIfAbsent(type, t -> new ProfileContainerStore(inventories, t)); + } + + public void clearCache() { + stores.clear(); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/data/EmptyProfileData.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/data/EmptyProfileData.java new file mode 100644 index 00000000..ff9d101f --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/data/EmptyProfileData.java @@ -0,0 +1,44 @@ +package org.mvplugins.multiverse.inventories.profile.data; + +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.share.Shares; + +import java.util.Map; + +final class EmptyProfileData implements ProfileData { + private static final EmptyProfileData INSTANCE = new EmptyProfileData(); + + static EmptyProfileData getInstance() { + return INSTANCE; + } + + @Override + public T get(Sharable sharable) { + return null; + } + + @Override + public void set(Sharable sharable, T value) { + throw new UnsupportedOperationException(); + } + + @Override + public Map getData() { + return Map.of(); + } + + @Override + public void update(ProfileData snapshot) { + throw new UnsupportedOperationException(); + } + + @Override + public void update(ProfileData snapshot, Shares shares) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isEmpty() { + return true; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/data/PlayerProfile.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/data/PlayerProfile.java new file mode 100644 index 00000000..be47b959 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/data/PlayerProfile.java @@ -0,0 +1,88 @@ +package org.mvplugins.multiverse.inventories.profile.data; + +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey; +import org.mvplugins.multiverse.inventories.profile.key.ProfileType; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; + +import java.util.UUID; + +/** + * Contains all the world/group specific data for a player. + */ +public final class PlayerProfile extends ProfileDataSnapshot { + + public static PlayerProfile newProfile(ProfileKey profileKey) { + return new PlayerProfile( + profileKey.getContainerType(), + profileKey.getDataName(), + profileKey.getProfileType(), + profileKey.getPlayerUUID(), + profileKey.getPlayerName() + ); + } + + private final ContainerType containerType; + private final String containerName; + private final ProfileType profileType; + private final UUID playerUUID; + private final String playerName; + + private PlayerProfile(ContainerType containerType, String containerName, ProfileType profileType, UUID playerUUID, String playerName) { + super(); + this.containerType = containerType; + this.profileType = profileType; + this.containerName = containerName; + this.playerUUID = playerUUID; + this.playerName = playerName; + } + + /** + * @return The container type of profile, a group or world. + */ + public ContainerType getContainerType() { + return this.containerType; + } + + /** + * @return The name of the container, world or group, containing this profile. + */ + public String getContainerName() { + return this.containerName; + } + + /** + * @return the Player uuid associated with this profile. + */ + public UUID getPlayerUUID() { + return this.playerUUID; + } + + /** + * @return the Player name associated with this profile. + */ + public String getPlayerName() { + return this.playerName; + } + + /** + * @return The type of profile this object represents. + */ + public ProfileType getProfileType() { + return this.profileType; + } + + public PlayerProfile clone() { + return (PlayerProfile) super.clone(); + } + + @Override + public String toString() { + return "PlayerProfile{" + + "playerUUID=" + playerUUID + + ", playerName='" + playerName + '\'' + + ", containerType=" + containerType + + ", containerName='" + containerName + '\'' + + ", profileType=" + profileType + + '}'; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/data/ProfileData.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/data/ProfileData.java new file mode 100644 index 00000000..bede58c1 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/data/ProfileData.java @@ -0,0 +1,59 @@ +package org.mvplugins.multiverse.inventories.profile.data; + +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.share.Shares; + +import java.util.Map; + +public interface ProfileData { + + /** + * Gets an immutable empty profile data constant. + * + * @return An empty profile data. + */ + static ProfileData empty() { + return EmptyProfileData.getInstance(); + } + + /** + * Retrieves the profile's value of the {@link Sharable} passed in. + * + * @param sharable Represents the key for the data wanted from the profile. + * @param This indicates the type of return value to be expected. + * @return The value of the sharable for this profile. Null if no value is set. + */ + T get(Sharable sharable); + + /** + * Sets the profile's value for the {@link Sharable} passed in. + * + * @param sharable Represents the key for the data to store. + * @param value The value of the data. + * @param The type of value to be expected. + */ + void set(Sharable sharable, T value); + + Map getData(); + + /** + * Updates this profile with the data from another profile data. + * + * @param snapshot The snapshot data to update from. + */ + void update(ProfileData snapshot); + + /** + * Updates this profile with the data from another profile data for a specific set of {@link Sharable}s. + * @param snapshot The snapshot data to update from. + * @param shares The set of {@link Sharable}s to update. + */ + void update(ProfileData snapshot, Shares shares); + + /** + * Checks whether this profile contains any data. + * + * @return True if the profile is empty. + */ + boolean isEmpty(); +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/data/ProfileDataSnapshot.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/data/ProfileDataSnapshot.java new file mode 100644 index 00000000..a3f900be --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/data/ProfileDataSnapshot.java @@ -0,0 +1,61 @@ +package org.mvplugins.multiverse.inventories.profile.data; + +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; + +import java.util.HashMap; +import java.util.Map; + +public sealed class ProfileDataSnapshot implements Cloneable, ProfileData permits PlayerProfile { + + private final Map data; + + public ProfileDataSnapshot() { + this.data = new HashMap<>(Sharables.all().size(), 1); + } + + @Override + public T get(Sharable sharable) { + return (T) this.data.get(sharable); + } + + @Override + public void set(Sharable sharable, T value) { + this.data.put(sharable, value); + } + + @Override + public Map getData() { + return data; + } + + @Override + public void update(ProfileData snapshot) { + this.data.putAll(snapshot.getData()); + } + + @Override + public void update(ProfileData snapshot, Shares shares) { + shares.forEach(sharable -> { + Object data = snapshot.getData().get(sharable); + if (data != null) { + this.data.put(sharable, data); + } + }); + } + + @Override + public boolean isEmpty() { + return data.isEmpty(); + } + + @Override + public ProfileDataSnapshot clone() { + try { + return (ProfileDataSnapshot) super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/data/SingleSharableData.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/data/SingleSharableData.java new file mode 100644 index 00000000..3d93288e --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/data/SingleSharableData.java @@ -0,0 +1,53 @@ +package org.mvplugins.multiverse.inventories.profile.data; + +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.share.Shares; + +import java.util.Map; + +public final class SingleSharableData implements ProfileData { + + private final Sharable sharable; + private T value; + + public SingleSharableData(Sharable sharable, T value) { + this.sharable = sharable; + this.value = value; + } + + @Override + public S get(Sharable sharable) { + return sharable.equals(this.sharable) ? (S) this.value : null; + } + + @Override + public void set(Sharable sharable, S value) { + if (sharable.equals(this.sharable)) { + this.value = (T) value; + } + } + + @Override + public Map getData() { + return Map.of(sharable, value); + } + + @Override + public void update(ProfileData snapshot) { + if (snapshot.get(sharable) != null) { + this.value = snapshot.get(sharable); + } + } + + @Override + public void update(ProfileData snapshot, Shares shares) { + if (shares.contains(sharable)) { + this.value = snapshot.get(sharable); + } + } + + @Override + public boolean isEmpty() { + return value == null; + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/AbstractWorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java similarity index 58% rename from src/main/java/com/onarandombox/multiverseinventories/AbstractWorldGroupManager.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java index 59ee14f5..fc477965 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/AbstractWorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/AbstractWorldGroupManager.java @@ -1,33 +1,54 @@ -package com.onarandombox.multiverseinventories; +package org.mvplugins.multiverse.inventories.profile.group; import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.profile.WorldGroupManager; -import com.onarandombox.multiverseinventories.profile.GroupingConflict; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.share.Shares; -import com.onarandombox.multiverseinventories.locale.Message; +import org.jvnet.hk2.annotations.Contract; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld; +import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; import org.bukkit.Bukkit; import org.bukkit.World; -import org.bukkit.command.CommandSender; +import org.mvplugins.multiverse.inventories.util.MVInvi18n; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace; + /** * Abstract implementation of GroupManager with no persistence of groups. */ -abstract class AbstractWorldGroupManager implements WorldGroupManager { +@Contract +abstract sealed class AbstractWorldGroupManager implements WorldGroupManager permits YamlWorldGroupManager { static final String DEFAULT_GROUP_NAME = "default"; protected final Map groupNamesMap = new LinkedHashMap<>(); protected final MultiverseInventories plugin; - - public AbstractWorldGroupManager(final MultiverseInventories plugin) { + protected final MVCommandManager commandManager; + protected final InventoriesConfig inventoriesConfig; + protected final ProfileContainerStoreProvider profileContainerStoreProvider; + protected final WorldManager worldManager; + + public AbstractWorldGroupManager( + @NotNull MultiverseInventories plugin, + @NotNull MVCommandManager commandManager, + @NotNull InventoriesConfig config, + @NotNull ProfileContainerStoreProvider profileContainerStoreProvider, + @NotNull WorldManager worldManager) { this.plugin = plugin; + this.commandManager = commandManager; + this.inventoriesConfig = config; + this.profileContainerStoreProvider = profileContainerStoreProvider; + this.worldManager = worldManager; } /** @@ -43,7 +64,7 @@ public WorldGroup getGroup(String groupName) { */ @Override public List getGroups() { - return Collections.unmodifiableList(new ArrayList(getGroupNames().values())); + return List.copyOf(getGroupNames().values()); } /** @@ -59,8 +80,8 @@ public List getGroupsForWorld(String worldName) { } } // Only use the default group for worlds managed by MV-Core - if (worldGroups.isEmpty() && plugin.getMVIConfig().isDefaultingUngroupedWorlds() && - plugin.getCore().getMVWorldManager().isMVWorld(worldName)) { + if (worldGroups.isEmpty() && inventoriesConfig.getDefaultUngroupedWorlds() && + this.worldManager.isWorld(worldName)) { Logging.finer("Returning default group for world: " + worldName); worldGroups.add(getDefaultGroup()); } @@ -71,8 +92,9 @@ public List getGroupsForWorld(String worldName) { * {@inheritDoc} */ @Override - public boolean hasGroup(String worldName) { - return !getGroupsForWorld(worldName).isEmpty(); + public boolean hasConfiguredGroup(String worldName) { + return groupNamesMap.values().stream() + .anyMatch(worldGroup -> worldGroup.getWorlds().contains(worldName)); } /** @@ -84,21 +106,10 @@ protected Map getGroupNames() { return groupNamesMap; } - /** - * {@inheritDoc} - */ - @Override - @Deprecated - public void addGroup(final WorldGroup worldGroup, final boolean persist) { - updateGroup(worldGroup); - } - @Override public void updateGroup(final WorldGroup worldGroup) { getGroupNames().put(worldGroup.getName().toLowerCase(), worldGroup); - } - - protected void persistGroup(final WorldGroup worldGroup) { + worldGroup.recalculateApplicableShares(); } /** @@ -117,15 +128,7 @@ public WorldGroup newEmptyGroup(String name) { if (getGroup(name) != null) { return null; } - return new WorldGroup(plugin, name); - } - - /** - * {@inheritDoc} - */ - @Override - @Deprecated - public void setGroups(List worldGroups) { + return new WorldGroup(this, profileContainerStoreProvider, inventoriesConfig, name); } /** @@ -136,24 +139,24 @@ public void createDefaultGroup() { if (getGroup(DEFAULT_GROUP_NAME) != null) { return; } - World defaultWorld = Bukkit.getWorlds().get(0); + World defaultWorld = worldManager.getDefaultWorld() + .flatMap(LoadedMultiverseWorld::getBukkitWorld) + .fold(() -> Bukkit.getWorlds().get(0), world -> world); World defaultNether = Bukkit.getWorld(defaultWorld.getName() + "_nether"); World defaultEnd = Bukkit.getWorld(defaultWorld.getName() + "_the_end"); - WorldGroup worldGroup = new WorldGroup(plugin, DEFAULT_GROUP_NAME); + WorldGroup worldGroup = new WorldGroup(this, profileContainerStoreProvider, inventoriesConfig, DEFAULT_GROUP_NAME); worldGroup.getShares().mergeShares(Sharables.allOf()); worldGroup.addWorld(defaultWorld); - StringBuilder worlds = new StringBuilder().append(defaultWorld.getName()); if (defaultNether != null) { worldGroup.addWorld(defaultNether); - worlds.append(", ").append(defaultNether.getName()); } if (defaultEnd != null) { worldGroup.addWorld(defaultEnd); - worlds.append(", ").append(defaultEnd.getName()); } updateGroup(worldGroup); - plugin.getMVIConfig().save(); - Logging.info("Created a default group for you containing all of your default worlds: " + worlds.toString()); + worldGroup.recalculateApplicableShares(); + Logging.info("Created a default group for you containing all of your default worlds: " + + String.join(", ", worldGroup.getWorlds())); } /** @@ -175,7 +178,7 @@ public WorldGroup getDefaultGroup() { */ @Override public List checkGroups() { - List conflicts = new ArrayList(); + List conflicts = new ArrayList<>(); Map previousConflicts = new HashMap<>(); for (WorldGroup checkingGroup : getGroupNames().values()) { for (String worldName : checkingGroup.getWorlds()) { @@ -215,35 +218,29 @@ public List checkGroups() { * {@inheritDoc} */ @Override - public void checkForConflicts(CommandSender sender) { - String message = plugin.getMessager().getMessage(Message.CONFLICT_CHECKING); - if (sender != null) { - plugin.getMessager().sendMessage(sender, message); + public void checkForConflicts(MVCommandIssuer issuer) { + if (issuer == null) { + issuer = commandManager.getCommandIssuer(Bukkit.getConsoleSender()); } - Logging.fine(message); - List conflicts = plugin.getGroupManager().checkGroups(); + + issuer.sendInfo(MVInvi18n.CONFLICT_CHECKING); + List conflicts = checkGroups(); for (GroupingConflict conflict : conflicts) { - message = plugin.getMessager().getMessage(Message.CONFLICT_RESULTS, - conflict.getFirstGroup().getName(), conflict.getSecondGroup().getName(), - conflict.getConflictingShares().toString(), conflict.getWorldsString()); - if (sender != null) { - plugin.getMessager().sendMessage(sender, message); - } - Logging.info(message); + issuer.sendInfo(MVInvi18n.CONFLICT_RESULTS, + replace("{group1}").with(conflict.getFirstGroup().getName()), + replace("{group2}").with(conflict.getSecondGroup().getName()), + replace("{shares}").with(conflict.getConflictingShares().toString()), + replace("{worlds}").with(conflict.getWorldsString())); } if (!conflicts.isEmpty()) { - message = plugin.getMessager().getMessage(Message.CONFLICT_FOUND); - if (sender != null) { - plugin.getMessager().sendMessage(sender, message); - } - Logging.info(message); + issuer.sendInfo(MVInvi18n.CONFLICT_FOUND); } else { - message = plugin.getMessager().getMessage(Message.CONFLICT_NOT_FOUND); - if (sender != null) { - plugin.getMessager().sendMessage(sender, message); - } - Logging.fine(message); + issuer.sendInfo(MVInvi18n.CONFLICT_NOTFOUND); } } -} + @Override + public void recalculateApplicableShares() { + getGroupNames().values().forEach(WorldGroup::recalculateApplicableShares); + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/profile/GroupingConflict.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/GroupingConflict.java similarity index 82% rename from src/main/java/com/onarandombox/multiverseinventories/profile/GroupingConflict.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/group/GroupingConflict.java index cd30b4b5..c42eba65 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/profile/GroupingConflict.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/GroupingConflict.java @@ -1,72 +1,71 @@ -package com.onarandombox.multiverseinventories.profile; - -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.share.Shares; - -import java.util.ArrayList; -import java.util.List; - -/** - * A data class to hold information about any conflicts between world groups. - */ -public final class GroupingConflict { - - private WorldGroup groupOne; - private WorldGroup groupTwo; - private Shares conflictingShares; - - public GroupingConflict(WorldGroup groupOne, WorldGroup groupTwo, Shares conflictingShares) { - this.groupOne = groupOne; - this.groupTwo = groupTwo; - this.conflictingShares = conflictingShares; - } - - /** - * @return The first group in the conflict. - */ - public WorldGroup getFirstGroup() { - return this.groupOne; - } - - /** - * @return The second group in the conflict. - */ - public WorldGroup getSecondGroup() { - return this.groupTwo; - } - - /** - * @return The shares that are causing a conflict. - */ - public Shares getConflictingShares() { - return this.conflictingShares; - } - - /** - * @return The worlds the two groups share. - */ - public List getConflictingWorlds() { - List worlds = new ArrayList(); - for (String world : this.getFirstGroup().getWorlds()) { - if (this.getSecondGroup().getWorlds().contains(world)) { - worlds.add(world); - } - } - return worlds; - } - - /** - * @return The worlds the two groups share as a single string. - */ - public String getWorldsString() { - StringBuilder builder = new StringBuilder(); - for (String world : this.getConflictingWorlds()) { - if (!builder.toString().isEmpty()) { - builder.append(", "); - } - builder.append(world); - } - return builder.toString(); - } -} - +package org.mvplugins.multiverse.inventories.profile.group; + +import org.mvplugins.multiverse.inventories.share.Shares; + +import java.util.ArrayList; +import java.util.List; + +/** + * A data class to hold information about any conflicts between world groups. + */ +public final class GroupingConflict { + + private final WorldGroup groupOne; + private final WorldGroup groupTwo; + private final Shares conflictingShares; + + public GroupingConflict(WorldGroup groupOne, WorldGroup groupTwo, Shares conflictingShares) { + this.groupOne = groupOne; + this.groupTwo = groupTwo; + this.conflictingShares = conflictingShares; + } + + /** + * @return The first group in the conflict. + */ + public WorldGroup getFirstGroup() { + return this.groupOne; + } + + /** + * @return The second group in the conflict. + */ + public WorldGroup getSecondGroup() { + return this.groupTwo; + } + + /** + * @return The shares that are causing a conflict. + */ + public Shares getConflictingShares() { + return this.conflictingShares; + } + + /** + * @return The worlds the two groups share. + */ + public List getConflictingWorlds() { + List worlds = new ArrayList(); + for (String world : this.getFirstGroup().getWorlds()) { + if (this.getSecondGroup().getWorlds().contains(world)) { + worlds.add(world); + } + } + return worlds; + } + + /** + * @return The worlds the two groups share as a single string. + */ + public String getWorldsString() { + StringBuilder builder = new StringBuilder(); + for (String world : this.getConflictingWorlds()) { + if (!builder.toString().isEmpty()) { + builder.append(", "); + } + builder.append(world); + } + return builder.toString(); + } +} + diff --git a/src/main/java/com/onarandombox/multiverseinventories/WorldGroup.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java similarity index 71% rename from src/main/java/com/onarandombox/multiverseinventories/WorldGroup.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java index ef42ac42..d0829c4e 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/WorldGroup.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroup.java @@ -1,247 +1,288 @@ -package com.onarandombox.multiverseinventories; - -import com.onarandombox.multiverseinventories.share.Sharable; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.share.Shares; -import com.onarandombox.multiverseinventories.profile.container.ProfileContainer; -import org.bukkit.World; -import org.bukkit.event.EventPriority; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -public final class WorldGroup { - - private final MultiverseInventories plugin; - private final String name; - private final HashSet worlds = new HashSet<>(); - private final Shares shares = Sharables.noneOf(); - - private String spawnWorld = null; - private EventPriority spawnPriority = EventPriority.NORMAL; - - WorldGroup(final MultiverseInventories inventories, final String name) { - this.plugin = inventories; - this.name = name; - } - - /** - * Get the name of this World Group. - * - * @return Name of this World Group. - */ - public String getName() { - return this.name; - } - - /** - * Adds a world to this world group and updates it in the Config. - * - * @param worldName The name of the world to add. - */ - public void addWorld(String worldName) { - this.addWorld(worldName, true); - } - - /** - * Adds a world to this world group and optionally updates it in the Config. - * - * @param worldName The name of the world to add. - * @param updateConfig True to update this group in the config. - */ - public void addWorld(String worldName, boolean updateConfig) { - this.getWorlds().add(worldName.toLowerCase()); - if (updateConfig) { - plugin.getGroupManager().updateGroup(this); - } - } - - /** - * Convenience method to add a {@link org.bukkit.World} to this World Group. - * - * @param world The world to add. - */ - public void addWorld(World world) { - this.addWorld(world.getName()); - } - - /** - * Convenience method to add multiple worlds to this World Group and updates it in the Config. - * - * @param worlds A collections of worlds to add. - */ - public void addWorlds(Collection worlds) { - this.addWorlds(worlds, true); - } - - /** - * Convenience method to add multiple worlds to this World Group. - * - * @param worlds A collections of worlds to add. - * @param updateConfig True to update this group in the config. - */ - public void addWorlds(Collection worlds, boolean updateConfig) { - worlds.forEach(worldName -> this.addWorld(worldName, false)); - if (updateConfig) { - this.plugin.getGroupManager().updateGroup(this); - } - } - - /** - * Removes a world from this world group and updates the group in the Config. - * - * @param worldName The name of the world to remove. - */ - public void removeWorld(String worldName) { - this.removeWorld(worldName, true); - } - - /** - * Removes a world from this world group and optionally updates it in the Config. - * - * @param worldName The name of the world to remove. - * @param updateConfig True to update this group in the config. - */ - public void removeWorld(String worldName, boolean updateConfig) { - this.getWorlds().remove(worldName.toLowerCase()); - if (updateConfig) { - plugin.getGroupManager().updateGroup(this); - } - } - - /** - * Convenience method to remove a {@link org.bukkit.World} from this World Group. - * - * @param world The world to remove. - */ - public void removeWorld(World world) { - this.removeWorld(world.getName()); - } - - /** - * Remove all the worlds in this World Group. - */ - public void removeAllWorlds() { - this.removeAllWorlds(true); - } - - /** - * Remove all the worlds in this World Group. - * - * @param updateConfig True to update this group in the config. - */ - public void removeAllWorlds(boolean updateConfig) { - this.worlds.clear(); - if (updateConfig) { - this.plugin.getGroupManager().updateGroup(this); - } - } - - /** - * Retrieves all of the worlds in this World Group. - * - * @return The worlds of this World Group. - */ - public Set getWorlds() { - return this.worlds; - } - - /** - * Checks if this group is sharing sharable. This will check both shares and negative shares of the group. - * This is the preferred method for checking if a group shares something as shares may contain ALL shares while - * ones indicated in negative shares means those aren't actually shared. - * - * @param sharable Sharable to check if sharing. - * @return true is the sharable is shared for this group. - */ - public boolean isSharing(Sharable sharable) { - return getShares().isSharing(sharable); - } - - /** - * Retrieves the shares for this World Group. Any changes to this group must be subsequently saved to the data - * source for the changes to be permanent. - * - * @return The shares for this World Group. - */ - public Shares getShares() { - return this.shares; - } - - /** - * @param worldName Name of world to check for. - * @return True if specified world is part of this group. - */ - public boolean containsWorld(String worldName) { - return this.getWorlds().contains(worldName.toLowerCase()); - } - - /** - * @return The name of the world that will be used as the spawn for this group. - * Or null if no world was specified as the group spawn world. - */ - public String getSpawnWorld() { - return this.spawnWorld; - } - - /** - * @param worldName The name of the world to set this groups spawn to. - */ - public void setSpawnWorld(String worldName) { - this.spawnWorld = worldName.toLowerCase(); - } - - /** - * @return The priority for the respawn event that this spawn will act on. - */ - public EventPriority getSpawnPriority() { - return this.spawnPriority; - } - - /** - * @param priority The priority that will be used for respawning the player at - * this group's spawn location if there is one set. - */ - public void setSpawnPriority(EventPriority priority) { - this.spawnPriority = priority; - } - - /** - * Is this a default group. - * - * @return true if this is the default group. - */ - public boolean isDefault() { - return AbstractWorldGroupManager.DEFAULT_GROUP_NAME.equals(getName()); - } - - /** - * Returns the profile container for this group. - * - * @return the profile container for this group. - */ - public ProfileContainer getGroupProfileContainer() { - return plugin.getGroupProfileContainerStore().getContainer(getName()); - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append(this.getName()).append(": {Worlds: ["); - String[] worldsString = this.getWorlds().toArray(new String[this.getWorlds().size()]); - for (int i = 0; i < worldsString.length; i++) { - if (i != 0) { - builder.append(", "); - } - builder.append(worldsString[i]); - } - builder.append("], Shares: [").append(this.getShares().toString()).append("]"); - if (this.getSpawnWorld() != null) { - builder.append(", Spawn World: ").append(this.getSpawnWorld()); - builder.append(", Spawn Priority: ").append(this.getSpawnPriority().toString()); - } - builder.append("}"); - return builder.toString(); - } -} +package org.mvplugins.multiverse.inventories.profile.group; + +import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.key.ContainerType; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.share.Sharable; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.share.Shares; +import org.bukkit.World; +import org.bukkit.event.EventPriority; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +public final class WorldGroup { + + private final WorldGroupManager worldGroupManager; + private final ProfileContainerStoreProvider profileContainerStoreProvider; + private final InventoriesConfig inventoriesConfig; + private final String name; + private final HashSet worlds = new HashSet<>(); + private final Shares shares = Sharables.noneOf(); + private final Shares disabledShares = Sharables.noneOf(); + + private Shares applicableShares = Sharables.noneOf(); + private String spawnWorld = null; + private EventPriority spawnPriority = EventPriority.NORMAL; + + WorldGroup( + final WorldGroupManager worldGroupManager, + final ProfileContainerStoreProvider profileContainerStoreProvider, + final InventoriesConfig inventoriesConfig, + final String name) { + this.worldGroupManager = worldGroupManager; + this.profileContainerStoreProvider = profileContainerStoreProvider; + this.inventoriesConfig = inventoriesConfig; + this.name = name; + } + + /** + * Get the name of this World Group. + * + * @return Name of this World Group. + */ + public String getName() { + return this.name; + } + + /** + * Adds a world to this world group and updates it in the Config. + * + * @param worldName The name of the world to add. + */ + public void addWorld(String worldName) { + this.addWorld(worldName, true); + } + + /** + * Adds a world to this world group and optionally updates it in the Config. + * + * @param worldName The name of the world to add. + * @param updateConfig True to update this group in the config. + */ + public void addWorld(String worldName, boolean updateConfig) { + this.getWorlds().add(worldName.toLowerCase()); + if (updateConfig) { + worldGroupManager.updateGroup(this); + } + } + + /** + * Convenience method to add a {@link org.bukkit.World} to this World Group. + * + * @param world The world to add. + */ + public void addWorld(World world) { + this.addWorld(world.getName()); + } + + /** + * Convenience method to add multiple worlds to this World Group and updates it in the Config. + * + * @param worlds A collections of worlds to add. + */ + public void addWorlds(Collection worlds) { + this.addWorlds(worlds, true); + } + + /** + * Convenience method to add multiple worlds to this World Group. + * + * @param worlds A collections of worlds to add. + * @param updateConfig True to update this group in the config. + */ + public void addWorlds(Collection worlds, boolean updateConfig) { + worlds.forEach(worldName -> this.addWorld(worldName, false)); + if (updateConfig) { + worldGroupManager.updateGroup(this); + } + } + + /** + * Removes a world from this world group and updates the group in the Config. + * + * @param worldName The name of the world to remove. + */ + public void removeWorld(String worldName) { + this.removeWorld(worldName, true); + } + + /** + * Removes a world from this world group and optionally updates it in the Config. + * + * @param worldName The name of the world to remove. + * @param updateConfig True to update this group in the config. + */ + public void removeWorld(String worldName, boolean updateConfig) { + this.getWorlds().remove(worldName.toLowerCase()); + if (updateConfig) { + worldGroupManager.updateGroup(this); + } + } + + /** + * Convenience method to remove a {@link org.bukkit.World} from this World Group. + * + * @param world The world to remove. + */ + public void removeWorld(World world) { + this.removeWorld(world.getName()); + } + + /** + * Removes multiple worlds from this World Group. + * + * @param removeWorlds A collection of world names to remove. + * @return True if any of the worlds were removed. + */ + public boolean removeWorlds(Collection removeWorlds) { + return this.getWorlds().removeAll(removeWorlds); + } + + /** + * Remove all the worlds in this World Group. + */ + public void removeAllWorlds() { + this.removeAllWorlds(true); + } + + /** + * Remove all the worlds in this World Group. + * + * @param updateConfig True to update this group in the config. + */ + public void removeAllWorlds(boolean updateConfig) { + this.worlds.clear(); + if (updateConfig) { + worldGroupManager.updateGroup(this); + } + } + + /** + * Retrieves all of the worlds in this World Group. + * + * @return The worlds of this World Group. + */ + public Set getWorlds() { + return this.worlds; + } + + /** + * Checks if this group is sharing sharable. This will check both shares and negative shares of the group. + * This is the preferred method for checking if a group shares something as shares may contain ALL shares while + * ones indicated in negative shares means those aren't actually shared. + * + * @param sharable Sharable to check if sharing. + * @return true is the sharable is shared for this group. + */ + public boolean isSharing(Sharable sharable) { + return getApplicableShares().isSharing(sharable); + } + + /** + * Retrieves the shares for this World Group. Any changes to this group must be subsequently saved to the data + * source for the changes to be permanent. + * + * @return The shares for this World Group. + */ + public Shares getShares() { + return this.shares; + } + + public Shares getDisabledShares() { + return this.disabledShares; + } + + public Shares getApplicableShares() { + return this.applicableShares; + } + + public void recalculateApplicableShares() { + this.applicableShares = Sharables.fromShares(this.shares); + this.applicableShares.removeAll(this.disabledShares); + Shares disabledOptionalShares = Sharables.optionalOf(); + disabledOptionalShares.removeAll(this.inventoriesConfig.getActiveOptionalShares()); + this.applicableShares.removeAll(disabledOptionalShares); + Logging.finest("Applicable shares for " + this.getName() + ": " + this.applicableShares); + } + + /** + * @param worldName Name of world to check for. + * @return True if specified world is part of this group. + */ + public boolean containsWorld(String worldName) { + return this.getWorlds().contains(worldName.toLowerCase()); + } + + /** + * @return The name of the world that will be used as the spawn for this group. + * Or null if no world was specified as the group spawn world. + */ + public String getSpawnWorld() { + return this.spawnWorld; + } + + /** + * @param worldName The name of the world to set this groups spawn to. + */ + public void setSpawnWorld(String worldName) { + this.spawnWorld = worldName.toLowerCase(); + } + + /** + * @return The priority for the respawn event that this spawn will act on. + */ + public EventPriority getSpawnPriority() { + return this.spawnPriority; + } + + /** + * @param priority The priority that will be used for respawning the player at + * this group's spawn location if there is one set. + */ + public void setSpawnPriority(EventPriority priority) { + this.spawnPriority = priority; + } + + /** + * Is this a default group. + * + * @return true if this is the default group. + */ + public boolean isDefault() { + return AbstractWorldGroupManager.DEFAULT_GROUP_NAME.equals(getName()); + } + + /** + * Returns the profile container for this group. + * + * @return the profile container for this group. + */ + public ProfileContainer getGroupProfileContainer() { + return profileContainerStoreProvider.getStore(ContainerType.GROUP).getContainer(getName()); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(this.getName()).append(": {Worlds: ["); + String[] worldsString = this.getWorlds().toArray(new String[0]); + for (int i = 0; i < worldsString.length; i++) { + if (i != 0) { + builder.append(", "); + } + builder.append(worldsString[i]); + } + builder.append("], Shares: [").append(this.getShares()).append("]"); + if (this.getSpawnWorld() != null) { + builder.append(", Spawn World: ").append(this.getSpawnWorld()); + builder.append(", Spawn Priority: ").append(this.getSpawnPriority().toString()); + } + builder.append("}"); + return builder.toString(); + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/profile/WorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java similarity index 77% rename from src/main/java/com/onarandombox/multiverseinventories/profile/WorldGroupManager.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java index 251a2b1d..0f488fb4 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/profile/WorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/WorldGroupManager.java @@ -1,14 +1,21 @@ -package com.onarandombox.multiverseinventories.profile; +package org.mvplugins.multiverse.inventories.profile.group; -import com.onarandombox.multiverseinventories.WorldGroup; -import org.bukkit.command.CommandSender; +import org.jvnet.hk2.annotations.Contract; +import org.mvplugins.multiverse.core.command.MVCommandIssuer; +import org.mvplugins.multiverse.external.vavr.control.Try; import java.util.List; /** * Manager class for manipulating the groups of this plugin that are contained in the groups configuration. */ -public interface WorldGroupManager { +@Contract +public sealed interface WorldGroupManager permits AbstractWorldGroupManager { + + /** + *

Loads the groups from storage.

+ */ + Try load(); /** *

Retrieves the world group associated with the given name.

@@ -43,26 +50,7 @@ public interface WorldGroupManager { * @param worldName Name of the world to check. * @return true if this world has one or more groups. */ - boolean hasGroup(String worldName); - - /** - * Sets up the World Groups in memory. - * - * @param worldGroups List of World Groups to store in memory. - * @deprecated This feature is now completely unused. - */ - @Deprecated - void setGroups(List worldGroups); - - /** - * Adds a World Group to the collection in memory, also writing it to the groups configuration. - * - * @param worldGroup World group to add. Casing is ignored. - * @param persist This parameter is unused due to deprecation of the method. - * @deprecated - */ - @Deprecated - void addGroup(WorldGroup worldGroup, boolean persist); + boolean hasConfiguredGroup(String worldName); /** *

Adds or updates a world group in Multiverse-Inventories.

@@ -116,10 +104,14 @@ public interface WorldGroupManager { List checkGroups(); /** - * Runs a check for conflicts between groups and displays them to console and sender if not null. + * Runs a check for conflicts between groups and displays them to issuer or console. * - * @param sender The sender to relay information to. If null, info only displayed in console. + * @param issuer The issuer to relay information to. If null, info only displayed in console. */ - void checkForConflicts(CommandSender sender); -} + void checkForConflicts(MVCommandIssuer issuer); + /** + * Recalculates the applicable shares for all groups removing disabled optional shares. + */ + void recalculateApplicableShares(); +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/YamlWorldGroupManager.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java similarity index 61% rename from src/main/java/com/onarandombox/multiverseinventories/YamlWorldGroupManager.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java index a4ce12cf..f0833a07 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/YamlWorldGroupManager.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/group/YamlWorldGroupManager.java @@ -1,11 +1,19 @@ -package com.onarandombox.multiverseinventories; +package org.mvplugins.multiverse.inventories.profile.group; import com.dumptruckman.minecraft.util.Logging; import com.google.common.collect.Lists; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.util.CommentedYamlConfiguration; -import com.onarandombox.multiverseinventories.util.DeserializationException; -import io.papermc.lib.PaperLib; +import org.jvnet.hk2.annotations.Service; +import org.mvplugins.multiverse.core.command.MVCommandManager; +import org.mvplugins.multiverse.core.world.WorldManager; +import org.mvplugins.multiverse.external.commentedconfiguration.CommentedConfiguration; +import org.mvplugins.multiverse.external.jakarta.inject.Inject; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.external.vavr.control.Try; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider; +import org.mvplugins.multiverse.inventories.share.Sharables; +import org.mvplugins.multiverse.inventories.util.DeserializationException; import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.configuration.Configuration; @@ -16,61 +24,75 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +@Service final class YamlWorldGroupManager extends AbstractWorldGroupManager { - private final List groupSectionComments = Collections.unmodifiableList(new ArrayList() {{ - add("# To ADD, DELETE, and EDIT groups use the command /mvinv group."); - add("# No support will be given for those who manually edit these groups."); - }}); + private final String[] groupSectionComments = { + "# Multiverse-Inventories Groups", + "", + "# To ADD, DELETE, and EDIT groups use the command /mvinv group.", + "# No support will be given for those who manually edit these groups." + }; - private final CommentedYamlConfiguration groupsConfig; + private CommentedConfiguration groupsConfig; - YamlWorldGroupManager(final MultiverseInventories inventories, final Configuration config) throws IOException { - super(inventories); + @Inject + YamlWorldGroupManager( + @NotNull MultiverseInventories plugin, + @NotNull MVCommandManager commandManager, + @NotNull InventoriesConfig inventoriesConfig, + @NotNull ProfileContainerStoreProvider profileContainerStoreProvider, + @NotNull WorldManager worldManager) { + super(plugin, commandManager, inventoriesConfig, profileContainerStoreProvider, worldManager); + } - // Check if the group config file exists. If not, create it and migrate group data. - File groupsConfigFile = new File(plugin.getDataFolder(), "groups.yml"); - boolean groupsConfigFileExists = groupsConfigFile.exists(); - boolean migrateGroups = false; - if (!groupsConfigFile.exists()) { - Logging.fine("Created groups file."); - groupsConfigFile.createNewFile(); - migrateGroups = true; - } - // Load the configuration file into memory - boolean supportsCommentsNatively = PaperLib.getMinecraftVersion() > 17; - groupsConfig = new CommentedYamlConfiguration(groupsConfigFile, !groupsConfigFileExists || !supportsCommentsNatively); + @Override + public Try load() { + return Try.run(() -> { + // Check if the group config file exists. If not, create it and migrate group data. + File groupsConfigFile = new File(plugin.getDataFolder(), "groups.yml"); + boolean migrateGroups = false; + if (!groupsConfigFile.exists()) { + Logging.fine("Created groups file."); + groupsConfigFile.createNewFile(); + migrateGroups = true; + } + // Load the configuration file into memory + groupsConfig = new CommentedConfiguration(groupsConfigFile.toPath()); + groupsConfig.load(); - if (migrateGroups) { - migrateGroups(config); - } + if (migrateGroups) { + migrateGroups(inventoriesConfig.getConfig()); + } - groupsConfig.addComment("groups", groupSectionComments); - if (groupsConfig.getConfig().get("groups") == null) { - this.getConfig().createSection("groups"); - } + groupsConfig.addComment("groups", groupSectionComments); + if (groupsConfig.get("groups") == null) { + this.getConfig().createSection("groups"); + } - groupsConfig.getConfig().options().header("Multiverse-Inventories Groups"); - // Saves the configuration from memory to file - groupsConfig.save(); + // Saves the configuration from memory to file + groupsConfig.save(); - // Setup groups in memory - final List worldGroups = getGroupsFromConfig(); - if (worldGroups == null) { - Logging.info("No world groups have been configured!"); - Logging.info("This will cause all worlds configured for Multiverse to have separate player statistics/inventories."); - return; - } + // Setup groups in memory + final List worldGroups = getGroupsFromConfig(); + if (worldGroups == null) { + Logging.info("No world groups have been configured!"); + Logging.info("This will cause all worlds configured for Multiverse to have separate player " + + "statistics/inventories."); + return; + } - for (final WorldGroup worldGroup : worldGroups) { - getGroupNames().put(worldGroup.getName().toLowerCase(), worldGroup); - } + for (final WorldGroup worldGroup : worldGroups) { + getGroupNames().put(worldGroup.getName().toLowerCase(), worldGroup); + } + recalculateApplicableShares(); + Logging.fine("Loaded " + worldGroups.size() + " world groups from config."); + }); } private void migrateGroups(final Configuration config) { @@ -86,7 +108,7 @@ private void migrateGroups(final Configuration config) { } private FileConfiguration getConfig() { - return this.groupsConfig.getConfig(); + return this.groupsConfig; } private List getGroupsFromConfig() { @@ -123,7 +145,7 @@ private List getGroupsFromConfig() { private WorldGroup deserializeGroup(final String name, final Map dataMap) throws DeserializationException { - WorldGroup profile = new WorldGroup(this.plugin, name); + WorldGroup profile = new WorldGroup(this, profileContainerStoreProvider, inventoriesConfig, name); if (dataMap.containsKey("worlds")) { Object worldListObj = dataMap.get("worlds"); if (worldListObj == null) { @@ -141,13 +163,13 @@ private WorldGroup deserializeGroup(final String name, final Map profile.addWorld(worldNameObj.toString(), false); World world = Bukkit.getWorld(worldNameObj.toString()); if (world == null) { - if (builder.length() != 0) { + if (!builder.isEmpty()) { builder.append(", "); } - builder.append(worldNameObj.toString()); + builder.append(worldNameObj); } } - if (builder.length() > 0) { + if (!builder.isEmpty()) { Logging.config("The following worlds for group '%s' are not loaded: %s", name, builder.toString()); } } @@ -162,6 +184,15 @@ private WorldGroup deserializeGroup(final String name, final Map Logging.warning("Shares formatted incorrectly for group: " + name); } } + if (dataMap.containsKey("disabled-shares")) { + Object sharesListObj = dataMap.get("disabled-shares"); + if (sharesListObj instanceof List) { + profile.getDisabledShares().mergeShares(Sharables.fromList((List) sharesListObj)); + profile.getDisabledShares().removeAll(Sharables.negativeFromList((List) sharesListObj)); + } else { + Logging.warning("Disabled shares formatted incorrectly for group: " + name); + } + } if (dataMap.containsKey("spawn")) { Object spawnPropsObj = dataMap.get("spawn"); if (spawnPropsObj instanceof ConfigurationSection) { @@ -184,6 +215,7 @@ private WorldGroup deserializeGroup(final String name, final Map Logging.warning("Spawn settings for group formatted incorrectly"); } } + profile.recalculateApplicableShares(); return profile; } @@ -199,6 +231,10 @@ private Map serializeWorldGroupProfile(WorldGroup profile) { if (!sharesList.isEmpty()) { results.put("shares", sharesList); } + List disabledSharesList = profile.getDisabledShares().toStringList(); + if (!disabledSharesList.isEmpty()) { + results.put("disabled-shares", disabledSharesList); + } Map spawnProps = new LinkedHashMap(); if (profile.getSpawnWorld() != null) { spawnProps.put("world", profile.getSpawnWorld()); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ContainerKey.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ContainerKey.java new file mode 100644 index 00000000..1903bc83 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ContainerKey.java @@ -0,0 +1,46 @@ +package org.mvplugins.multiverse.inventories.profile.key; + +import java.util.Objects; + +public final class ContainerKey { + + public static ContainerKey create(ContainerType containerType, String dataName) { + return new ContainerKey(containerType, dataName); + } + + private final ContainerType containerType; + private final String dataName; + + private ContainerKey(ContainerType containerType, String dataName) { + this.containerType = containerType; + this.dataName = dataName; + } + + public ContainerType getContainerType() { + return containerType; + } + + public String getDataName() { + return dataName; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + ContainerKey that = (ContainerKey) o; + return containerType == that.containerType && Objects.equals(dataName, that.dataName); + } + + @Override + public int hashCode() { + return Objects.hash(containerType, dataName); + } + + @Override + public String toString() { + return "ContainerKey{" + + "containerType=" + containerType + + ", dataName='" + dataName + '\'' + + '}'; + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/profile/container/ContainerType.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ContainerType.java similarity index 66% rename from src/main/java/com/onarandombox/multiverseinventories/profile/container/ContainerType.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/key/ContainerType.java index 4cb56a28..93044852 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/profile/container/ContainerType.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ContainerType.java @@ -1,17 +1,19 @@ -package com.onarandombox.multiverseinventories.profile.container; - -/** - * Used to describe whether a {@link ProfileContainer} represents a single world or a group of worlds. - */ -public enum ContainerType { - - /** - * Indicates World type profiles. - */ - WORLD, - /** - * Indicates Group type profiles. - */ - GROUP; -} - +package org.mvplugins.multiverse.inventories.profile.key; + +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainer; + +/** + * Used to describe whether a {@link ProfileContainer} represents a single world or a group of worlds. + */ +public enum ContainerType { + + /** + * Indicates World type profiles. + */ + WORLD, + /** + * Indicates Group type profiles. + */ + GROUP; +} + diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/GlobalProfileKey.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/GlobalProfileKey.java new file mode 100644 index 00000000..382a09fe --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/GlobalProfileKey.java @@ -0,0 +1,77 @@ +package org.mvplugins.multiverse.inventories.profile.key; + +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.mvplugins.multiverse.external.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.external.jetbrains.annotations.Nullable; +import org.mvplugins.multiverse.inventories.profile.PlayerNamesMapper; + +import java.util.Objects; +import java.util.UUID; + +public sealed class GlobalProfileKey permits ProfileFileKey { + + /** + * Gets a GlobalProfileKey from a playerUUID. NOTE: Player name may be empty if player has never logged in before. + * + * @param playerUUID The player's UUID + * @return A GlobalProfileKey + */ + public static GlobalProfileKey of(UUID playerUUID) { + return PlayerNamesMapper.getInstance().getKey(playerUUID) + .getOrElse(() -> new GlobalProfileKey(playerUUID, "")); + } + + public static GlobalProfileKey of(OfflinePlayer offlinePlayer) { + return of(offlinePlayer.getUniqueId(), offlinePlayer.getName()); + } + + public static GlobalProfileKey of(UUID playerUUID, String playerName) { + return new GlobalProfileKey(playerUUID, playerName); + } + + protected final UUID playerUUID; + protected final String playerName; + + protected GlobalProfileKey(UUID playerUUID, String playerName) { + this.playerUUID = playerUUID; + this.playerName = playerName; + } + + public UUID getPlayerUUID() { + return playerUUID; + } + + public String getPlayerName() { + return playerName; + } + + public @NotNull OfflinePlayer getOfflinePlayer() { + return Bukkit.getOfflinePlayer(playerUUID); + } + + public @Nullable Player getOnlinePlayer() { + return Bukkit.getPlayer(playerUUID); + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + GlobalProfileKey that = (GlobalProfileKey) o; + return Objects.equals(playerUUID, that.playerUUID); + } + + @Override + public int hashCode() { + return playerUUID.hashCode(); + } + + @Override + public String toString() { + return "GlobalProfileKey{" + + "playerUUID=" + playerUUID + + ", playerName='" + playerName + '\'' + + '}'; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileFileKey.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileFileKey.java new file mode 100644 index 00000000..47537f8a --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileFileKey.java @@ -0,0 +1,108 @@ +package org.mvplugins.multiverse.inventories.profile.key; + +import com.google.common.base.Objects; +import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; + +import java.util.UUID; + +public sealed class ProfileFileKey extends GlobalProfileKey permits ProfileKey { + + public static ProfileFileKey fromPlayerProfile(PlayerProfile profile) { + return of( + profile.getContainerType(), + profile.getContainerName(), + profile.getPlayerUUID(), + profile.getPlayerName() + ); + } + + public static ProfileFileKey of( + ContainerType containerType, + String dataName, + GlobalProfileKey globalProfileKey) { + return of(containerType, dataName, globalProfileKey.getPlayerUUID(), globalProfileKey.getPlayerName()); + } + + public static ProfileFileKey of( + ContainerType containerType, + String dataName, + OfflinePlayer offlinePlayer) { + return of(containerType, dataName, offlinePlayer.getUniqueId(), offlinePlayer.getName()); + } + + public static ProfileFileKey of( + ContainerType containerType, + String dataName, + UUID playerUUID, + String playerName) { + return new ProfileFileKey(containerType, dataName, playerUUID, playerName); + } + + protected final ContainerType containerType; + protected final String dataName; + protected final int hashCode; + + private ProfileFileKey(ContainerType containerType, String dataName, UUID playerUUID, String playerName) { + this(containerType, + dataName, + playerUUID, + playerName, + Objects.hashCode(containerType, dataName, playerUUID, playerName)); + } + + protected ProfileFileKey(ContainerType containerType, String dataName, UUID playerUUID, String playerName, int hashCode) { + super(playerUUID, playerName); + this.containerType = containerType; + this.dataName = dataName; + this.hashCode = hashCode; + } + + public ProfileKey forProfileType(@Nullable ProfileType profileType) { + return ProfileKey.of(containerType, dataName, profileType, playerUUID, playerName); + } + + public ProfileFileKey forContainerType(@NotNull ContainerType containerType) { + return new ProfileFileKey(containerType, dataName, playerUUID, playerName); + } + + public ContainerType getContainerType() { + return containerType; + } + + public String getDataName() { + return dataName; + } + + public boolean isSameFile(ProfileFileKey other) { + return Objects.equal(getContainerType(), other.getContainerType()) && + Objects.equal(getDataName(), other.getDataName()) && + Objects.equal(getPlayerUUID(), other.getPlayerUUID()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ProfileKey that)) return false; + return getContainerType() == that.getContainerType() && + Objects.equal(getDataName(), that.getDataName()) && + Objects.equal(getPlayerUUID(), that.getPlayerUUID()); + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public String toString() { + return "ProfileFileKey{" + + "containerType=" + containerType + + ", dataName='" + dataName + '\'' + + ", playerName='" + playerName + '\'' + + ", playerUUID=" + playerUUID + + '}'; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileKey.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileKey.java new file mode 100644 index 00000000..557f02dc --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileKey.java @@ -0,0 +1,97 @@ +package org.mvplugins.multiverse.inventories.profile.key; + +import com.google.common.base.Objects; +import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; + +import java.util.UUID; + +public final class ProfileKey extends ProfileFileKey { + + public static ProfileKey fromPlayerProfile(PlayerProfile profile) { + return of( + profile.getContainerType(), + profile.getContainerName(), + profile.getProfileType(), + profile.getPlayerUUID(), + profile.getPlayerName() + ); + } + + public static ProfileKey of( + ContainerType containerType, + String dataName, + ProfileType profileType, + UUID playerUUID) { + return of(containerType, dataName, profileType, GlobalProfileKey.of(playerUUID)); + } + + public static ProfileKey of( + ContainerType containerType, + String dataName, + ProfileType profileType, + GlobalProfileKey globalProfileKey) { + return of(containerType, dataName, profileType, globalProfileKey.getPlayerUUID(), globalProfileKey.getPlayerName()); + } + + public static ProfileKey of( + ContainerType containerType, + String dataName, + ProfileType profileType, + OfflinePlayer offlinePlayer) { + return of(containerType, dataName, profileType, offlinePlayer.getUniqueId(), offlinePlayer.getName()); + } + + public static ProfileKey of( + ContainerType containerType, + String dataName, + ProfileType profileType, + UUID playerUUID, + String playerName) { + return new ProfileKey(containerType, dataName, profileType, playerUUID, playerName); + } + + private final ProfileType profileType; + + private ProfileKey( + ContainerType containerType, + String dataName, + ProfileType profileType, + UUID playerUUID, + String playerName + ) { + super(containerType, dataName, playerUUID, playerName, Objects.hashCode(containerType, dataName, profileType, playerUUID)); + this.profileType = profileType; + } + + @Override + public ProfileKey forContainerType(@NotNull ContainerType containerType) { + return new ProfileKey(containerType, dataName, profileType, playerUUID, playerName); + } + + public ProfileType getProfileType() { + return profileType; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ProfileKey that)) return false; + return getContainerType() == that.getContainerType() && + Objects.equal(getDataName(), that.getDataName()) && + Objects.equal(getProfileType(), that.getProfileType()) && + Objects.equal(getPlayerUUID(), that.getPlayerUUID()); + } + + @Override + public String toString() { + return "ProfileKey{" + + "containerType=" + containerType + + ", dataName='" + dataName + '\'' + + ", profileType=" + profileType + + ", playerName='" + playerName + '\'' + + ", playerUUID=" + playerUUID + + '}'; + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/profile/ProfileType.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileType.java similarity index 65% rename from src/main/java/com/onarandombox/multiverseinventories/profile/ProfileType.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileType.java index 6af78391..a837c839 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/profile/ProfileType.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileType.java @@ -1,39 +1,39 @@ -package com.onarandombox.multiverseinventories.profile; - -/** - * Used to differentiate between profiles in the same world or world group, primarily for game modes. - */ -public final class ProfileType { - - static ProfileType createProfileType(String name) { - return new ProfileType(name); - } - - private String name; - - private ProfileType(String name) { - this.name = name; - } - - /** - * @return The name of the profile. The default profile type will return a blank string. - */ - public String getName() { - return name; - } - - @Override - public final boolean equals(Object o) { - return o instanceof ProfileType && ((ProfileType) o).getName().equals(this.getName()); - } - - @Override - public final int hashCode() { - return getName().hashCode(); - } - - @Override - public String toString() { - return "ProfileType:" + getName(); - } -} +package org.mvplugins.multiverse.inventories.profile.key; + +/** + * Used to differentiate between profiles in the same world or world group, primarily for game modes. + */ +public final class ProfileType { + + static ProfileType createProfileType(String name) { + return new ProfileType(name); + } + + private final String name; + + private ProfileType(String name) { + this.name = name; + } + + /** + * @return The name of the profile. The default profile type will return a blank string. + */ + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + return o instanceof ProfileType otherType && otherType.getName().equals(this.getName()); + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public String toString() { + return "ProfileType:" + getName(); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileTypes.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileTypes.java new file mode 100644 index 00000000..1640f4fa --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/key/ProfileTypes.java @@ -0,0 +1,106 @@ +package org.mvplugins.multiverse.inventories.profile.key; + +import com.google.common.collect.Sets; +import org.bukkit.GameMode; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Nullable; +import org.mvplugins.multiverse.external.vavr.control.Option; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Static class for profile type lookup and protected registration. + */ +public final class ProfileTypes { + + private static final Set types = new HashSet<>(); + private static InventoriesConfig config; + + public static void init(MultiverseInventories plugin) { + config = plugin.getServiceLocator().getService(InventoriesConfig.class); + } + + private static ProfileType createProfileType(String name) { + ProfileType type = ProfileType.createProfileType(name); + types.add(type); + return type; + } + + /** + * The profile type for the SURVIVAL Game Mode. + */ + public static final ProfileType SURVIVAL = createProfileType("SURVIVAL"); + + /** + * The profile type for the CREATIVE Game Mode. + */ + public static final ProfileType CREATIVE = createProfileType("CREATIVE"); + + /** + * The profile type for the ADVENTURE Game Mode. + */ + public static final ProfileType ADVENTURE = createProfileType("ADVENTURE"); + + /** + * The profile type for the SPECTATOR Game Mode. + */ + public static final ProfileType SPECTATOR = createProfileType("SPECTATOR"); + + public static Collection getTypes() { + return types; + } + + public static Collection getApplicableTypes() { + if (config != null && config.getEnableGamemodeShareHandling()) { + return types; + } + return List.of(getDefault()); + } + + public static ProfileType getDefault() { + return SURVIVAL; + } + + public static ProfileType forPlayer(Player player) { + if (config != null && config.getEnableGamemodeShareHandling()) { + return forGameMode(player.getGameMode()); + } + return getDefault(); + } + + public static Option forName(String name) { + return Option.ofOptional(types.stream() + .filter(type -> type.getName().equalsIgnoreCase(name)) + .findFirst()); + } + + /** + * Returns the appropriate ProfileType for the given game mode. + * + * @param mode The game mode to get the profile type for. + * @return The profile type for the given game mode. + */ + public static ProfileType forGameMode(GameMode mode) { + return switch (mode) { + case SURVIVAL -> SURVIVAL; + case CREATIVE -> CREATIVE; + case ADVENTURE -> ADVENTURE; + case SPECTATOR -> SPECTATOR; + default -> SURVIVAL; + }; + } + + public static boolean isAll(ProfileType[] otherTypes) { + return Set.of(otherTypes).equals(types); + } + + private ProfileTypes() { + throw new IllegalStateException(); + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/profile/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/profile/package-info.java similarity index 64% rename from src/main/java/com/onarandombox/multiverseinventories/profile/package-info.java rename to src/main/java/org/mvplugins/multiverse/inventories/profile/package-info.java index 38c8b5f9..9480ae1f 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/profile/package-info.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/profile/package-info.java @@ -1,5 +1,5 @@ -/** - * This package contains classes related to groups and worlds and the player profiles they contain. - */ -package com.onarandombox.multiverseinventories.profile; - +/** + * This package contains classes related to groups and worlds and the player profiles they contain. + */ +package org.mvplugins.multiverse.inventories.profile; + diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/DefaultSerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/DefaultSerializer.java similarity index 84% rename from src/main/java/com/onarandombox/multiverseinventories/share/DefaultSerializer.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/DefaultSerializer.java index 1888dc38..1765b9db 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/DefaultSerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/DefaultSerializer.java @@ -1,30 +1,30 @@ -package com.onarandombox.multiverseinventories.share; - -/** - * The default Sharable serializer. It performs no special tasks on the objects being sent to persistence, they are - * sent as is. - * - * @param The type of data this serializer serializes. - */ -final class DefaultSerializer implements SharableSerializer { - - private Class type; - - public DefaultSerializer(Class type) { - this.type = type; - } - - private Class getType() { - return this.type; - } - - @Override - public T deserialize(Object obj) { - return getType().cast(obj); - } - - @Override - public Object serialize(T t) { - return t; - } -} +package org.mvplugins.multiverse.inventories.share; + +/** + * The default Sharable serializer. It performs no special tasks on the objects being sent to persistence, they are + * sent as is. + * + * @param The type of data this serializer serializes. + */ +final class DefaultSerializer implements SharableSerializer { + + private final Class type; + + public DefaultSerializer(Class type) { + this.type = type; + } + + private Class getType() { + return this.type; + } + + @Override + public T deserialize(Object obj) { + return getType().cast(obj); + } + + @Override + public Object serialize(T t) { + return t; + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/DefaultSharable.java b/src/main/java/org/mvplugins/multiverse/inventories/share/DefaultSharable.java similarity index 92% rename from src/main/java/com/onarandombox/multiverseinventories/share/DefaultSharable.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/DefaultSharable.java index 306616e9..5d3c61ba 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/DefaultSharable.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/DefaultSharable.java @@ -1,83 +1,83 @@ -package com.onarandombox.multiverseinventories.share; - -/** - * A class used to define a value that can be shared between worlds and world groups in Multiverse-Inventories. - * - * @param The type of data this Sharable represents. - */ -final class DefaultSharable implements Sharable { - - private final String[] names; - private final SharableHandler handler; - private final SharableSerializer serializer; - private final ProfileEntry profileEntry; - private final boolean optional; - private final Class type; - - DefaultSharable(final String[] names, final Class type, final SharableHandler handler, - final SharableSerializer serializer, final ProfileEntry entry, final boolean optional) { - this.names = names; - this.handler = handler; - this.serializer = serializer; - this.profileEntry = entry; - this.optional = optional; - this.type = type; - Sharables.register(this); - } - - /** - * {@inheritDoc} - */ - @Override - public String[] getNames() { - return names; - } - - /** - * {@inheritDoc} - */ - @Override - public Class getType() { - return this.type; - } - - /** - * {@inheritDoc} - */ - @Override - public String toString() { - return this.names[0]; - } - - /** - * {@inheritDoc} - */ - @Override - public SharableHandler getHandler() { - return this.handler; - } - - /** - * {@inheritDoc} - */ - @Override - public SharableSerializer getSerializer() { - return this.serializer; - } - - /** - * {@inheritDoc} - */ - @Override - public ProfileEntry getProfileEntry() { - return this.profileEntry; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isOptional() { - return this.optional; - } -} +package org.mvplugins.multiverse.inventories.share; + +/** + * A class used to define a value that can be shared between worlds and world groups in Multiverse-Inventories. + * + * @param The type of data this Sharable represents. + */ +final class DefaultSharable implements Sharable { + + private final String[] names; + private final SharableHandler handler; + private final SharableSerializer serializer; + private final ProfileEntry profileEntry; + private final boolean optional; + private final Class type; + + DefaultSharable(final String[] names, final Class type, final SharableHandler handler, + final SharableSerializer serializer, final ProfileEntry entry, final boolean optional) { + this.names = names; + this.handler = handler; + this.serializer = serializer; + this.profileEntry = entry; + this.optional = optional; + this.type = type; + Sharables.register(this); + } + + /** + * {@inheritDoc} + */ + @Override + public String[] getNames() { + return names; + } + + /** + * {@inheritDoc} + */ + @Override + public Class getType() { + return this.type; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return this.names[0]; + } + + /** + * {@inheritDoc} + */ + @Override + public SharableHandler getHandler() { + return this.handler; + } + + /** + * {@inheritDoc} + */ + @Override + public SharableSerializer getSerializer() { + return this.serializer; + } + + /** + * {@inheritDoc} + */ + @Override + public ProfileEntry getProfileEntry() { + return this.profileEntry; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isOptional() { + return this.optional; + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/DefaultStringSerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/DefaultStringSerializer.java similarity index 92% rename from src/main/java/com/onarandombox/multiverseinventories/share/DefaultStringSerializer.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/DefaultStringSerializer.java index c18f2374..7f5f85d3 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/DefaultStringSerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/DefaultStringSerializer.java @@ -1,51 +1,51 @@ -package com.onarandombox.multiverseinventories.share; - -import com.dumptruckman.minecraft.util.Logging; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; - -/** - * This Sharable serializer attempts to deserialize the string form of objects passed to it through use of - * T.valueOf(String). Likewise, it serializes data simply by calling Object.toString() on the value passed in. - * - * @param The type of data this serializer serializes. This class MUST have a static valueOf(String) method that - * returns it's type. - */ -final class DefaultStringSerializer implements SharableSerializer { - - private Method valueOfMethod; - private Class clazz; - - DefaultStringSerializer(Class clazz) { - this.clazz = clazz; - try { - valueOfMethod = clazz.getMethod("valueOf", String.class); - valueOfMethod.setAccessible(true); - if (!valueOfMethod.getReturnType().equals(clazz) || !Modifier.isStatic(valueOfMethod.getModifiers())) { - throw new IllegalArgumentException(clazz.getName() + " has no static valueOf(String) method!"); - } - } catch (NoSuchMethodException e) { - throw new IllegalArgumentException(clazz.getName() + " has no static valueOf(String) method!"); - } - } - - @Override - public T deserialize(Object obj) { - try { - return clazz.cast(valueOfMethod.invoke(null, obj.toString())); - } catch (IllegalAccessException e) { - Logging.severe(this.clazz.getName() + " has no accessible static valueOf(String) method!"); - } catch (InvocationTargetException e) { - Logging.severe(this.clazz.getName() + ".valueOf(String) is throwing an exception:"); - e.printStackTrace(); - } - throw new IllegalStateException(this.getClass().getName() + " was used illegally! Contact dumptruckman!"); - } - - @Override - public Object serialize(T t) { - return t.toString(); - } -} +package org.mvplugins.multiverse.inventories.share; + +import com.dumptruckman.minecraft.util.Logging; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +/** + * This Sharable serializer attempts to deserialize the string form of objects passed to it through use of + * T.valueOf(String). Likewise, it serializes data simply by calling Object.toString() on the value passed in. + * + * @param The type of data this serializer serializes. This class MUST have a static valueOf(String) method that + * returns it's type. + */ +final class DefaultStringSerializer implements SharableSerializer { + + private final Method valueOfMethod; + private final Class clazz; + + DefaultStringSerializer(Class clazz) { + this.clazz = clazz; + try { + valueOfMethod = clazz.getMethod("valueOf", String.class); + valueOfMethod.setAccessible(true); + if (!valueOfMethod.getReturnType().equals(clazz) || !Modifier.isStatic(valueOfMethod.getModifiers())) { + throw new IllegalArgumentException(clazz.getName() + " has no static valueOf(String) method!"); + } + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException(clazz.getName() + " has no static valueOf(String) method!"); + } + } + + @Override + public T deserialize(Object obj) { + try { + return clazz.cast(valueOfMethod.invoke(null, obj.toString())); + } catch (IllegalAccessException e) { + Logging.severe(this.clazz.getName() + " has no accessible static valueOf(String) method!"); + } catch (InvocationTargetException e) { + Logging.severe(this.clazz.getName() + ".valueOf(String) is throwing an exception:"); + e.printStackTrace(); + } + throw new IllegalStateException(this.getClass().getName() + " was used illegally! Contact dumptruckman!"); + } + + @Override + public Object serialize(T t) { + return t.toString(); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java new file mode 100644 index 00000000..5e7db125 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java @@ -0,0 +1,64 @@ +package org.mvplugins.multiverse.inventories.share; + +import org.mvplugins.multiverse.inventories.util.ItemStackConverter; +import org.mvplugins.multiverse.inventories.util.MinecraftTools; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import java.util.HashMap; +import java.util.Map; + +/** + * A simple {@link SharableSerializer} usable with ItemStack[] which converts the ItemStack[] to the string format + * that is used by default in Multiverse-Inventories. + */ +final class InventorySerializer implements SharableSerializer { + + private final int inventorySize; + + public InventorySerializer(final int inventorySize) { + this.inventorySize = inventorySize; + } + + @Override + public ItemStack[] deserialize(Object obj) { + return unmapSlots(obj); + } + + @Override + public Object serialize(ItemStack[] itemStacks) { + return mapSlots(itemStacks); + } + + private Map mapSlots(ItemStack[] itemStacks) { + Map result = new HashMap<>(itemStacks.length); + for (int i = 0; i < itemStacks.length; i++) { + Object serialize = ItemStackConverter.serialize(itemStacks[i]); + if (serialize != null) { + result.put(Integer.toString(i), serialize); + } + } + return result; + } + + private ItemStack[] unmapSlots(Object obj) { + ItemStack[] inventory = new ItemStack[inventorySize]; + if (!(obj instanceof Map invMap)) { + return MinecraftTools.fillWithAir(inventory); + } + for (int i = 0; i < inventory.length; i++) { + Object value = invMap.get(Integer.toString(i)); + if (value == null) { + inventory[i] = new ItemStack(Material.AIR); + continue; + } + ItemStack item = ItemStackConverter.deserialize(value); + if (item == null) { + inventory[i] = new ItemStack(Material.AIR); + continue; + } + inventory[i] = item; + } + return inventory; + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/ItemStackSerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/ItemStackSerializer.java new file mode 100644 index 00000000..b60cbba4 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/ItemStackSerializer.java @@ -0,0 +1,17 @@ +package org.mvplugins.multiverse.inventories.share; + +import org.bukkit.inventory.ItemStack; +import org.mvplugins.multiverse.inventories.util.ItemStackConverter; + +final class ItemStackSerializer implements SharableSerializer { + + @Override + public ItemStack deserialize(Object obj) { + return ItemStackConverter.deserialize(obj); + } + + @Override + public Object serialize(ItemStack itemStack) { + return ItemStackConverter.serialize(itemStack); + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/LocationSerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/LocationSerializer.java similarity index 55% rename from src/main/java/com/onarandombox/multiverseinventories/share/LocationSerializer.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/LocationSerializer.java index 8284c7f0..430e13ef 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/LocationSerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/LocationSerializer.java @@ -1,36 +1,35 @@ -package com.onarandombox.multiverseinventories.share; - -import com.onarandombox.multiverseinventories.DataStrings; -import org.bukkit.Location; - -import java.util.Map; - -/** - * A simple {@link SharableSerializer} usable with {@link Location} which converts the {@link Location} to the string - * format that is used by default in Multiverse-Inventories. - * @deprecated Locations no longer need a special serializer because they are - * {@link org.bukkit.configuration.serialization.ConfigurationSerializable}. This remains to convert legacy data. - */ -@Deprecated -public final class LocationSerializer implements SharableSerializer { - - @Override - public Location deserialize(Object obj) { - if (obj instanceof Location) { - return (Location) obj; - } else if (obj instanceof String) { - return DataStrings.parseLocation(obj.toString()); - } else { - if (obj instanceof Map) { - return DataStrings.parseLocation((Map) obj); - } else { - return DataStrings.parseLocation(obj.toString()); - } - } - } - - @Override - public Object serialize(Location location) { - return location; - } -} +package org.mvplugins.multiverse.inventories.share; + +import org.bukkit.Location; +import org.mvplugins.multiverse.inventories.util.LegacyParsers; + +import java.util.Map; + +/** + * A simple {@link SharableSerializer} usable with {@link Location} which converts the {@link Location} to the string + * format that is used by default in Multiverse-Inventories. + * @deprecated Locations no longer need a special serializer because they are + * {@link org.bukkit.configuration.serialization.ConfigurationSerializable}. This remains to convert legacy data. + */ +@Deprecated +final class LocationSerializer implements SharableSerializer { + + @Override + public Location deserialize(Object obj) { + if (obj instanceof Location) { + return (Location) obj; + } + if (obj instanceof String) { + return LegacyParsers.parseLocation(obj.toString()); + } + if (obj instanceof Map) { + return LegacyParsers.parseLocation((Map) obj); + } + return LegacyParsers.parseLocation(obj.toString()); + } + + @Override + public Object serialize(Location location) { + return location; + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/PotionEffectSerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/PotionEffectSerializer.java similarity index 61% rename from src/main/java/com/onarandombox/multiverseinventories/share/PotionEffectSerializer.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/PotionEffectSerializer.java index f16c0bff..8be1eae4 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/PotionEffectSerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/PotionEffectSerializer.java @@ -1,36 +1,36 @@ -package com.onarandombox.multiverseinventories.share; - -import com.onarandombox.multiverseinventories.DataStrings; -import org.bukkit.potion.PotionEffect; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * A simple {@link com.onarandombox.multiverseinventories.share.SharableSerializer} usable with PotionEffect[] - * which converts the PotionEffect[] to the string format that is used by default in Multiverse-Inventories. - */ -public final class PotionEffectSerializer implements SharableSerializer { - - @Override - public PotionEffect[] deserialize(Object obj) { - if (obj instanceof List) { - List list = (List) obj; - List resultList = new ArrayList<>(list.size()); - for (Object o : list) { - if (o instanceof PotionEffect) { - resultList.add((PotionEffect) o); - } - } - return resultList.toArray(new PotionEffect[resultList.size()]); - } else { - return DataStrings.parsePotionEffects(obj.toString()); - } - } - - @Override - public Object serialize(PotionEffect[] potionEffects) { - return Arrays.asList(potionEffects); - } -} +package org.mvplugins.multiverse.inventories.share; + +import org.bukkit.potion.PotionEffect; +import org.mvplugins.multiverse.inventories.util.LegacyParsers; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * A simple {@link SharableSerializer} usable with PotionEffect[] + * which converts the PotionEffect[] to the string format that is used by default in Multiverse-Inventories. + */ +final class PotionEffectSerializer implements SharableSerializer { + + @Override + public PotionEffect[] deserialize(Object obj) { + if (obj instanceof List) { + List list = (List) obj; + List resultList = new ArrayList<>(list.size()); + for (Object o : list) { + if (o instanceof PotionEffect) { + resultList.add((PotionEffect) o); + } + } + return resultList.toArray(new PotionEffect[0]); + } else { + return LegacyParsers.parsePotionEffects(obj.toString()); + } + } + + @Override + public Object serialize(PotionEffect[] potionEffects) { + return Arrays.asList(potionEffects); + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/ProfileEntry.java b/src/main/java/org/mvplugins/multiverse/inventories/share/ProfileEntry.java similarity index 74% rename from src/main/java/com/onarandombox/multiverseinventories/share/ProfileEntry.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/ProfileEntry.java index d3cf5a65..95b4ad6f 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/ProfileEntry.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/ProfileEntry.java @@ -1,70 +1,64 @@ -package com.onarandombox.multiverseinventories.share; - -import java.util.HashMap; -import java.util.Map; - -/** - * Indicates how a Sharable should be stored in the profile file. Serves as a lookup for finding a sharable based on - * it's file tag. - */ -public final class ProfileEntry { - - private static final Map STATS_MAP = new HashMap(); - private static final Map OTHERS_MAP = new HashMap(); - - private boolean isStat; - private String fileTag; - - public ProfileEntry(boolean isStat, String fileTag) { - this.isStat = isStat; - this.fileTag = fileTag; - } - - /** - * @return True if this indicates a {@link Sharable} whose data will be stored in the stats string of the player - * file. - */ - public boolean isStat() { - return this.isStat; - } - - /** - * @return The String that represents where this file is stored in the player profile. - */ - public String getFileTag() { - return this.fileTag; - } - - /** - * Registers a {@link Sharable} with it's respective ProfileEntry. - * - * @param sharable The sharable to register. - */ - static void register(Sharable sharable) { - ProfileEntry entry = sharable.getProfileEntry(); - if (entry == null) { - // This would mean the sharable is not intended for saving in profile files. - return; - } - if (entry.isStat()) { - STATS_MAP.put(entry.getFileTag(), sharable); - } else { - OTHERS_MAP.put(entry.getFileTag(), sharable); - } - } - - /** - * Used to look up a {@link Sharable} by it's file tag. - * - * @param stat True means this sharable is in the stats section of the player file. - * @param fileTag The string representing where the sharable is stored in the player file. - * @return A sharable if one has been registered with it's ProfileEntry otherwise null. - */ - public static Sharable lookup(boolean stat, String fileTag) { - if (stat) { - return STATS_MAP.get(fileTag); - } else { - return OTHERS_MAP.get(fileTag); - } - } -} +package org.mvplugins.multiverse.inventories.share; + +import java.util.HashMap; +import java.util.Map; + +/** + * Indicates how a Sharable should be stored in the profile file. Serves as a lookup for finding a sharable based on + * it's file tag. + */ +public record ProfileEntry(boolean isStat, String fileTag) { + + private static final Map STATS_MAP = new HashMap<>(); + private static final Map OTHERS_MAP = new HashMap<>(); + + /** + * @return True if this indicates a {@link Sharable} whose data will be stored in the stats string of the player + * file. + */ + @Override + public boolean isStat() { + return this.isStat; + } + + /** + * @return The String that represents where this file is stored in the player profile. + */ + @Override + public String fileTag() { + return this.fileTag; + } + + /** + * Registers a {@link Sharable} with it's respective ProfileEntry. + * + * @param sharable The sharable to register. + */ + static void register(Sharable sharable) { + ProfileEntry entry = sharable.getProfileEntry(); + if (entry == null) { + // This would mean the sharable is not intended for saving in profile files. + return; + } + if (entry.isStat()) { + STATS_MAP.put(entry.fileTag(), sharable); + } else { + OTHERS_MAP.put(entry.fileTag(), sharable); + } + } + + /** + * Used to look up a {@link Sharable} by it's file tag. + * + * @param stat True means this sharable is in the stats section of the player file. + * @param fileTag The string representing where the sharable is stored in the player file. + * @return A sharable if one has been registered with it's ProfileEntry otherwise null. + */ + public static Sharable lookup(boolean stat, String fileTag) { + if (stat) { + return STATS_MAP.get(fileTag); + } else { + return OTHERS_MAP.get(fileTag); + } + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/Sharable.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharable.java similarity index 94% rename from src/main/java/com/onarandombox/multiverseinventories/share/Sharable.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/Sharable.java index 64dca20a..e0113e09 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/Sharable.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharable.java @@ -1,156 +1,156 @@ -package com.onarandombox.multiverseinventories.share; - -import com.onarandombox.multiverseinventories.InventoriesConfig; - -import java.util.ArrayList; -import java.util.List; - -/** - * An interface for any attribute that can be shared between worlds in a world group. These objects are intended to - * be used as constants and may not function properly otherwise. - * - * @param The type of data that this sharable represents. - */ -public interface Sharable { - - /** - * @return The names of this Sharable for setting as shared in the config. There should ALWAYS be a index-0 which - * represents the main name, and the one that will be used for storing the sharable in a groups shares list in - * the config file. All names in this array may be used to set a group as sharing this Sharable. - */ - String[] getNames(); - - /** - * @return The object that will handle changing out the player's data with the profile's data and vice versa when - * a player changes worlds. - */ - SharableHandler getHandler(); - - /** - * @return The object that will handle serializing a profile's data for this sharable for saving/loading in the - * profile's data file. If this is null it means that persistence is not handled by Multiverse-Inventories for - * this Sharable. - */ - SharableSerializer getSerializer(); - - /** - * @return The profile entry that describes how to store this Sharable in a profile's data file. This may NOT be - * null if this Sharable getSerializer() is not null. If getSerializer() IS null, this method is never called. - */ - ProfileEntry getProfileEntry(); - - /** - * @return The type of data this Sharable represents. Used primarily for casting. - */ - Class getType(); - - /** - * @return True if this Sharable is optional. That is to say that it is completely ignored when share handling - * takes place UNLESS it is present in {@link InventoriesConfig#getOptionalShares()}. - */ - boolean isOptional(); - - /** - * This class is used to build new {@link Sharable}s. Simply instantiate this and use method chaining to set - * all the options for your Sharable. - * - * @param The type of data the new Sharable will represent. - */ - class Builder { - - private List names = new ArrayList(); - private ProfileEntry profileEntry = null; - private SharableHandler handler; - private SharableSerializer serializer = null; - private boolean optional = false; - private Class type; - - /** - * @param name The primary name of the new Sharable. - * @param type The type of data the Sharable represents. - * @param handler The object that will handle switching the Sharable data between player and profile. - */ - public Builder(String name, Class type, SharableHandler handler) { - this.names.add(name); - this.handler = handler; - this.type = type; - } - - /** - * Indicates that the new Sharable is optional as described in {@link Sharable#isOptional()}. - * - * @return This builder object for method chaining. - */ - public Builder optional() { - this.optional = true; - return this; - } - - /** - * @param name An alternate name for this Sharable which can be used to indicate a group is sharing this - * Sharable. - * @return This builder object for method chaining. - */ - public Builder altName(String name) { - this.names.add(name); - return this; - } - - /** - * Sets this sharable to be serialized as a string in the profile data file. To use this, the class type - * indicates in the Builder's constructor MUST have a static .valueOf(String) method that returns it's type. - * - * @param entry The profile entry describing where this Sharable is located in the profile file. - * @return This builder object for method chaining. - * @throws IllegalArgumentException This is thrown if the type indicated in the Builder's constructor does not - * fit the constraints indicated above. - */ - public Builder stringSerializer(ProfileEntry entry) { - this.serializer = new DefaultStringSerializer(this.type); - this.profileEntry = entry; - return this; - } - - /** - * This will make the Sharable use the default serializer which simply passes the data as is to the persistence - * object for persistence. This will only work depending on the data type this Sharable represents and further - * depending on the types the persistence methods accept. Generally, boxed primitives are okay as well as - * Lists of boxed primitives and {@link java.util.Map}<{@link String}, {@link Object}>. All other types - * will likely require a custom {@link SharableSerializer} indicated with - * {@link #serializer(ProfileEntry, SharableSerializer)}. - * - * @param entry The profile entry describing where this Sharable is located in the profile file. - * @return This builder object for method chaining. - */ - public Builder defaultSerializer(ProfileEntry entry) { - this.serializer = new DefaultSerializer(this.type); - this.profileEntry = entry; - return this; - } - - /** - * This allows you to specify a custom {@link SharableSerializer} to use to convert the data represented by - * this Sharable into something acceptable by persistence. - * - * @param entry The profile entry describing where this Sharable is located in the profile file. - * @param serializer A custom serializer describing how to handle the data in order for it to be persisted in - * the profile. - * @return This builder object for method chaining. - */ - public Builder serializer(ProfileEntry entry, SharableSerializer serializer) { - this.serializer = serializer; - this.profileEntry = entry; - return this; - } - - /** - * @return The new Sharable object built by this Builder. - */ - public Sharable build() { - Sharable sharable = new DefaultSharable(names.toArray(new String[names.size()]), type, - handler, serializer, profileEntry, optional); - ProfileEntry.register(sharable); - return sharable; - } - } -} +package org.mvplugins.multiverse.inventories.share; + +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; + +import java.util.ArrayList; +import java.util.List; + +/** + * An interface for any attribute that can be shared between worlds in a world group. These objects are intended to + * be used as constants and may not function properly otherwise. + * + * @param The type of data that this sharable represents. + */ +public interface Sharable { + + /** + * @return The names of this Sharable for setting as shared in the config. There should ALWAYS be a index-0 which + * represents the main name, and the one that will be used for storing the sharable in a groups shares list in + * the config file. All names in this array may be used to set a group as sharing this Sharable. + */ + String[] getNames(); + + /** + * @return The object that will handle changing out the player's data with the profile's data and vice versa when + * a player changes worlds. + */ + SharableHandler getHandler(); + + /** + * @return The object that will handle serializing a profile's data for this sharable for saving/loading in the + * profile's data file. If this is null it means that persistence is not handled by Multiverse-Inventories for + * this Sharable. + */ + SharableSerializer getSerializer(); + + /** + * @return The profile entry that describes how to store this Sharable in a profile's data file. This may NOT be + * null if this Sharable getSerializer() is not null. If getSerializer() IS null, this method is never called. + */ + ProfileEntry getProfileEntry(); + + /** + * @return The type of data this Sharable represents. Used primarily for casting. + */ + Class getType(); + + /** + * @return True if this Sharable is optional. That is to say that it is completely ignored when share handling + * takes place UNLESS it is present in {@link InventoriesConfig#getActiveOptionalShares()}. + */ + boolean isOptional(); + + /** + * This class is used to build new {@link Sharable}s. Simply instantiate this and use method chaining to set + * all the options for your Sharable. + * + * @param The type of data the new Sharable will represent. + */ + class Builder { + + private List names = new ArrayList(); + private ProfileEntry profileEntry = null; + private SharableHandler handler; + private SharableSerializer serializer = null; + private boolean optional = false; + private Class type; + + /** + * @param name The primary name of the new Sharable. + * @param type The type of data the Sharable represents. + * @param handler The object that will handle switching the Sharable data between player and profile. + */ + public Builder(String name, Class type, SharableHandler handler) { + this.names.add(name); + this.handler = handler; + this.type = type; + } + + /** + * Indicates that the new Sharable is optional as described in {@link Sharable#isOptional()}. + * + * @return This builder object for method chaining. + */ + public Builder optional() { + this.optional = true; + return this; + } + + /** + * @param name An alternate name for this Sharable which can be used to indicate a group is sharing this + * Sharable. + * @return This builder object for method chaining. + */ + public Builder altName(String name) { + this.names.add(name); + return this; + } + + /** + * Sets this sharable to be serialized as a string in the profile data file. To use this, the class type + * indicates in the Builder's constructor MUST have a static .valueOf(String) method that returns it's type. + * + * @param entry The profile entry describing where this Sharable is located in the profile file. + * @return This builder object for method chaining. + * @throws IllegalArgumentException This is thrown if the type indicated in the Builder's constructor does not + * fit the constraints indicated above. + */ + public Builder stringSerializer(ProfileEntry entry) { + this.serializer = new DefaultStringSerializer(this.type); + this.profileEntry = entry; + return this; + } + + /** + * This will make the Sharable use the default serializer which simply passes the data as is to the persistence + * object for persistence. This will only work depending on the data type this Sharable represents and further + * depending on the types the persistence methods accept. Generally, boxed primitives are okay as well as + * Lists of boxed primitives and {@link java.util.Map}<{@link String}, {@link Object}>. All other types + * will likely require a custom {@link SharableSerializer} indicated with + * {@link #serializer(ProfileEntry, SharableSerializer)}. + * + * @param entry The profile entry describing where this Sharable is located in the profile file. + * @return This builder object for method chaining. + */ + public Builder defaultSerializer(ProfileEntry entry) { + this.serializer = new DefaultSerializer(this.type); + this.profileEntry = entry; + return this; + } + + /** + * This allows you to specify a custom {@link SharableSerializer} to use to convert the data represented by + * this Sharable into something acceptable by persistence. + * + * @param entry The profile entry describing where this Sharable is located in the profile file. + * @param serializer A custom serializer describing how to handle the data in order for it to be persisted in + * the profile. + * @return This builder object for method chaining. + */ + public Builder serializer(ProfileEntry entry, SharableSerializer serializer) { + this.serializer = serializer; + this.profileEntry = entry; + return this; + } + + /** + * @return The new Sharable object built by this Builder. + */ + public Sharable build() { + Sharable sharable = new DefaultSharable(names.toArray(new String[0]), type, + handler, serializer, profileEntry, optional); + ProfileEntry.register(sharable); + return sharable; + } + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/SharableGroup.java b/src/main/java/org/mvplugins/multiverse/inventories/share/SharableGroup.java similarity index 88% rename from src/main/java/com/onarandombox/multiverseinventories/share/SharableGroup.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/SharableGroup.java index c030cd8f..5d9a973d 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/SharableGroup.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/SharableGroup.java @@ -1,134 +1,134 @@ -package com.onarandombox.multiverseinventories.share; - -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; - -/** - * This class represents a grouping of Sharable objects for the sole purpose of being able to use one keyword in - * group setups to indicate multiple {@link Sharable}s. - */ -public final class SharableGroup implements Shares { - - private String[] names; - private Shares shares; - - public SharableGroup(String name, Shares shares, String... alternateNames) { - this.names = new String[alternateNames.length + 1]; - this.names[0] = name; - System.arraycopy(alternateNames, 0, this.names, 1, alternateNames.length); - this.shares = shares; - for (String lookupName : this.names) { - Sharables.LOOKUP_MAP.put(lookupName, this); - } - } - - /** - * @return The names of this SharableGroup for setting as shared in the config. - * All names in this array may be used to set a group as sharing this SharableGroup. - */ - public String[] getNames() { - return this.names; - } - - @Override - public void mergeShares(Shares newShares) { - throw new IllegalStateException("May not alter SharableGroup!"); - } - - @Override - public boolean isSharing(Sharable sharable) { - return shares.isSharing(sharable); - } - - @Override - public boolean isSharing(Shares shares) { - return this.shares.isSharing(shares); - } - - @Override - public Shares compare(Shares shares) { - return this.shares.compare(shares); - } - - @Override - public void setSharing(Sharable sharable, boolean sharing) { - throw new IllegalStateException("May not alter SharableGroup!"); - } - - @Override - public void setSharing(Shares sharables, boolean sharing) { - throw new IllegalStateException("May not alter SharableGroup!"); - } - - @Override - public List toStringList() { - return shares.toStringList(); - } - - @Override - public Iterator iterator() { - return shares.iterator(); - } - - @Override - public int size() { - return shares.size(); - } - - @Override - public boolean isEmpty() { - return shares.isEmpty(); - } - - @Override - public boolean contains(Object o) { - return shares.contains(o); - } - - @Override - public Object[] toArray() { - return Collections.unmodifiableCollection(this).toArray(); - } - - @Override - public T[] toArray(T[] a) { - return Collections.unmodifiableCollection(this).toArray(a); - } - - @Override - public boolean add(Sharable sharable) { - throw new IllegalStateException("May not alter SharableGroup!"); - } - - @Override - public boolean remove(Object o) { - throw new IllegalStateException("May not alter SharableGroup!"); - } - - @Override - public boolean containsAll(Collection c) { - return shares.containsAll(c); - } - - @Override - public boolean addAll(Collection c) { - throw new IllegalStateException("May not alter SharableGroup!"); - } - - @Override - public boolean removeAll(Collection c) { - throw new IllegalStateException("May not alter SharableGroup!"); - } - - @Override - public boolean retainAll(Collection c) { - throw new IllegalStateException("May not alter SharableGroup!"); - } - - @Override - public void clear() { - throw new IllegalStateException("May not alter SharableGroup!"); - } -} +package org.mvplugins.multiverse.inventories.share; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * This class represents a grouping of Sharable objects for the sole purpose of being able to use one keyword in + * group setups to indicate multiple {@link Sharable}s. + */ +final class SharableGroup implements Shares { + + private final String[] names; + private final Shares shares; + + public SharableGroup(String name, Shares shares, String... alternateNames) { + this.names = new String[alternateNames.length + 1]; + this.names[0] = name; + System.arraycopy(alternateNames, 0, this.names, 1, alternateNames.length); + this.shares = shares; + for (String lookupName : this.names) { + Sharables.LOOKUP_MAP.put(lookupName, this); + } + } + + /** + * @return The names of this SharableGroup for setting as shared in the config. + * All names in this array may be used to set a group as sharing this SharableGroup. + */ + public String[] getNames() { + return this.names; + } + + @Override + public void mergeShares(Shares newShares) { + throw new IllegalStateException("May not alter SharableGroup!"); + } + + @Override + public boolean isSharing(Sharable sharable) { + return shares.isSharing(sharable); + } + + @Override + public boolean isSharing(Shares shares) { + return this.shares.isSharing(shares); + } + + @Override + public Shares compare(Shares shares) { + return this.shares.compare(shares); + } + + @Override + public Shares setSharing(Sharable sharable, boolean sharing) { + throw new IllegalStateException("May not alter SharableGroup!"); + } + + @Override + public Shares setSharing(Shares sharables, boolean sharing) { + throw new IllegalStateException("May not alter SharableGroup!"); + } + + @Override + public List toStringList() { + return shares.toStringList(); + } + + @Override + public Iterator iterator() { + return shares.iterator(); + } + + @Override + public int size() { + return shares.size(); + } + + @Override + public boolean isEmpty() { + return shares.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return shares.contains(o); + } + + @Override + public Object[] toArray() { + return Collections.unmodifiableCollection(this).toArray(); + } + + @Override + public T[] toArray(T[] a) { + return Collections.unmodifiableCollection(this).toArray(a); + } + + @Override + public boolean add(Sharable sharable) { + throw new IllegalStateException("May not alter SharableGroup!"); + } + + @Override + public boolean remove(Object o) { + throw new IllegalStateException("May not alter SharableGroup!"); + } + + @Override + public boolean containsAll(Collection c) { + return shares.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + throw new IllegalStateException("May not alter SharableGroup!"); + } + + @Override + public boolean removeAll(Collection c) { + throw new IllegalStateException("May not alter SharableGroup!"); + } + + @Override + public boolean retainAll(Collection c) { + throw new IllegalStateException("May not alter SharableGroup!"); + } + + @Override + public void clear() { + throw new IllegalStateException("May not alter SharableGroup!"); + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/SharableHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/share/SharableHandler.java similarity index 80% rename from src/main/java/com/onarandombox/multiverseinventories/share/SharableHandler.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/SharableHandler.java index fe853755..925bd4f6 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/SharableHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/SharableHandler.java @@ -1,7 +1,8 @@ -package com.onarandombox.multiverseinventories.share; +package org.mvplugins.multiverse.inventories.share; -import com.onarandombox.multiverseinventories.profile.PlayerProfile; +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile; import org.bukkit.entity.Player; +import org.mvplugins.multiverse.inventories.profile.data.ProfileData; /** * This class is used to handle the transition of data from a player profile to a player and vice versa, typically @@ -19,7 +20,7 @@ public interface SharableHandler { * with the values of the player. * @param player The player whose values will be used to update the given profile. */ - void updateProfile(PlayerProfile profile, Player player); + void updateProfile(ProfileData profile, Player player); /** * This method is called during share handling (aka PlayerChangeWorldEvent). It will perform updates to @@ -30,5 +31,5 @@ public interface SharableHandler { * @param profile The profile whose values will be used to update the give player. * @return True if player was updated from existing profile. False if default was used (new profile). */ - boolean updatePlayer(Player player, PlayerProfile profile); + boolean updatePlayer(Player player, ProfileData profile); } diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/SharableSerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/SharableSerializer.java similarity index 93% rename from src/main/java/com/onarandombox/multiverseinventories/share/SharableSerializer.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/SharableSerializer.java index 4c078b1c..d42fe40f 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/SharableSerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/SharableSerializer.java @@ -1,29 +1,29 @@ -package com.onarandombox.multiverseinventories.share; - -/** - * This represents how a Sharable's data will be serialized/deserialized. - * - * @param The type of data the {@link Sharable} this belongs to represents. - */ -public interface SharableSerializer { - - /** - * This deserializes the data for a Sharable. You must be expecting the type of data coming in in order to process - * this. That type will generally be the type that this serializes as with {@link #serialize(Object)}. - * - * @param obj The incoming (serialized) data to be deserialized. - * @return The data represented by the Sharable this object represents in deserialized form. - */ - T deserialize(Object obj); - - /** - * This serializes the data for a Sharable. The output is an Object but what you return is up to you, however, - * this is limited by the constraints of the persistence method. Generally, returning a String is the safest way - * to serialize your data. Most boxed primitives are accepted as well as Lists of boxed primitives and - * {@link java.util.Map}<{@link String}, {@link Object}>. - * - * @param t The value of the data represented by the Sharable. - * @return The serialized form of the data. - */ - Object serialize(T t); -} +package org.mvplugins.multiverse.inventories.share; + +/** + * This represents how a Sharable's data will be serialized/deserialized. + * + * @param The type of data the {@link Sharable} this belongs to represents. + */ +public interface SharableSerializer { + + /** + * This deserializes the data for a Sharable. You must be expecting the type of data coming in in order to process + * this. That type will generally be the type that this serializes as with {@link #serialize(Object)}. + * + * @param obj The incoming (serialized) data to be deserialized. + * @return The data represented by the Sharable this object represents in deserialized form. + */ + T deserialize(Object obj); + + /** + * This serializes the data for a Sharable. The output is an Object but what you return is up to you, however, + * this is limited by the constraints of the persistence method. Generally, returning a String is the safest way + * to serialize your data. Most boxed primitives are accepted as well as Lists of boxed primitives and + * {@link java.util.Map}<{@link String}, {@link Object}>. + * + * @param t The value of the data represented by the Sharable. + * @return The serialized form of the data. + */ + Object serialize(T t); +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java similarity index 63% rename from src/main/java/com/onarandombox/multiverseinventories/share/Sharables.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index c66aec90..dc94c24a 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -1,27 +1,47 @@ -package com.onarandombox.multiverseinventories.share; +package org.mvplugins.multiverse.inventories.share; import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import com.onarandombox.multiverseinventories.WorldGroup; -import com.onarandombox.multiverseinventories.DataStrings; -import com.onarandombox.multiverseinventories.PlayerStats; -import com.onarandombox.multiverseinventories.profile.PlayerProfile; -import com.onarandombox.multiverseinventories.util.MinecraftTools; +import com.google.common.collect.Sets; +import org.bukkit.advancement.AdvancementProgress; +import org.mvplugins.multiverse.core.economy.MVEconomist; +import org.mvplugins.multiverse.core.teleportation.AsyncSafetyTeleporter; +import org.mvplugins.multiverse.external.vavr.control.Option; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; +import org.mvplugins.multiverse.inventories.profile.data.ProfileData; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroup; +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager; +import org.mvplugins.multiverse.inventories.util.DataStrings; +import org.mvplugins.multiverse.inventories.util.MinecraftTools; +import org.mvplugins.multiverse.inventories.util.PlayerStats; +import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.bukkit.Statistic; import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.potion.PotionEffect; + +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import static org.mvplugins.multiverse.inventories.util.MinecraftTools.findBedFromRespawnLocation; /** * The Sharables class is where all the default Sharable instances are located as constants as well as a factory class @@ -29,14 +49,24 @@ */ public final class Sharables implements Shares { - private static final Shares ALL_SHARABLES = new Sharables(new LinkedHashSet()); + private static final Shares ALL_SHARABLES = new Sharables(new LinkedHashSet<>()); + private static final Shares STANDARD_SHARABLES = new Sharables(new LinkedHashSet<>()); + private static final Shares OPTIONAL_SHARABLES = new Sharables(new LinkedHashSet<>()); /** * The map used to lookup a Sharable or set of Sharables by their name. */ static final Map LOOKUP_MAP = new HashMap(); + private static Shares enabledShares = noneOf(); + private static MultiverseInventories inventories = null; + private static InventoriesConfig inventoriesConfig = null; + private static WorldGroupManager worldGroupManager = null; + private static MVEconomist economist = null; + private static AsyncSafetyTeleporter safetyTeleporter = null; + + private static Attribute maxHealthAttr = null; /** * Initialize this class with the instance of Inventories. @@ -44,8 +74,22 @@ public final class Sharables implements Shares { * @param inventories the instance of Inventories. */ public static void init(MultiverseInventories inventories) { - if (Sharables.inventories == null) { - Sharables.inventories = inventories; + Sharables.inventories = inventories; + Sharables.economist = inventories.getServiceLocator().getService(MVEconomist.class); + Sharables.safetyTeleporter = inventories.getServiceLocator().getService(AsyncSafetyTeleporter.class); + Sharables.inventoriesConfig = inventories.getServiceLocator().getService(InventoriesConfig.class); + Sharables.worldGroupManager = inventories.getServiceLocator().getService(WorldGroupManager.class); + initMaxHealthAttr(); + } + + private static void initMaxHealthAttr() { + Sharables.maxHealthAttr = Registry.ATTRIBUTE.get(NamespacedKey.minecraft("max_health")); + if (Sharables.maxHealthAttr == null) { + // Old key for older minecraft version (<1.21) + Sharables.maxHealthAttr = Registry.ATTRIBUTE.get(NamespacedKey.minecraft("generic.max_health")); + } + if (Sharables.maxHealthAttr == null) { + Logging.warning("Could not find max_health attribute. Health related sharables may not work as expected."); } } @@ -55,12 +99,12 @@ public static void init(MultiverseInventories inventories) { public static final Sharable ENDER_CHEST = new Sharable.Builder("ender_chest", ItemStack[].class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(ENDER_CHEST, player.getEnderChest().getContents()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { ItemStack[] value = profile.get(ENDER_CHEST); if (value == null) { player.getEnderChest().setContents(MinecraftTools.fillWithAir( @@ -79,21 +123,19 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable INVENTORY = new Sharable.Builder("inventory_contents", ItemStack[].class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(INVENTORY, player.getInventory().getContents()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { ItemStack[] value = profile.get(INVENTORY); if (value == null) { player.getInventory().setContents(MinecraftTools.fillWithAir( new ItemStack[PlayerStats.INVENTORY_SIZE])); - player.updateInventory(); return false; } player.getInventory().setContents(value); - player.updateInventory(); return true; } }).serializer(new ProfileEntry(false, DataStrings.PLAYER_INVENTORY_CONTENTS), @@ -105,21 +147,19 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable ARMOR = new Sharable.Builder("armor_contents", ItemStack[].class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(ARMOR, player.getInventory().getArmorContents()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { ItemStack[] value = profile.get(ARMOR); if (value == null) { player.getInventory().setArmorContents(MinecraftTools.fillWithAir( new ItemStack[PlayerStats.ARMOR_SIZE])); - player.updateInventory(); return false; } player.getInventory().setArmorContents(value); - player.updateInventory(); return true; } }).serializer(new ProfileEntry(false, DataStrings.PLAYER_ARMOR_CONTENTS), @@ -131,24 +171,47 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable OFF_HAND = new Sharable.Builder("off_hand", ItemStack.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(OFF_HAND, player.getInventory().getItemInOffHand()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { ItemStack value = profile.get(OFF_HAND); if (value == null) { player.getInventory().setItemInOffHand(new ItemStack(Material.AIR)); - player.updateInventory(); return false; } player.getInventory().setItemInOffHand(value); - player.updateInventory(); return true; } }).serializer(new ProfileEntry(false, DataStrings.PLAYER_OFF_HAND_ITEM), - new DefaultSerializer<>(ItemStack.class)).altName("shield").build(); + new ItemStackSerializer()).altName("shield").build(); + + /** + * Sharing Max Health. + */ + public static final Sharable MAX_HEALTH = new Sharable.Builder<>("max_hit_points", Double.class, + new SharableHandler() { + @Override + public void updateProfile(ProfileData profile, Player player) { + profile.set(MAX_HEALTH, getMaxHealth(player)); + } + + @Override + public boolean updatePlayer(Player player, ProfileData profile) { + Double value = profile.get(MAX_HEALTH); + if (value == null) { + Option.of(maxHealthAttr).map(player::getAttribute) + .peek(attr -> attr.setBaseValue(attr.getDefaultValue())); + return false; + } + Option.of(maxHealthAttr).map(player::getAttribute) + .peek(attr -> attr.setBaseValue(value)); + return true; + } + }).stringSerializer(new ProfileEntry(true, DataStrings.PLAYER_MAX_HEALTH)) + .altName("maxhealth").altName("maxhp").altName("maxhitpoints").build(); /** * Sharing Health. @@ -156,46 +219,60 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable HEALTH = new Sharable.Builder("hit_points", Double.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { double health = player.getHealth(); // Player is dead, so health should be regained to full. if (health <= 0) { - health = player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue(); + health = getMaxHealth(player); } profile.set(HEALTH, health); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Double value = profile.get(HEALTH); if (value == null) { player.setHealth(PlayerStats.HEALTH); return false; } try { + double maxHealth = getMaxHealth(player); + // This share may handled before MAX_HEALTH. + // Thus this is needed to ensure there is no loss in health stored + if (value > maxHealth) { + Option.of(maxHealthAttr).map(player::getAttribute) + .peek(attr -> attr.setBaseValue(maxHealth)); + } player.setHealth(value); } catch (IllegalArgumentException e) { Logging.fine("Invalid value '" + value + "': " + e.getMessage()); - player.setHealth(player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getValue()); + player.setHealth(PlayerStats.HEALTH); return false; } return true; } + }).stringSerializer(new ProfileEntry(true, DataStrings.PLAYER_HEALTH)) .altName("health").altName("hp").altName("hitpoints").build(); + private static double getMaxHealth(Player player) { + return Option.of(maxHealthAttr).map(player::getAttribute) + .map(AttributeInstance::getValue) + .getOrElse(PlayerStats.MAX_HEALTH); + } + /** * Sharing Remaining Air. */ public static final Sharable REMAINING_AIR = new Sharable.Builder("remaining_air", Integer.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(REMAINING_AIR, player.getRemainingAir()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Integer value = profile.get(REMAINING_AIR); if (value == null) { player.setRemainingAir(PlayerStats.REMAINING_AIR); @@ -218,12 +295,12 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable MAXIMUM_AIR = new Sharable.Builder("maximum_air", Integer.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(MAXIMUM_AIR, player.getMaximumAir()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Integer value = profile.get(MAXIMUM_AIR); if (value == null) { player.setMaximumAir(PlayerStats.MAXIMUM_AIR); @@ -246,12 +323,12 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable FALL_DISTANCE = new Sharable.Builder("fall_distance", Float.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(FALL_DISTANCE, player.getFallDistance()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Float value = profile.get(FALL_DISTANCE); if (value == null) { player.setFallDistance(PlayerStats.FALL_DISTANCE); @@ -275,12 +352,12 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable FIRE_TICKS = new Sharable.Builder("fire_ticks", Integer.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(FIRE_TICKS, player.getFireTicks()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Integer value = profile.get(FIRE_TICKS); if (value == null) { player.setFireTicks(PlayerStats.FIRE_TICKS); @@ -305,12 +382,12 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable EXPERIENCE = new Sharable.Builder("xp", Float.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(EXPERIENCE, player.getExp()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Float value = profile.get(EXPERIENCE); if (value == null) { player.setExp(PlayerStats.EXPERIENCE); @@ -333,12 +410,12 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable LEVEL = new Sharable.Builder("lvl", Integer.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(LEVEL, player.getLevel()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Integer value = profile.get(LEVEL); if (value == null) { player.setLevel(PlayerStats.LEVEL); @@ -361,12 +438,12 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable TOTAL_EXPERIENCE = new Sharable.Builder("total_xp", Integer.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(TOTAL_EXPERIENCE, player.getTotalExperience()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Integer value = profile.get(TOTAL_EXPERIENCE); if (value == null) { player.setTotalExperience(PlayerStats.TOTAL_EXPERIENCE); @@ -389,12 +466,12 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable FOOD_LEVEL = new Sharable.Builder("food_level", Integer.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(FOOD_LEVEL, player.getFoodLevel()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Integer value = profile.get(FOOD_LEVEL); if (value == null) { player.setFoodLevel(PlayerStats.FOOD_LEVEL); @@ -418,12 +495,12 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable EXHAUSTION = new Sharable.Builder("exhaustion", Float.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(EXHAUSTION, player.getExhaustion()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Float value = profile.get(EXHAUSTION); if (value == null) { player.setExhaustion(PlayerStats.EXHAUSTION); @@ -447,12 +524,12 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable SATURATION = new Sharable.Builder("saturation", Float.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { profile.set(SATURATION, player.getSaturation()); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Float value = profile.get(SATURATION); if (value == null) { player.setSaturation(PlayerStats.SATURATION); @@ -476,10 +553,15 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable BED_SPAWN = new Sharable.Builder("bed_spawn", Location.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { + if (inventories.isUsingSpawnChangeEvent()) { + // Bed spawn location already updated during PlayerSpawnChangeEvent + return; + } Location bedSpawnLocation = null; try { - bedSpawnLocation = player.getBedSpawnLocation(); + Logging.finer("profile bed: " + player.getBedSpawnLocation()); + bedSpawnLocation = findBedFromRespawnLocation(player.getBedSpawnLocation()); } catch (NullPointerException e) { // TODO this is a temporary fix for the bug occurring in 1.16.X CB/Spigot/Paper StackTraceElement[] stackTrace = e.getStackTrace(); @@ -495,36 +577,49 @@ public void updateProfile(PlayerProfile profile, Player player) { } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Location loc = profile.get(BED_SPAWN); if (loc == null) { - player.setBedSpawnLocation(player.getWorld().getSpawnLocation()); + Logging.finer("No bed location saved"); + ignoreSpawnListener.add(player.getUniqueId()); + player.setBedSpawnLocation(player.getWorld().getSpawnLocation(), true); + ignoreSpawnListener.remove(player.getUniqueId()); return false; } + ignoreSpawnListener.add(player.getUniqueId()); player.setBedSpawnLocation(loc, true); + ignoreSpawnListener.remove(player.getUniqueId()); + Logging.finer("updating bed: " + player.getBedSpawnLocation()); return true; } }).serializer(new ProfileEntry(false, DataStrings.PLAYER_BED_SPAWN_LOCATION), new LocationSerializer()) .altName("bedspawn").altName("bed").altName("beds").altName("bedspawns").build(); + // todo: handle this somewhere better + private static List ignoreSpawnListener = new ArrayList<>(); + + public static boolean isIgnoringSpawnListener(Player player) { + return ignoreSpawnListener.contains(player.getUniqueId()); + } + /** * Sharing Last Location. */ public static final Sharable LAST_LOCATION = new Sharable.Builder("last_location", Location.class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { /* It's too late to update the profile for last location here because the world change has already happened. The update occurs in the PlayerTeleportEvent handler in InventoriesListener. */ } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { Location loc = profile.get(LAST_LOCATION); - if (loc == null) { + if (loc == null || loc.getWorld() == null || loc.equals(player.getLocation())) { return false; } - player.teleport(loc); + safetyTeleporter.to(loc).checkSafety(false).teleport(player); return true; } }).serializer(new ProfileEntry(false, DataStrings.PLAYER_LAST_LOCATION), new LocationSerializer()) @@ -536,7 +631,7 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable ECONOMY = new Sharable.Builder("economy", Double.class, new SharableHandler() { private boolean hasValidEconomyHandler() { - if (inventories.getCore().getEconomist().isUsingEconomyPlugin()) { + if (economist.isUsingEconomyPlugin()) { return true; } Logging.warning("You do not have an an economy plugin with Vault. Economy sharable will not work!"); @@ -546,24 +641,24 @@ private boolean hasValidEconomyHandler() { } @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { if (!hasValidEconomyHandler()) { return; } - profile.set(ECONOMY, inventories.getCore().getEconomist().getBalance(player)); + profile.set(ECONOMY, economist.getBalance(player)); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { if (!hasValidEconomyHandler()) { return false; } Double money = profile.get(ECONOMY); if (money == null) { - inventories.getCore().getEconomist().setBalance(player, 0); + economist.setBalance(player, 0); return false; } - inventories.getCore().getEconomist().setBalance(player, money); + economist.setBalance(player, money); return true; } }).stringSerializer(new ProfileEntry(false, "balance")).optional() @@ -575,13 +670,13 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { public static final Sharable POTIONS = new Sharable.Builder("potion_effects", PotionEffect[].class, new SharableHandler() { @Override - public void updateProfile(PlayerProfile profile, Player player) { + public void updateProfile(ProfileData profile, Player player) { Collection potionEffects = player.getActivePotionEffects(); profile.set(POTIONS, potionEffects.toArray(new PotionEffect[potionEffects.size()])); } @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { + public boolean updatePlayer(Player player, ProfileData profile) { PotionEffect[] effects = profile.get(POTIONS); for (PotionEffect effect : player.getActivePotionEffects()) { player.removePotionEffect(effect.getType()); @@ -597,22 +692,149 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { }).serializer(new ProfileEntry(false, "potions"), new PotionEffectSerializer()) .altName("potion").altName("potions").build(); + /** + * Sharing Advancements. + */ + public static final Sharable ADVANCEMENTS = new Sharable.Builder<>("advancements", List.class, + new SharableHandler<>() { + @Override + public void updateProfile(ProfileData profile, Player player) { + Set completedAdvancements = new HashSet<>(); + Bukkit.advancementIterator().forEachRemaining(advancement -> { + Collection awardedCriteria = player.getAdvancementProgress(advancement).getAwardedCriteria(); + completedAdvancements.addAll(awardedCriteria); + }); + profile.set(ADVANCEMENTS, new ArrayList<>(completedAdvancements)); + } + + @Override + public boolean updatePlayer(Player player, ProfileData profile) { + List advancements = profile.get(ADVANCEMENTS); + Set processedCriteria = new HashSet<>(); + Set completedCriteria = (advancements != null) ? new HashSet<>(advancements) : new HashSet<>(); + + // Advancements may cause the player to level up, which we don't want to happen + int totalExperience = player.getTotalExperience(); + int level = player.getLevel(); + float exp = player.getExp(); + + Bukkit.advancementIterator().forEachRemaining(advancement -> { + AdvancementProgress advancementProgress = player.getAdvancementProgress(advancement); + for (String criteria : advancement.getCriteria()) { + if (processedCriteria.contains(criteria)) { + continue; + } else if (completedCriteria.contains(criteria)) { + advancementProgress.awardCriteria(criteria); + } else { + advancementProgress.revokeCriteria(criteria); + } + processedCriteria.add(criteria); + } + }); + + // Set back the level from before applying the advancements + player.setExp(exp); + player.setLevel(level); + player.setTotalExperience(totalExperience); + + return advancements != null; + } + }).defaultSerializer(new ProfileEntry(false, "advancements")).altName("achievements").optional().build(); + + /** + * Sharing Statistics. + */ + public static final Sharable GAME_STATISTICS = new Sharable.Builder<>("game_statistics", Map.class, + new SharableHandler<>() { + @Override + public void updateProfile(ProfileData profile, Player player) { + Map playerStats = new HashMap<>(); + for (Statistic stat: Statistic.values()) { + if (stat.getType() == Statistic.Type.UNTYPED) { + int val = player.getStatistic(stat); + // no need to save values of 0, that's the default! + if (val != 0) { + playerStats.put(stat.name(), val); + } + } + } + profile.set(GAME_STATISTICS, playerStats); + } + + @Override + public boolean updatePlayer(Player player, ProfileData profile) { + Map playerStats = profile.get(GAME_STATISTICS); + if (playerStats == null) { + // Set all to 0 + for (Statistic stat : Statistic.values()) { + if (stat.getType() == Statistic.Type.UNTYPED) { + player.setStatistic(stat, 0); + } + } + return false; + } + + for (Statistic stat : Statistic.values()) { + if (stat.getType() == Statistic.Type.UNTYPED) { + player.setStatistic(stat, playerStats.getOrDefault(stat.name(), 0)); + } + } + + return true; + } + }).defaultSerializer(new ProfileEntry(false, "game_statistics")).altName("game_stats").optional().build(); + + /** + * Sharing Recipes. + */ + public static final Sharable RECIPES = new Sharable.Builder<>("recipes", List.class, + new SharableHandler<>() { + @Override + public void updateProfile(ProfileData profile, Player player) { + List recipes = player.getDiscoveredRecipes().stream() + // Save space by removing the namespace if its default minecraft + .map(key -> NamespacedKey.MINECRAFT.equals(key.getNamespace()) + ? key.getKey() : key.toString()) + .toList(); + profile.set(RECIPES, recipes); + } + + @Override + public boolean updatePlayer(Player player, ProfileData profile) { + List recipes = profile.get(RECIPES); + if (recipes == null) { + player.undiscoverRecipes(player.getDiscoveredRecipes()); + return false; + } + + Set discoveredRecipes = player.getDiscoveredRecipes(); + Set toDiscover = recipes.stream().map(NamespacedKey::fromString) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + player.undiscoverRecipes(Sets.difference(discoveredRecipes, toDiscover)); + player.discoverRecipes(Sets.difference(toDiscover, discoveredRecipes)); + + return true; + } + }).defaultSerializer(new ProfileEntry(false, "recipes")).optional().build(); + /** * Grouping for inventory sharables. */ - public static final SharableGroup ALL_INVENTORY = new SharableGroup("inventory", + public static final Shares ALL_INVENTORY = new SharableGroup("inventory", fromSharables(INVENTORY, ARMOR, ENDER_CHEST, OFF_HAND), "inv", "inventories"); /** * Grouping for experience sharables. */ - public static final SharableGroup ALL_EXPERIENCE = new SharableGroup("experience", + public static final Shares ALL_EXPERIENCE = new SharableGroup("experience", fromSharables(EXPERIENCE, TOTAL_EXPERIENCE, LEVEL), "exp", "level"); /** * Grouping for air/breath related sharables. */ - public static final SharableGroup AIR = new SharableGroup("air", + public static final Shares AIR = new SharableGroup("air", fromSharables(REMAINING_AIR, MAXIMUM_AIR), "breath"); /** @@ -625,22 +847,23 @@ public boolean updatePlayer(Player player, PlayerProfile profile) { * Grouping for player health related sharables. */ public static final SharableGroup ALL_HEALTH = new SharableGroup("health", - fromSharables(HEALTH, REMAINING_AIR, MAXIMUM_AIR, FALL_DISTANCE, FIRE_TICKS)); + fromSharables(HEALTH, MAX_HEALTH, REMAINING_AIR, MAXIMUM_AIR, FALL_DISTANCE, FIRE_TICKS)); /** * Grouping for player stat related sharables not including inventory. */ public static final SharableGroup STATS = new SharableGroup("stats", - fromSharables(HEALTH, FOOD_LEVEL, SATURATION, EXHAUSTION, EXPERIENCE, TOTAL_EXPERIENCE, LEVEL, - REMAINING_AIR, MAXIMUM_AIR, FALL_DISTANCE, FIRE_TICKS, POTIONS)); + fromSharables(HEALTH, MAX_HEALTH, FOOD_LEVEL, SATURATION, EXHAUSTION, EXPERIENCE, TOTAL_EXPERIENCE, LEVEL, + REMAINING_AIR, MAXIMUM_AIR, FALL_DISTANCE, FIRE_TICKS, POTIONS, GAME_STATISTICS, ADVANCEMENTS)); /** * Grouping for ALL default sharables. * TODO: make this really mean all, including 3rd party. */ - public static final SharableGroup ALL_DEFAULT = new SharableGroup("all", fromSharables(HEALTH, ECONOMY, - FOOD_LEVEL, SATURATION, EXHAUSTION, EXPERIENCE, TOTAL_EXPERIENCE, LEVEL, INVENTORY, ARMOR, BED_SPAWN, - MAXIMUM_AIR, REMAINING_AIR, FALL_DISTANCE, FIRE_TICKS, POTIONS, LAST_LOCATION, ENDER_CHEST, OFF_HAND), + public static final SharableGroup ALL_DEFAULT = new SharableGroup("all", fromSharables(HEALTH, MAX_HEALTH, + ECONOMY, FOOD_LEVEL, SATURATION, EXHAUSTION, EXPERIENCE, TOTAL_EXPERIENCE, LEVEL, INVENTORY, ARMOR, BED_SPAWN, + MAXIMUM_AIR, REMAINING_AIR, FALL_DISTANCE, FIRE_TICKS, POTIONS, LAST_LOCATION, ENDER_CHEST, OFF_HAND, + GAME_STATISTICS, ADVANCEMENTS, RECIPES), "*", "everything"); @@ -655,15 +878,20 @@ static boolean register(Sharable sharable) { if (!ALL_SHARABLES.contains(sharable)) { // If the plugin has been enabled, we need to add this sharable to the existing groups with all sharables. if (inventories != null) { - for (WorldGroup group : inventories.getGroupManager().getGroups()) { + var worldGroupManager = inventories.getServiceLocator().getService(WorldGroupManager.class); + for (WorldGroup group : worldGroupManager.getGroups()) { if (group.getShares().isSharing(Sharables.all())) { group.getShares().setSharing(sharable, true); - } } } } if (ALL_SHARABLES.add(sharable)) { + if (sharable.isOptional()) { + OPTIONAL_SHARABLES.add(sharable); + } else { + STANDARD_SHARABLES.add(sharable); + } for (String name : sharable.getNames()) { String key = name.toLowerCase(); Shares shares = LOOKUP_MAP.get(key); @@ -688,6 +916,13 @@ public static Shares lookup(String name) { return LOOKUP_MAP.get(name.toLowerCase()); } + /** + * @return A collection of all registered {@link Shares}. This is NOT to be modified and serves only as a reference. + */ + public static Collection getShareNames() { + return LOOKUP_MAP.keySet(); + } + /** * @return A {@link Shares} collection containing ALL registered {@link Sharable}s. This is NOT to be modified and * serves only as a reference. For a version you can do what you want with, see {@link #allOf()}. @@ -696,18 +931,60 @@ public static Shares all() { return ALL_SHARABLES; } + /** + * @return A {@link Shares} collection containing ALL registered standard {@link Sharable}s. This is NOT to be modified and + * serves only as a reference. For a version you can do what you want with, see {@link #allOf()}. + */ + public static Shares standard() { + return STANDARD_SHARABLES; + } + + /** + * @return A {@link Shares} collection containing ALL registered enabled {@link Sharable}s. This is NOT to be modified and + * serves only as a reference. For a version you can do what you want with, see {@link #allOf()}. + */ + public static Shares enabled() { + return enabledShares; + } + + /** + * @return A {@link Shares} collection containing ALL registered optional {@link Sharable}s. This is NOT to be modified and + * serves only as a reference. For a version you can do what you want with, see {@link #optionalOf()}. + */ + public static Shares optional() { + return OPTIONAL_SHARABLES; + } + /** * @return A new {@link Shares} instance containing ALL registered {@link Sharable}s for your own devices. */ public static Shares allOf() { - return new Sharables(new LinkedHashSet(ALL_SHARABLES)); + return new Sharables(new LinkedHashSet<>(ALL_SHARABLES)); + } + + /** + * @return A new {@link Shares} instance containing ALL enabled optional {@link Sharable}s for your own devices. + */ + public static Shares enabledOf() { + return new Sharables(new LinkedHashSet<>(enabledShares)); + } + + public static Shares standardOf() { + return new Sharables(new LinkedHashSet<>(STANDARD_SHARABLES)); + } + + /** + * @return A new {@link Shares} instance containing ALL registered optional {@link Sharable}s for your own devices. + */ + public static Shares optionalOf() { + return new Sharables(new LinkedHashSet<>(OPTIONAL_SHARABLES)); } /** * @return A new empty {@link Shares} instance for your own devices. */ public static Shares noneOf() { - return new Sharables(new LinkedHashSet(ALL_SHARABLES.size())); + return new Sharables(new LinkedHashSet<>(ALL_SHARABLES.size())); } /** @@ -716,9 +993,9 @@ public static Shares noneOf() { * contained in the shares argument. */ public static Shares complimentOf(Shares shares) { - Set compliment = Sharables.allOf(); + Shares compliment = Sharables.allOf(); compliment.removeAll(shares); - return new Sharables(compliment); + return compliment; } /** @@ -809,6 +1086,13 @@ public static Shares fromList(List sharesList) { return shares; } + public static void recalculateEnabledShares() { + Logging.finer("Recalculating enabled shares..."); + enabledShares = standardOf(); + enabledShares.addAll(inventoriesConfig.getActiveOptionalShares()); + worldGroupManager.recalculateApplicableShares(); + } + private Set sharables; private Sharables(Set sharableSet) { @@ -907,19 +1191,20 @@ public void mergeShares(Shares newShares) { * {@inheritDoc} */ @Override - public void setSharing(Sharable sharable, boolean sharing) { + public Shares setSharing(Sharable sharable, boolean sharing) { if (sharing) { this.add(sharable); } else { this.remove(sharable); } + return this; } /** * {@inheritDoc} */ @Override - public void setSharing(Shares sharables, boolean sharing) { + public Shares setSharing(Shares sharables, boolean sharing) { for (Sharable sharable : sharables) { if (sharing) { this.add(sharable); @@ -927,6 +1212,7 @@ public void setSharing(Shares sharables, boolean sharing) { this.remove(sharable); } } + return this; } @Override diff --git a/src/main/java/com/onarandombox/multiverseinventories/share/Shares.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Shares.java similarity index 86% rename from src/main/java/com/onarandombox/multiverseinventories/share/Shares.java rename to src/main/java/org/mvplugins/multiverse/inventories/share/Shares.java index a523bf27..631a8eae 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/share/Shares.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Shares.java @@ -1,56 +1,56 @@ -package com.onarandombox.multiverseinventories.share; - -import java.util.Collection; -import java.util.List; -import java.util.Set; - -/** - * Interface for getting what is shared in a world player. - */ -public interface Shares extends Cloneable, Iterable, Collection, Set { - - /** - * Merges what is shared with another share. Only the false items should be merged. - * - * @param newShares The set of shares to merge into this set of shares. - */ - void mergeShares(Shares newShares); - - /** - * @param sharable The Sharable you want to check for. - * @return True if it is sharing the sharable. - */ - boolean isSharing(Sharable sharable); - - /** - * @param shares Shares to compare with. - * @return True if it is sharing the same sharables. - */ - boolean isSharing(Shares shares); - - /** - * Checks to see if any of the sharables passed in are shared by this Shares. - * - * @param shares Shares to check for. - * @return A Set containing all of the Sharables both sets contain. - */ - Shares compare(Shares shares); - - /** - * @param sharable The Sharable you wish to set sharing for. - * @param sharing Whether to share or not. - */ - void setSharing(Sharable sharable, boolean sharing); - - /** - * @param sharables a Set of Sharables you wish to set sharing for. - * @param sharing Whether to share or not. - */ - void setSharing(Shares sharables, boolean sharing); - - /** - * @return These shares as a string list. - */ - List toStringList(); -} - +package org.mvplugins.multiverse.inventories.share; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +/** + * Interface for getting what is shared in a world player. + */ +public interface Shares extends Cloneable, Iterable, Collection, Set { + + /** + * Merges what is shared with another share. Only the false items should be merged. + * + * @param newShares The set of shares to merge into this set of shares. + */ + void mergeShares(Shares newShares); + + /** + * @param sharable The Sharable you want to check for. + * @return True if it is sharing the sharable. + */ + boolean isSharing(Sharable sharable); + + /** + * @param shares Shares to compare with. + * @return True if it is sharing the same sharables. + */ + boolean isSharing(Shares shares); + + /** + * Checks to see if any of the sharables passed in are shared by this Shares. + * + * @param shares Shares to check for. + * @return A Set containing all of the Sharables both sets contain. + */ + Shares compare(Shares shares); + + /** + * @param sharable The Sharable you wish to set sharing for. + * @param sharing Whether to share or not. + */ + Shares setSharing(Sharable sharable, boolean sharing); + + /** + * @param sharables a Set of Sharables you wish to set sharing for. + * @param sharing Whether to share or not. + */ + Shares setSharing(Shares sharables, boolean sharing); + + /** + * @return These shares as a string list. + */ + List toStringList(); +} + diff --git a/src/main/java/org/mvplugins/multiverse/inventories/share/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/share/package-info.java new file mode 100644 index 00000000..b4eb4791 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/package-info.java @@ -0,0 +1,5 @@ +/** + * Contains all external API classes for {@link org.mvplugins.multiverse.inventories.share.Sharable}s and handling the sharing of those between worlds. + */ +package org.mvplugins.multiverse.inventories.share; + diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/DataStrings.java b/src/main/java/org/mvplugins/multiverse/inventories/util/DataStrings.java new file mode 100644 index 00000000..3e7be136 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/DataStrings.java @@ -0,0 +1,148 @@ +package org.mvplugins.multiverse.inventories.util; + +/** + * This class handles the formatting of strings for data i/o. + */ +public final class DataStrings { + + /** + * Delimiter to separate a key and it's value. + */ + public static final String VALUE_DELIMITER = ":"; + /** + * Player stats identifier. + */ + public static final String PLAYER_STATS = "stats"; + /** + * Player inventory contents identifier. + */ + public static final String PLAYER_INVENTORY_CONTENTS = "inventoryContents"; + /** + * Player armor contents identifier. + */ + public static final String PLAYER_ARMOR_CONTENTS = "armorContents"; + /** + * Player off hand item identifier. + */ + public static final String PLAYER_OFF_HAND_ITEM = "offHandItem"; + /** + * Ender chest inventory contents identifier. + */ + public static final String ENDER_CHEST_CONTENTS = "enderChestContents"; + /** + * Player bed spawn location identifier. + */ + public static final String PLAYER_BED_SPAWN_LOCATION = "bedSpawnLocation"; + /** + * Player last location identifier. + */ + public static final String PLAYER_LAST_LOCATION = "lastLocation"; + /** + * Player global profile data + */ + public static final String PLAYER_DATA = "playerData"; + /** + * Player last world identifier. + */ + public static final String PLAYER_LAST_WORLD = "lastWorld"; + /** + * Player should load identifier. + */ + public static final String PLAYER_SHOULD_LOAD = "shouldLoad"; + /** + * Player last known name identifier. + */ + public static final String PLAYER_LAST_KNOWN_NAME = "lastKnownName"; + /** + * Player profile type identifier. + */ + public static final String PLAYER_PROFILE_TYPE = "profileType"; + /** + * Player max health identifier. + */ + public static final String PLAYER_MAX_HEALTH = "mhp"; + /** + * Player health identifier. + */ + public static final String PLAYER_HEALTH = "hp"; + /** + * Player experience identifier. + */ + public static final String PLAYER_EXPERIENCE = "xp"; + /** + * Player total experience identifier. + */ + public static final String PLAYER_TOTAL_EXPERIENCE = "txp"; + /** + * Player experience level identifier. + */ + public static final String PLAYER_LEVEL = "el"; + /** + * Player food level identifier. + */ + public static final String PLAYER_FOOD_LEVEL = "fl"; + /** + * Player exhaustion identifier. + */ + public static final String PLAYER_EXHAUSTION = "ex"; + /** + * Player saturation identifier. + */ + public static final String PLAYER_SATURATION = "sa"; + /** + * Player fall distance identifier. + */ + public static final String PLAYER_FALL_DISTANCE = "fd"; + /** + * Player fire ticks identifier. + */ + public static final String PLAYER_FIRE_TICKS = "ft"; + /** + * Player remaining air identifier. + */ + public static final String PLAYER_REMAINING_AIR = "ra"; + /** + * Player max air identifier. + */ + public static final String PLAYER_MAX_AIR = "ma"; + /** + * Location x identifier. + */ + public static final String LOCATION_X = "x"; + /** + * Location y identifier. + */ + public static final String LOCATION_Y = "y"; + /** + * Location z identifier. + */ + public static final String LOCATION_Z = "z"; + /** + * Location world identifier. + */ + public static final String LOCATION_WORLD = "wo"; + /** + * Location pitch identifier. + */ + public static final String LOCATION_PITCH = "pi"; + /** + * Location yaw identifier. + */ + public static final String LOCATION_YAW = "ya"; + /** + * Potion type identifier. + */ + public static final String POTION_TYPE = "pt"; + /** + * Potion duration identifier. + */ + public static final String POTION_DURATION = "pd"; + /** + * Potion amplifier identifier. + */ + public static final String POTION_AMPLIFIER = "pa"; + + private DataStrings() { + throw new IllegalStateException(); + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/util/DeserializationException.java b/src/main/java/org/mvplugins/multiverse/inventories/util/DeserializationException.java similarity index 82% rename from src/main/java/com/onarandombox/multiverseinventories/util/DeserializationException.java rename to src/main/java/org/mvplugins/multiverse/inventories/util/DeserializationException.java index 98a978e6..64c6f695 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/util/DeserializationException.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/DeserializationException.java @@ -1,4 +1,4 @@ -package com.onarandombox.multiverseinventories.util; +package org.mvplugins.multiverse.inventories.util; /** * Exception thrown when something goes wrong while deserializing this plugin's objects. diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/FutureNow.java b/src/main/java/org/mvplugins/multiverse/inventories/util/FutureNow.java new file mode 100644 index 00000000..1ba2fe88 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/FutureNow.java @@ -0,0 +1,22 @@ +package org.mvplugins.multiverse.inventories.util; + +import com.dumptruckman.minecraft.util.Logging; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public final class FutureNow { + + private static final long TIMEOUT = TimeUnit.SECONDS.toNanos(10); + + public static T get(CompletableFuture future) { + try { + return future.get(TIMEOUT, TimeUnit.NANOSECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + Logging.severe("Could not get future as it timed out: " + future); + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/ItemStackConverter.java b/src/main/java/org/mvplugins/multiverse/inventories/util/ItemStackConverter.java new file mode 100644 index 00000000..afc4c4d7 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/ItemStackConverter.java @@ -0,0 +1,79 @@ +package org.mvplugins.multiverse.inventories.util; + +import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; +import org.mvplugins.multiverse.external.vavr.control.Try; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; + +import java.util.Base64; + +public final class ItemStackConverter { + + private final static boolean hasByteSerializeSupport; + + static { + hasByteSerializeSupport = Try.run(() -> ItemStack.class.getMethod("deserializeBytes", byte[].class)) + .map(ignore -> true) + .recover(ignore -> false) + .getOrElse(false); + } + + private static InventoriesConfig config = null; + + public static void init(MultiverseInventories plugin) { + config = plugin.getServiceLocator().getService(InventoriesConfig.class); + } + + public static boolean isEmptyItemStack(@Nullable ItemStack itemStack) { + return itemStack == null || itemStack.getType() == Material.AIR || itemStack.getAmount() == 0; + } + +// /** +// * Format: `material[custom properties] amount` +// * Example: iron_sword[attribute_modifiers=[{type:"generic.attack_damage",id:"op_damage",amount:10000,operation:"add_value",slot:"mainhand"}]] 1 +// * +// * @return +// */ +// @Nullable +// public static ItemStack fromString(String item) { +// +// } + + @Nullable + public static ItemStack deserialize(Object obj) { + if (obj instanceof ItemStack itemStack) { + // Already handled by ConfigurationSerialization + return itemStack; + } + if (hasByteSerializeSupport && obj instanceof String string) { + byte[] bytes = Base64.getDecoder().decode(string); + return ItemStack.deserializeBytes(bytes); + } + return null; + } + + @Nullable + public static Object serialize(ItemStack itemStack) { + if (isEmptyItemStack(itemStack)) { + return null; + } + if (config == null || !config.getUseByteSerializationForInventoryData() || !hasByteSerializeSupport) { + // let ConfigurationSerialization handle it + return itemStack; + } + return Try.of(() -> Base64.getEncoder().encodeToString(itemStack.serializeAsBytes())) + .onFailure(e -> Logging.severe("Could not byte serialize item stack: %s", e.getMessage())) + .getOrNull(); + } + + public static boolean hasByteSerializeSupport() { + return hasByteSerializeSupport; + } + + private ItemStackConverter() { + throw new IllegalStateException(); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/LegacyParsers.java b/src/main/java/org/mvplugins/multiverse/inventories/util/LegacyParsers.java new file mode 100644 index 00000000..466b1aa1 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/LegacyParsers.java @@ -0,0 +1,171 @@ +package org.mvplugins.multiverse.inventories.util; + +import com.dumptruckman.minecraft.util.Logging; +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; +import net.minidev.json.parser.JSONParser; +import net.minidev.json.parser.ParseException; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * Exists for backward compatibility with older versions of Multiverse-Inventories. + */ +@Deprecated +public final class LegacyParsers { + + private static final JSONParser JSON_PARSER = new JSONParser( + JSONParser.USE_INTEGER_STORAGE | JSONParser.ACCEPT_TAILLING_SPACE); + + /** + * @param locString Parses this string and creates Location. + * @return New location object or null if no location could be created. + * @deprecated Locations do not use special handling because they are + * {@link org.bukkit.configuration.serialization.ConfigurationSerializable}. + */ + @Deprecated + public static Location parseLocation(String locString) { + if (locString.isEmpty()) { + return null; + } + JSONObject jsonLoc; + try { + jsonLoc = (JSONObject) JSON_PARSER.parse(locString); + } catch (ParseException | ClassCastException e) { + Logging.warning("Could not parse location! " + e.getMessage()); + return null; + } + return parseLocMap(jsonLoc); + } + + /** + * @deprecated Locations do not use special handling because they are + * {@link org.bukkit.configuration.serialization.ConfigurationSerializable}. + */ + @Deprecated + public static Location parseLocation(Map locMap) { + return parseLocMap(locMap); + } + + @Deprecated + private static Location parseLocMap(Map locMap) { + World world = null; + double x = 0; + double y = 0; + double z = 0; + float pitch = 0; + float yaw = 0; + if (locMap.containsKey(DataStrings.LOCATION_WORLD)) { + world = Bukkit.getWorld(locMap.get(DataStrings.LOCATION_WORLD).toString()); + } + if (locMap.containsKey(DataStrings.LOCATION_X)) { + Object value = locMap.get(DataStrings.LOCATION_X); + if (value instanceof Number) { + x = ((Number) value).doubleValue(); + } + } + if (locMap.containsKey(DataStrings.LOCATION_Y)) { + Object value = locMap.get(DataStrings.LOCATION_Y); + if (value instanceof Number) { + y = ((Number) value).doubleValue(); + } + } + if (locMap.containsKey(DataStrings.LOCATION_Z)) { + Object value = locMap.get(DataStrings.LOCATION_Z); + if (value instanceof Number) { + z = ((Number) value).doubleValue(); + } + } + if (locMap.containsKey(DataStrings.LOCATION_PITCH)) { + Object value = locMap.get(DataStrings.LOCATION_PITCH); + if (value instanceof Number) { + pitch = ((Number) value).floatValue(); + } + } + if (locMap.containsKey(DataStrings.LOCATION_YAW)) { + Object value = locMap.get(DataStrings.LOCATION_YAW); + if (value instanceof Number) { + yaw = ((Number) value).floatValue(); + } + } + if (world == null) { + return null; + } + return new Location(world, x, y, z, yaw, pitch); + } + + /** + * @param potionsString A player's potion effects in string form to be parsed into + * {@link java.util.Collection}<{@link org.bukkit.potion.PotionEffect}>. + * @return a collection of potion effects parsed from potionsString. + * @deprecated PotionEffect do not use special handling because they are + * {@link org.bukkit.configuration.serialization.ConfigurationSerializable}. + */ + @Deprecated + public static PotionEffect[] parsePotionEffects(String potionsString) { + List potionEffectList = new LinkedList(); + if (potionsString.isEmpty()) { + return potionEffectList.toArray(new PotionEffect[potionEffectList.size()]); + } + JSONArray jsonPotions; + try { + jsonPotions = (JSONArray) JSON_PARSER.parse(potionsString); + } catch (ParseException e) { + Logging.warning("Could not parse potions! " + e.getMessage()); + return potionEffectList.toArray(new PotionEffect[potionEffectList.size()]); + } catch (ClassCastException e) { + Logging.warning("Could not parse potions! " + e.getMessage()); + return potionEffectList.toArray(new PotionEffect[potionEffectList.size()]); + } + for (Object obj : jsonPotions) { + if (obj instanceof JSONObject) { + JSONObject jsonPotion = (JSONObject) obj; + int type = -1; + int duration = -1; + int amplifier = -1; + if (jsonPotion.containsKey(DataStrings.POTION_TYPE)) { + Object value = jsonPotion.get(DataStrings.POTION_TYPE); + if (value instanceof Number) { + type = ((Number) value).intValue(); + } + } + if (jsonPotion.containsKey(DataStrings.POTION_AMPLIFIER)) { + Object value = jsonPotion.get(DataStrings.POTION_AMPLIFIER); + if (value instanceof Number) { + amplifier = ((Number) value).intValue(); + } + } + if (jsonPotion.containsKey(DataStrings.POTION_DURATION)) { + Object value = jsonPotion.get(DataStrings.POTION_DURATION); + if (value instanceof Number) { + duration = ((Number) value).intValue(); + } + } + if (type == -1 || duration == -1 || amplifier == -1) { + Logging.fine("Could not parse potion effect string: " + obj); + } else { + PotionEffectType pType = PotionEffectType.getById(type); + if (pType == null) { + Logging.warning("Could not parse potion effect type: " + type); + continue; + } + potionEffectList.add(new PotionEffect(pType, duration, amplifier)); + } + } else { + Logging.warning("Could not parse potion effect: " + obj); + } + } + return potionEffectList.toArray(new PotionEffect[0]); + } + + private LegacyParsers() { + throw new IllegalStateException(); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/MVInvi18n.java b/src/main/java/org/mvplugins/multiverse/inventories/util/MVInvi18n.java new file mode 100644 index 00000000..7e1dd403 --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/MVInvi18n.java @@ -0,0 +1,123 @@ +package org.mvplugins.multiverse.inventories.util; + +import org.jetbrains.annotations.NotNull; +import org.mvplugins.multiverse.core.locale.message.Message; +import org.mvplugins.multiverse.core.locale.message.MessageReplacement; +import org.mvplugins.multiverse.external.acf.locales.MessageKey; +import org.mvplugins.multiverse.external.acf.locales.MessageKeyProvider; + +/** + * Locales keys for Multiverse-Inventories + */ +public enum MVInvi18n implements MessageKeyProvider { + TEST_STRING, + + GENERIC_SORRY, + GENERIC_PAGE, + GENERIC_OF, + GENERIC_UNLOADED, + GENERIC_PLUGINDISABLED, + GENERIC_ERROR, + GENERIC_SUCCESS, + GENERIC_INFO, + GENERIC_HELP, + GENERIC_COMMANDNOPERMISION, + GENERIC_THECONSOLE, + GENERIC_NOTLOGGEDIN, + GENERIC_OFF, + + ERROR_CONFIGLOAD, + ERROR_DATALOAD, + ERROR_NOGROUP, + ERROR_NOWORLD, + ERROR_NOWORLDPROFILE, + ERROR_NOSHARESSPECIFIED, + + CONFLICT_RESULTS, + CONFLICT_CHECKING, + CONFLICT_FOUND, + CONFLICT_NOTFOUND, + + INFO_WORLD, + INFO_WORLD_INFO, + INFO_GROUP, + INFO_GROUP_INFO, + INFO_GROUP_INFOSHARES, + INFO_GROUP_INFONEGATIVESHARES, + INFO_ZEROARG, + + LIST_GROUPS, + LIST_GROUPS_INFO, + + RELOAD_COMPLETE, + + ADDWORLD_WORLDADDED, + ADDWORLD_WORLDALREADYEXISTS, + + REMOVEWORLD_WORLDREMOVED, + REMOVEWORLD_WORLDNOTINGROUP, + + SHARES_NOWSHARING, + + DISABLEDSHARES_NOWSHARING, + + SPAWN_TELEPORTING, + SPAWN_TELEPORTEDBY, + SPAWN_TELEPORTCONSOLEERROR, + + DEBUG_INVALIDDEBUG, + DEBUG_SET, + + TOGGLE_NOWUSINGOPTIONAL, + TOGGLE_NOWNOTUSINGOPTIONAL, + TOGGLE_NOOPTIONALSHARES, + + GROUP_COMMANDPROMPT, + GROUP_CREATEPROMPT, + GROUP_EDITPROMPT, + GROUP_DELETEPROMPT, + GROUP_MODIFYPROMPT, + GROUP_WORLDSPROMPT, + GROUP_SHARESPROMPT, + GROUP_INVALIDNAME, + GROUP_EXISTS, + GROUP_REMOVED, + GROUP_WORLDSEMPTY, + GROUP_CREATIONCOMPLETE, + GROUP_UPDATED, + GROUP_NONCONVERSABLE, + GROUP_INVALIDOPTION, + + MIGRATE_PLUGINNOTENABLED, + MIGRATE_UNSUPPORTEDPLUGIN, + MIGRATE_CONFIRMPROMPT, + MIGRATE_SUCCESS, + MIGRATE_FAILED, + + DELETEGROUP_CONFIRMPROMPT, + DELETEGROUP_SUCCESS, + ; + + private final MessageKey key = MessageKey.of("mv-inventories." + this.name().replace('_', '.') + .toLowerCase()); + + /** + * {@inheritDoc} + */ + @Override + public MessageKey getMessageKey() { + return this.key; + } + + /** + * Creates a message with non-localized message fallback and replacements + * @param nonLocalizedMessage The non-localized message + * @param replacements The replacements + * + * @return A new localizable Message + */ + @NotNull + public Message bundle(@NotNull String nonLocalizedMessage, @NotNull MessageReplacement... replacements) { + return Message.of(this, nonLocalizedMessage, replacements); + } +} diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/MinecraftTools.java b/src/main/java/org/mvplugins/multiverse/inventories/util/MinecraftTools.java new file mode 100644 index 00000000..0fa4771d --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/MinecraftTools.java @@ -0,0 +1,87 @@ +package org.mvplugins.multiverse.inventories.util; + +import com.dumptruckman.minecraft.util.Logging; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.data.type.Bed; +import org.bukkit.block.data.type.RespawnAnchor; +import org.bukkit.inventory.ItemStack; + +import javax.annotation.Nullable; + +/** + * General tools to help with minecraftian things. + */ +public final class MinecraftTools { + + private static final int TICKS_PER_SECOND = 20; + + /** + * Converts an amount of seconds to the appropriate amount of ticks. + * + * @param seconds Amount of seconds to convert + * @return Ticks converted from seconds. + */ + public static long convertSecondsToTicks(long seconds) { + return seconds * TICKS_PER_SECOND; + } + + /** + * Fills an ItemStack array with air. + * + * @param items The ItemStack array to fill. + * @return The air filled ItemStack array. + */ + public static ItemStack[] fillWithAir(ItemStack[] items) { + for (int i = 0; i < items.length; i++) { + items[i] = new ItemStack(Material.AIR); + } + return items; + } + + public static @Nullable Location findBedFromRespawnLocation(@Nullable Location respawnLocation) { + if (respawnLocation == null) { + return null; + } + var bedSpawnBlock = respawnLocation.getBlock(); + for(int x = -2; x <= 2; x++) { + for (int y = -1; y <= 1; y++) { + for (int z = -2; z <= 2; z++) { + var newBedBlock = bedSpawnBlock.getRelative(x, y, z); + Logging.finest("Finding bed at: " + newBedBlock); + if (newBedBlock.getBlockData() instanceof Bed) { + Logging.finer("Found bed!"); + return newBedBlock.getLocation(); + } + } + } + } + Logging.warning("Unable to anchor, respawn may not work as expected!"); + return respawnLocation; + } + + public static @Nullable Location findAnchorFromRespawnLocation(@Nullable Location respawnLocation) { + if (respawnLocation == null) { + return null; + } + var bedSpawnBlock = respawnLocation.getBlock(); + for(int x = -2; x <= 2; x++) { + for (int y = -2; y <= 2; y++) { + for (int z = -2; z <= 2; z++) { + var newBedBlock = bedSpawnBlock.getRelative(x, y, z); + Logging.finest("Finding anchor at: " + newBedBlock); + if (newBedBlock.getBlockData() instanceof RespawnAnchor) { + Logging.finer("Found anchor!"); + return newBedBlock.getLocation(); + } + } + } + } + Logging.warning("Unable to anchor, respawn may not work as expected!"); + return respawnLocation; + } + + private MinecraftTools() { + throw new IllegalStateException(); + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/util/Perm.java b/src/main/java/org/mvplugins/multiverse/inventories/util/Perm.java similarity index 94% rename from src/main/java/com/onarandombox/multiverseinventories/util/Perm.java rename to src/main/java/org/mvplugins/multiverse/inventories/util/Perm.java index 42c1be28..244f500c 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/util/Perm.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/Perm.java @@ -1,226 +1,228 @@ -package com.onarandombox.multiverseinventories.util; - -import com.dumptruckman.minecraft.util.Logging; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.permissions.Permission; -import org.bukkit.permissions.PermissionDefault; -import org.bukkit.plugin.PluginManager; - -/** - * @author dumptruckman - */ -public enum Perm { - /** - * Permission for /mvinv info. - */ - COMMAND_INFO(new Permission("multiverse.inventories.info", "Displays information about a world or group.", PermissionDefault.OP)), - /** - * Permission for /mvinv list. - */ - COMMAND_LIST(new Permission("multiverse.inventories.list", "Displays a list of groups.", PermissionDefault.OP)), - /** - * Permission for /mvinv reload. - */ - COMMAND_RELOAD(new Permission("multiverse.inventories.reload", "Reloads config file.", PermissionDefault.OP)), - /** - * Permission for /mvinv import. - */ - COMMAND_IMPORT(new Permission("multiverse.inventories.import", "Imports data from MultiInv/WorldInventories", PermissionDefault.OP)), - /** - * Permission for /mvinv group. - */ - COMMAND_GROUP(new Permission("multiverse.inventories.group", "Begins a conversation about groups.", - PermissionDefault.OP)), - /** - * Permission for /mvinv addworld. - */ - COMMAND_ADDWORLD(new Permission("multiverse.inventories.addworld", "Adds a world to a world group", - PermissionDefault.OP)), - /** - * Permission for /mvinv remvoveworld. - */ - COMMAND_RMWORLD(new Permission("multiverse.inventories.removeworld", "Removes a world from a world group", - PermissionDefault.OP)), - /** - * Permission for /mvinv addshare. - */ - COMMAND_ADDSHARES(new Permission("multiverse.inventories.addshares", "Adds share(s) to a world group", - PermissionDefault.OP)), - /** - * Permission for /mvinv rmshare. - */ - COMMAND_RMSHARES(new Permission("multiverse.inventories.removeshares", "Removes share(s) from a world group", - PermissionDefault.OP)), - /** - * Permission for /mvinv creategroup. - */ - COMMAND_CREATEGROUP(new Permission("multiverse.inventories.creategroup", "Creates a world group", - PermissionDefault.OP)), - /** - * Permission for /mvinv deletegroup. - */ - COMMAND_DELETEGROUP(new Permission("multiverse.inventories.deletegroup", "Deletes a world group", - PermissionDefault.OP)), - /** - * Permissions for /mvinv spawn. - */ - COMMAND_SPAWN(new Permission("multiverse.inventories.spawn.self", "teleport yourself to group spawn", - PermissionDefault.OP)), - /** - * Permissions for /mvinv spawn. - */ - COMMAND_SPAWN_OTHER(new Permission("multiverse.inventories.spawn.other", "teleport other to group spawn", - PermissionDefault.OP)), - /** - * Permission for debug command. - */ - COMMAND_DEBUG(new Permission("multiverse.inventories.debug", "Spams the console a bunch.", PermissionDefault.OP)), - /** - * Permission for bypassing all groups. - */ - BYPASS_GROUP_ALL(new Permission("mvinv.bypass.group.*", "", PermissionDefault.FALSE)), - /** - * Permission prefix for bypassing groups. - */ - BYPASS_GROUP("mvinv.bypass.group.") { - private String getBypassMessage(Player player, String name) { - return "Player: " + player.getName() + " has bypass perms for group: " + name; - } - }, - /** - * Permission for bypassing all worlds. - */ - BYPASS_WORLD_ALL(new Permission("mvinv.bypass.world.*", "", PermissionDefault.FALSE)), - /** - * Permission prefix for bypassing worlds. - */ - BYPASS_WORLD("mvinv.bypass.world.") { - private String getBypassMessage(Player player, String name) { - return "Player: " + player.getName() + " has bypass perms for world: " + name; - } - }, - /** - * Permission for bypassing all worlds. - */ - BYPASS_GAME_MODE_ALL(new Permission("mvinv.bypass.gamemode.*", "", PermissionDefault.FALSE)), - /** - * Permission prefix for bypassing worlds. - */ - BYPASS_GAME_MODE("mvinv.bypass.gamemode.") { - private String getBypassMessage(Player player, String name) { - return "Player: " + player.getName() + " has bypass perms for game mode: " + name; - } - }, - /** - * Permissions for bypassing all world/groups inventory handling. - */ - BYPASS_ALL(new Permission("mvinv.bypass.*", "Allows bypassing all of your groups/worlds and constantly use " - + "the same inventory", PermissionDefault.FALSE)); - - private Permission perm = null; - private String permNode = ""; - - Perm(Permission perm) { - this.perm = perm; - } - - Perm(String permNode) { - this.permNode = permNode; - } - - /** - * @return the Permission. - */ - public Permission getPermission() { - return this.perm; - } - - /** - * @return the Permission node string. - */ - public String getNode() { - return this.permNode; - } - - /** - * @param finalNode String to add to the bypass prefix. - * @return The full permission node for bypass. - */ - public Permission getBypassPermission(String finalNode) { - String bypassNode = this.getNode() + finalNode; - Logging.finer("Checking node " + bypassNode + "..."); - - Permission permission = Bukkit.getPluginManager().getPermission(bypassNode); - if (permission == null) { - permission = new Permission(bypassNode, PermissionDefault.FALSE); - switch (this) { - case BYPASS_GROUP: - permission.addParent(BYPASS_GROUP_ALL.getPermission(), true); - break; - case BYPASS_WORLD: - permission.addParent(BYPASS_WORLD_ALL.getPermission(), true); - break; - case BYPASS_GAME_MODE: - permission.addParent(BYPASS_GAME_MODE_ALL.getPermission(), true); - break; - default: - } - Bukkit.getPluginManager().addPermission(permission); - } - return permission; - } - - /** - * Checks if a player has permission to bypass something which requires a name of an object to be bypassed. - * A World name for example. - * - * @param player Player to check permission for. - * @param name Name of object to bypass. - * @return True if player is allowed to bypass. - */ - public boolean hasBypass(Player player, String name) { - if (inventories != null && !inventories.getMVIConfig().isUsingBypass()) { - return false; - } - Permission bypassPerm = this.getBypassPermission(name); - boolean hasBypass = player.hasPermission(bypassPerm); - if (hasBypass) { - Logging.fine("Player: " + player.getName() + " in World: " + player.getWorld().getName() - + " has permission: " + bypassPerm.getName() + "(Default: " - + bypassPerm.getDefault().toString() + ")!"); - } - return hasBypass; - } - - /** - * Checks if the sender has the node in question. - * - * @param sender CommandSender to check permission for. - * @return True if sender has the permission. - */ - public boolean has(CommandSender sender) { - return sender.hasPermission(perm); - } - - private static MultiverseInventories inventories = null; - - /** - * Registers all Permission to the plugin. - * - * @param plugin Plugin to register permissions to. - */ - public static void register(MultiverseInventories plugin) { - inventories = plugin; - BYPASS_WORLD_ALL.getPermission().addParent(BYPASS_ALL.getPermission(), true); - BYPASS_GROUP_ALL.getPermission().addParent(BYPASS_ALL.getPermission(), true); - PluginManager pm = plugin.getServer().getPluginManager(); - for (Perm perm : Perm.values()) { - if (perm.getPermission() != null) { - pm.addPermission(perm.getPermission()); - } - } - } -} +package org.mvplugins.multiverse.inventories.util; + +import com.dumptruckman.minecraft.util.Logging; +import org.mvplugins.multiverse.inventories.MultiverseInventories; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; +import org.bukkit.plugin.PluginManager; +import org.mvplugins.multiverse.inventories.config.InventoriesConfig; + +/** + * @author dumptruckman + */ +public enum Perm { + /** + * Permission for /mvinv info. + */ + COMMAND_INFO(new Permission("multiverse.inventories.info", "Displays information about a world or group.", PermissionDefault.OP)), + /** + * Permission for /mvinv list. + */ + COMMAND_LIST(new Permission("multiverse.inventories.list", "Displays a list of groups.", PermissionDefault.OP)), + /** + * Permission for /mvinv reload. + */ + COMMAND_RELOAD(new Permission("multiverse.inventories.reload", "Reloads config file.", PermissionDefault.OP)), + /** + * Permission for /mvinv import. + */ + COMMAND_IMPORT(new Permission("multiverse.inventories.import", "Imports data from MultiInv/WorldInventories", PermissionDefault.OP)), + /** + * Permission for /mvinv group. + */ + COMMAND_GROUP(new Permission("multiverse.inventories.group", "Begins a conversation about groups.", + PermissionDefault.OP)), + /** + * Permission for /mvinv addworld. + */ + COMMAND_ADDWORLD(new Permission("multiverse.inventories.addworld", "Adds a world to a world group", + PermissionDefault.OP)), + /** + * Permission for /mvinv remvoveworld. + */ + COMMAND_RMWORLD(new Permission("multiverse.inventories.removeworld", "Removes a world from a world group", + PermissionDefault.OP)), + /** + * Permission for /mvinv addshare. + */ + COMMAND_ADDSHARES(new Permission("multiverse.inventories.addshares", "Adds share(s) to a world group", + PermissionDefault.OP)), + /** + * Permission for /mvinv rmshare. + */ + COMMAND_RMSHARES(new Permission("multiverse.inventories.removeshares", "Removes share(s) from a world group", + PermissionDefault.OP)), + /** + * Permission for /mvinv creategroup. + */ + COMMAND_CREATEGROUP(new Permission("multiverse.inventories.creategroup", "Creates a world group", + PermissionDefault.OP)), + /** + * Permission for /mvinv deletegroup. + */ + COMMAND_DELETEGROUP(new Permission("multiverse.inventories.deletegroup", "Deletes a world group", + PermissionDefault.OP)), + /** + * Permissions for /mvinv spawn. + */ + COMMAND_SPAWN(new Permission("multiverse.inventories.spawn.self", "teleport yourself to group spawn", + PermissionDefault.OP)), + /** + * Permissions for /mvinv spawn. + */ + COMMAND_SPAWN_OTHER(new Permission("multiverse.inventories.spawn.other", "teleport other to group spawn", + PermissionDefault.OP)), + /** + * Permission for debug command. + */ + COMMAND_DEBUG(new Permission("multiverse.inventories.debug", "Spams the console a bunch.", PermissionDefault.OP)), + /** + * Permission for bypassing all groups. + */ + BYPASS_GROUP_ALL(new Permission("mvinv.bypass.group.*", "", PermissionDefault.FALSE)), + /** + * Permission prefix for bypassing groups. + */ + BYPASS_GROUP("mvinv.bypass.group.") { + private String getBypassMessage(Player player, String name) { + return "Player: " + player.getName() + " has bypass perms for group: " + name; + } + }, + /** + * Permission for bypassing all worlds. + */ + BYPASS_WORLD_ALL(new Permission("mvinv.bypass.world.*", "", PermissionDefault.FALSE)), + /** + * Permission prefix for bypassing worlds. + */ + BYPASS_WORLD("mvinv.bypass.world.") { + private String getBypassMessage(Player player, String name) { + return "Player: " + player.getName() + " has bypass perms for world: " + name; + } + }, + /** + * Permission for bypassing all worlds. + */ + BYPASS_GAME_MODE_ALL(new Permission("mvinv.bypass.gamemode.*", "", PermissionDefault.FALSE)), + /** + * Permission prefix for bypassing worlds. + */ + BYPASS_GAME_MODE("mvinv.bypass.gamemode.") { + private String getBypassMessage(Player player, String name) { + return "Player: " + player.getName() + " has bypass perms for game mode: " + name; + } + }, + /** + * Permissions for bypassing all world/groups inventory handling. + */ + BYPASS_ALL(new Permission("mvinv.bypass.*", "Allows bypassing all of your groups/worlds and constantly use " + + "the same inventory", PermissionDefault.FALSE)); + + private Permission perm = null; + private String permNode = ""; + + Perm(Permission perm) { + this.perm = perm; + } + + Perm(String permNode) { + this.permNode = permNode; + } + + /** + * @return the Permission. + */ + public Permission getPermission() { + return this.perm; + } + + /** + * @return the Permission node string. + */ + public String getNode() { + return this.permNode; + } + + /** + * @param finalNode String to add to the bypass prefix. + * @return The full permission node for bypass. + */ + public Permission getBypassPermission(String finalNode) { + String bypassNode = this.getNode() + finalNode; + Logging.finer("Checking node " + bypassNode + "..."); + + Permission permission = Bukkit.getPluginManager().getPermission(bypassNode); + if (permission == null) { + permission = new Permission(bypassNode, PermissionDefault.FALSE); + switch (this) { + case BYPASS_GROUP: + permission.addParent(BYPASS_GROUP_ALL.getPermission(), true); + break; + case BYPASS_WORLD: + permission.addParent(BYPASS_WORLD_ALL.getPermission(), true); + break; + case BYPASS_GAME_MODE: + permission.addParent(BYPASS_GAME_MODE_ALL.getPermission(), true); + break; + default: + } + Bukkit.getPluginManager().addPermission(permission); + } + return permission; + } + + /** + * Checks if a player has permission to bypass something which requires a name of an object to be bypassed. + * A World name for example. + * + * @param player Player to check permission for. + * @param name Name of object to bypass. + * @return True if player is allowed to bypass. + */ + public boolean hasBypass(Player player, String name) { + if (inventories != null && + !inventories.getServiceLocator().getService(InventoriesConfig.class).getEnableBypassPermissions()) { + return false; + } + Permission bypassPerm = this.getBypassPermission(name); + boolean hasBypass = player.hasPermission(bypassPerm); + if (hasBypass) { + Logging.fine("Player: " + player.getName() + " in World: " + player.getWorld().getName() + + " has permission: " + bypassPerm.getName() + "(Default: " + + bypassPerm.getDefault() + ")!"); + } + return hasBypass; + } + + /** + * Checks if the sender has the node in question. + * + * @param sender CommandSender to check permission for. + * @return True if sender has the permission. + */ + public boolean has(CommandSender sender) { + return sender.hasPermission(perm); + } + + private static MultiverseInventories inventories = null; + + /** + * Registers all Permission to the plugin. + * + * @param plugin Plugin to register permissions to. + */ + public static void register(MultiverseInventories plugin) { + inventories = plugin; + BYPASS_WORLD_ALL.getPermission().addParent(BYPASS_ALL.getPermission(), true); + BYPASS_GROUP_ALL.getPermission().addParent(BYPASS_ALL.getPermission(), true); + PluginManager pm = plugin.getServer().getPluginManager(); + for (Perm perm : Perm.values()) { + if (perm.getPermission() != null) { + pm.addPermission(perm.getPermission()); + } + } + } +} diff --git a/src/main/java/com/onarandombox/multiverseinventories/PlayerStats.java b/src/main/java/org/mvplugins/multiverse/inventories/util/PlayerStats.java similarity index 84% rename from src/main/java/com/onarandombox/multiverseinventories/PlayerStats.java rename to src/main/java/org/mvplugins/multiverse/inventories/util/PlayerStats.java index 8c546a53..1bafb4b2 100644 --- a/src/main/java/com/onarandombox/multiverseinventories/PlayerStats.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/PlayerStats.java @@ -1,69 +1,73 @@ -package com.onarandombox.multiverseinventories; - -/** - * A collection of values relating to a Minecraft player. - */ -public class PlayerStats { - - /** - * Number of slots in Minecraft player inventory. - */ - public static final int INVENTORY_SIZE = 36; - /** - * Number of slots for armor for player. - */ - public static final int ARMOR_SIZE = 4; - /** - * Number of slots in an ender chest. - */ - public static final int ENDER_CHEST_SIZE = 27; - /** - * Default health value. - */ - public static final int HEALTH = 20; - /** - * Default experience value. - */ - public static final float EXPERIENCE = 0F; - /** - * Default total experience value. - */ - public static final int TOTAL_EXPERIENCE = 0; - /** - * Default level value. - */ - public static final int LEVEL = 0; - /** - * Default food level value. - */ - public static final int FOOD_LEVEL = 20; - /** - * Default exhaustion value. - */ - public static final float EXHAUSTION = 0F; - /** - * Default saturation value. - */ - public static final float SATURATION = 5F; - /** - * Default fall distance value. - */ - public static final float FALL_DISTANCE = 0F; - /** - * Default fire ticks value. - */ - public static final int FIRE_TICKS = 0; - /** - * Default remaining air value. - */ - public static final int REMAINING_AIR = 300; - /** - * Default maximum air value. - */ - public static final int MAXIMUM_AIR = 300; - - private PlayerStats() { - throw new AssertionError(); - } -} - +package org.mvplugins.multiverse.inventories.util; + +/** + * A collection of values relating to a Minecraft player. + */ +public final class PlayerStats { + + /** + * Number of slots in Minecraft player inventory. + */ + public static final int INVENTORY_SIZE = 36; + /** + * Number of slots for armor for player. + */ + public static final int ARMOR_SIZE = 4; + /** + * Number of slots in an ender chest. + */ + public static final int ENDER_CHEST_SIZE = 27; + /** + * Default max health value. + */ + public static final double MAX_HEALTH = 20; + /** + * Default health value. + */ + public static final double HEALTH = 20; + /** + * Default experience value. + */ + public static final float EXPERIENCE = 0F; + /** + * Default total experience value. + */ + public static final int TOTAL_EXPERIENCE = 0; + /** + * Default level value. + */ + public static final int LEVEL = 0; + /** + * Default food level value. + */ + public static final int FOOD_LEVEL = 20; + /** + * Default exhaustion value. + */ + public static final float EXHAUSTION = 0F; + /** + * Default saturation value. + */ + public static final float SATURATION = 5F; + /** + * Default fall distance value. + */ + public static final float FALL_DISTANCE = 0F; + /** + * Default fire ticks value. + */ + public static final int FIRE_TICKS = 0; + /** + * Default remaining air value. + */ + public static final int REMAINING_AIR = 300; + /** + * Default maximum air value. + */ + public static final int MAXIMUM_AIR = 300; + + private PlayerStats() { + throw new IllegalStateException(); + } +} + diff --git a/src/main/java/org/mvplugins/multiverse/inventories/util/package-info.java b/src/main/java/org/mvplugins/multiverse/inventories/util/package-info.java new file mode 100644 index 00000000..ec546d9e --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/package-info.java @@ -0,0 +1,5 @@ +/** + * This package contains utility classes. + */ +package org.mvplugins.multiverse.inventories.util; + diff --git a/src/main/resources/de.yml b/src/main/resources/de.yml deleted file mode 100644 index bb2343c0..00000000 --- a/src/main/resources/de.yml +++ /dev/null @@ -1,110 +0,0 @@ -TEST_STRING: 'ein Test String von der Ressource' - -# Generic Strings -GENERIC_SORRY: 'Tut uns leid...' -GENERIC_PAGE: 'Seite' -GENERIC_OF: 'von' -GENERIC_UNLOADED: 'NICHT GELADEN' -GENERIC_PLUGIN_DISABLED: 'Dieses Plugin ist deaktiviert!' -GENERIC_ERROR: '[Fehler]' -GENERIC_SUCCESS: '[Erfolg]' -GENERIC_INFO: '[Info]' -GENERIC_HELP: '[Hilfe]' -GENERIC_COMMAND_NO_PERMISSION: 'Du hast nicht die Berechtigung für %1. (%2)' -GENERIC_THE_CONSOLE: 'die Konsole' -GENERIC_NOT_LOGGED_IN: '%1 wird gerade nicht geloggt!' -GENERIC_OFF: 'AUS' - -# Errors -ERROR_CONFIG_LOAD: 'Es ist ein Fehler beim Laden der Konfigurations-Datei aufgetreten. Deaktivieren...' -ERROR_DATA_LOAD: 'Es ist ein Fehler beim Laden der Daten-Datei aufgetreten. Deaktivieren...' -ERROR_NO_GROUP: '&6Es gibt keine Gruppe mit dem Namen: &f%1' -ERROR_NO_WORLD: '&6Es gibt keine Welt mit dem Namen: &f%1' -ERROR_NO_WORLD_PROFILE: '&6Es gibt kein Welt-Profil für die Welt: &f%1' -ERROR_PLUGIN_NOT_ENABLED: '&f%1 &6ist nicht aktiviert, deswegen kannst du keine Daten davon importieren!' -ERROR_UNSUPPORTED_IMPORT: '&6Tut uns leid, ''&f%1&6'' kann nicht importiert werden.' -ERROR_NO_SHARES_SPECIFIED: '&cDu hast keine gültigen Teilungen angegeben!' - -CONFLICT_RESULTS: 'Konflikt bei den Gruppen gefunden: ''%1'' und ''%2'' da beide Teilen: ''%3'' für die Welt(en): ''%4''' -CONFLICT_CHECKING: 'Überprüfe die Konfigurationen für die Gruppen...' -CONFLICT_FOUND: 'Konflikt gefunden... Wenn er nicht gelöst wird, werden die Daten eventuell korrupt.' -CONFLICT_NOT_FOUND: 'Keine Konflikte bei den Gruppen gefunden!' - -NON_CONVERSABLE: 'Du bist nicht berechtigt auf Konversationen zu zugreifen (remote Konsole?)' -INVALID_PROMPT_OPTION: '&cDas ist keine gültige Option! Schreibe &f##&c um das Arbeiten an Gruppen abzubrechen.' - -## Commands -# Info Command -INFO_WORLD: -- '&b===[ Info für Welt: &6%1&b ]===' -INFO_WORLD_INFO: -- '&6Gruppe:&f %1' -INFO_GROUP: -- '&b===[ Info für Gruppe: &6%1&b ]===' -INFO_GROUPS_INFO: -- '&6Welten:&f %1' -- '&bTeilungen:&f %2' -- '&bTrennungen:&f %3' - -# Group Command -GROUP_COMMAND_PROMPT: '&6Was möchtest du tun? &fErstellen&6, &fBearbeiten &6oder &fLöschen&6. Schreibe &f##&6 zum Abbrechen.' -GROUP_CREATE_PROMPT: '&6Bitte gib der Gruppe einen Namen: ' -GROUP_EDIT_PROMPT: '&6Welche Gruppe möchtest du bearbeiten? %1' -GROUP_DELETE_PROMPT: '&6Welche Gruppe möchtest du löschen? %1' -GROUP_MODIFY_PROMPT: '&6Was möchtest du ändern für &e%1&6? &fWelten &6oder &fTeilungen&6. Schreibe &f##&6 zum Abbrechen.' -GROUP_WORLDS_PROMPT: '6Gib den Namen der Welt ein, um ihn zur Gruppe hinzuzufügen &f%1&6 oder schreibe &f@&6 zum Fortfahren. Um eine Welt zu entfernen, schreibe vor den Namen ein Minus-Symbol (ex: &f-worldname&6). Aktuelle Welten: %2' -GROUP_SHARES_PROMPT: '&6Schreibe &fall&6 oder eine spezielle Teilung, um sie der Gruppe hinzuzufügen &f%1&6 oder schreibe &f@&6 zum Fortfahren. Um Teilungen zu entfernen, schreibe vor den Namen ein Minus-Symbol (ex: &f-inventory&6). Aktuelle Teilungen: %2' -GROUP_INVALID_NAME: '&cDieser Name ist nicht gültig! Er darf nur Buchstaben, Nummern, und unterstriche enthalten.' -GROUP_EXISTS: '&cDiese Gruppe existiert bereits! (&f%1&c)' -GROUP_REMOVED: '&2Gelöschte Gruppe: &f%1' -GROUP_WORLDS_EMPTY: '&cDu kannst keine Gruppe ohne Welten erstellen, bitte füge welche hinzu oder schreibe &f##&c zum Abbrechen.' -GROUP_CREATION_COMPLETE: '&2Du hast eine neue Gruppe erstellt!' -GROUP_UPDATED: '&2Die Gruppe wurde geupdated!' - -# List Command -LIST_GROUPS: -- '&b===[ Gruppen Liste ]===' -- '&6Gruppe:&f %1' - -# Reload Command -RELOAD_COMPLETE: -- '&b===[ Neu laden fertig! ]===' - -# Addworld Command -WORLD_ADDED: -- '&6Welt:&f %1 &6wurde hinzugefügt zur Gruppe: &f%2' -WORLD_ALREADY_EXISTS: -- '&6Welt:&f %1 &6ist bereits Teil der Gruppe: &f%2' - -# Removeworld Command -WORLD_REMOVED: -- '&6Welt:&f %1 &6wurde entfernt aus der Gruppe: &f%2' -WORLD_NOT_IN_GROUP: -- '&6Welt:&f %1 &6ist kein Teil der Gruppe: &f%2' - -# Add/remove shares command -NOW_SHARING: -- '&6Gruppe: &f%1 &6teilt nun: &f%2 &6und teilt NICHT: &f%3' - -# Spawn command -TELEPORTING: -- 'Teleportiere zum Spawn dieser Welt-Gruppe...' -TELEPORTED_BY: -- 'Du wurdest teleportiert von: %1' -TELEPORT_CONSOLE_ERROR: -- 'Von der Konsole, MUSS ein Spieler angegeben werden!' - -# Debug command -INVALID_DEBUG: -- '&fUngültige Debug Einstellung. Bitte verwende eine Nummer von 0-3. &b(3 sind viele Nachrichten!)' -DEBUG_SET: -- 'Der Debug Mode ist %1' - -# Toggle command -NOW_USING_OPTIONAL: '&f%1 &6wird nun in Betracht gezogen, wenn Spieler die Welt wechseln.' -NOW_NOT_USING_OPTIONAL: '&f%1 &6wird nun nicht mehr in Betracht gezogen, wenn Spieler die Welt wechseln.' -NO_OPTIONAL_SHARES: '&f%1 &6ist keine optionale Teilung!' - -# Migrate Command -MIGRATE_FAILED: 'Fehler beim übertragen der Daten von %1 zu %2! Siehe die logs für Details.' -MIGRATE_SUCCESSFUL: 'Die Daten von %1 wurden zu %2 übertragen!' diff --git a/src/main/resources/en.yml b/src/main/resources/en.yml deleted file mode 100644 index a32fcf0d..00000000 --- a/src/main/resources/en.yml +++ /dev/null @@ -1,89 +0,0 @@ -TEST_STRING: 'a test-string from the resource' - -# Generic Strings -GENERIC_SORRY: 'Sorry...' -GENERIC_PAGE: 'Page' -GENERIC_OF: 'of' -GENERIC_UNLOADED: 'UNLOADED' -GENERIC_PLUGIN_DISABLED: 'This plugin is Disabled!' -GENERIC_ERROR: '[Error]' -GENERIC_SUCCESS: '[Success]' -GENERIC_INFO: '[Info]' -GENERIC_HELP: '[Help]' -GENERIC_COMMAND_NO_PERMISSION: 'You do not have permission to %1. (%2)' -GENERIC_THE_CONSOLE: 'the console' -GENERIC_NOT_LOGGED_IN: '%1 is not logged on right now!' -GENERIC_OFF: 'OFF' - -# Errors -ERROR_CONFIG_LOAD: 'Encountered an error while loading the configuration file. Disabling...' -ERROR_DATA_LOAD: 'Encountered an error while loading the data file. Disabling...' -ERROR_NO_GROUP: '&6There is no group with the name: &f%1' -ERROR_NO_WORLD: '&6There is no world with the name: &f%1' -ERROR_NO_WORLD_PROFILE: '&6There is no world profile for the world: &f%1' -ERROR_PLUGIN_NOT_ENABLED: '&f%1 &6is not enabled so you may not import data from it!' -ERROR_UNSUPPORTED_IMPORT: '&6Sorry, ''&f%1&6'' is not supported for importing.' -ERROR_NO_SHARES_SPECIFIED: '&cYou did not specify any valid shares!' - -CONFLICT_RESULTS: 'Conflict found for groups: ''%1'' and ''%2'' because they both share: ''%3'' for the world(s): ''%4''' -CONFLICT_CHECKING: 'Checking for conflicts in groups...' -CONFLICT_FOUND: 'Conflicts have been found... If these are not resolved, you may experience problems with your data.' -CONFLICT_NOT_FOUND: 'No group conflicts found!' - - -## Commands -# Info Command -INFO_WORLD: -- '&b===[ Info for world: &6%1&b ]===' -INFO_WORLD_INFO: -- '&6Groups:&f %1' -INFO_GROUP: -- '&b===[ Info for group: &6%1&b ]===' -INFO_GROUP_INFO: -- '&6Worlds:&f %1' -- '&bShares:&f %2' -- '&bNegative Shares:&f %3' - -# List Command -LIST_GROUPS: -- '&b===[ Group List ]===' -- '&6Groups:&f %1' - -# Reload Command -RELOAD_COMPLETE: -- '&b===[ Reload Complete! ]===' - -# Addworld Command -WORLD_ADDED: -- '&6World:&f %1 &6added to Group: &f%2' -WORLD_ALREADY_EXISTS: -- '&6World:&f %1 &6already part of Group: &f%2' - -# Removeworld Command -WORLD_REMOVED: -- '&6World:&f %1 &6removed from Group: &f%2' -WORLD_NOT_IN_GROUP: -- '&6World:&f %1 &6is not part of Group: &f%2' - -# Add/remove shares command -NOW_SHARING: -- '&6Group: &f%1 &6is now sharing: &f%2 &6and NOT sharing: &f%3' - -# Spawn command -TELEPORTING: -- 'Teleporting to this world group''s spawn...' -TELEPORTED_BY: -- 'You were teleported by: %1' -TELEPORT_CONSOLE_ERROR: -- 'From the console, you must provide a PLAYER' - -# Debug command -INVALID_DEBUG: -- '&fInvalid debug level. Please use number 0-3. &b(3 being many many messages!)' -DEBUG_SET: -- 'Debug mode is %1' - -# Toggle command -NOW_USING_OPTIONAL: '&f%1 &6will now be considered when player''s change world.' -NOW_NOT_USING_OPTIONAL: '&f%1 &6will no longer be considered when player''s change world.' -NO_OPTIONAL_SHARES: '&f%1 &6is not an optional share!' \ No newline at end of file diff --git a/src/main/resources/multiverse-inventories_en.properties b/src/main/resources/multiverse-inventories_en.properties new file mode 100644 index 00000000..a2f4553f --- /dev/null +++ b/src/main/resources/multiverse-inventories_en.properties @@ -0,0 +1,103 @@ +mv-inventories.test.string=a test-string from the resource + +# Generic Strings +mv-inventories.generic.sorry=Sorry... +mv-inventories.generic.page=Page +mv-inventories.generic.of=of +mv-inventories.generic.unloaded=UNLOADED +mv-inventories.generic.plugindisabled=This plugin is Disabled! +mv-inventories.generic.error=[Error] +mv-inventories.generic.success=[Success] +mv-inventories.generic.info=[Info] +mv-inventories.generic.help=[Help] +mv-inventories.generic.commandnopermission=You do not have permission to {command}. ({permission}) +mv-inventories.generic.theconsole=the console +mv-inventories.generic.notloggedin={player} is not logged on right now! +mv-inventories.generic.off=OFF + +# Errors +mv-inventories.error.configload=Encountered an error while loading the configuration file. Disabling... +mv-inventories.error.dataload=Encountered an error while loading the data file. Disabling... +mv-inventories.error.nogroup=&6There is no group with the name: &f{group} +mv-inventories.error.noworld=&6There is no world with the name: &f{world} +mv-inventories.error.noworldprofile=&6There is no world profile for the world: &f{world} +mv-inventories.error.nosharesspecified=&cYou did not specify any valid shares! + +# Conflicts +mv-inventories.conflict.results=Conflict found for groups: ''{group1}'' and ''{group2}'' because they both share: ''{shares}'' for the world(s): ''{worlds}'' +mv-inventories.conflict.checking=Checking for conflicts in groups... +mv-inventories.conflict.found=Conflicts have been found... If these are not resolved, you may experience problems with your data. +mv-inventories.conflict.notfound=No group conflicts found! + +# Commands +## Info Command +mv-inventories.info.world=&b===[ Info for world: &6{world}&b ]=== +mv-inventories.info.world.info=&6Groups:&f {groups} +mv-inventories.info.group=&b===[ Info for group: &6{group}&b ]=== +mv-inventories.info.group.info=&6Worlds:&f {worlds} +mv-inventories.info.group.infoshares=&bShares:&f {shares} +mv-inventories.info.group.infonegativeshares=&bNegative Shares:&f {shares} +mv-inventories.info.zeroarg=You may only use the no argument version of this command in game! + +# List Command +mv-inventories.list.groups=&b===[ Group List ]=== +mv-inventories.list.groups.info=&6Groups:&f {groups} + +# Reload Command +mv-inventories.reload.complete=&b===[ Reload Complete! ]=== + +# Addworld Command +mv-inventories.addworld.worldadded=&6World:&f {world} &6added to Group: &f{group} +mv-inventories.addworld.worldalreadyexists=&6World:&f {world} &6already part of Group: &f{group} + +# Removeworld Command +mv-inventories.removeworld.worldremoved=&6World:&f {world} &6removed from Group: &f{group} +mv-inventories.removeworld.worldnotingroup=&6World:&f {world} &6is not part of Group: &f{group} + +# Add/remove shares command +mv-inventories.shares.nowsharing=&6Group: &f{group} &6is now sharing: &f{shares} &6and NOT sharing: &f{negativeshares} + +# Add/remove disabled shares command +mv-inventories.disabledshares.nowsharing=&6Group &f{group} &6now has the following disabled shares: &f{shares} + +# Spawn command +mv-inventories.spawn.teleporting=Teleporting to this world group's spawn... +mv-inventories.spawn.teleportedby=You were teleported by: {player} +mv-inventories.spawn.teleportconsoleerror=From the console, you must provide a PLAYER + +# Debug command +mv-inventories.debug.invaliddebug=&fInvalid debug level. Please use number 0-3. &b(3 being many many messages!) +mv-inventories.debug.set=Debug mode is set to {mode}. + +# Toggle command +mv-inventories.toggle.nowusingoptional=&f{share} &6will now be considered when player's change world. +mv-inventories.toggle.nownotusingoptional=&f{share} &6will no longer be considered when player's change world. +mv-inventories.toggle.nooptionalshares=&f{share} &6is not an optional share! + +# Group command +mv-inventories.group.commandprompt=&6What would you like to do? &fCreate&6, &fEdit &6or &fDelete&6. Enter &f##&6 at any time to cancel. +mv-inventories.group.createprompt=&6Please name your new group: +mv-inventories.group.editprompt=&6Edit which group? {groups} +mv-inventories.group.deleteprompt=&6Delete which group? {groups} +mv-inventories.group.modifyprompt=&6Which would you like to change for &e{group}&6? &fWorlds &6or &fShares&6. Enter &f##&6 to finish. +mv-inventories.group.worldsprompt=&6Enter the name of a world to add to group &f{group}&6 or enter &f@&6 to continue. To remove a world, precede the name with the minus symbol. (ex: &f-worldname&6). Current worlds: {worlds} +mv-inventories.group.sharesprompt=&6Enter &fall&6 or a specific share to add to group &f{group}&6 or enter &f@&6 to continue. To remove shares, precede the name with the minus symbol (ex: &f-inventory&6). Current shares: {shares} +mv-inventories.group.invalidname=&cThat name is not valid! May only contain letters, numbers, and underscores. +mv-inventories.group.exists=&cThat group already exists! (&f{group}&c) +mv-inventories.group.removed=&2Removed group: &f{group} +mv-inventories.group.worldsempty=&cYou may not have a group with no worlds, please add worlds or type &f##&c to cancel. +mv-inventories.group.creationcomplete=&2You created a new group '{group}'! +mv-inventories.group.updated=&2Group has been updated! +mv-inventories.group.nonconversable=You are not allowed to access conversations (remote console?) +mv-inventories.group.invalidoption=&cThat is not a valid option! Type &f##&c to stop working on groups. + +# Migrate command +mv-inventories.migrate.pluginnotenabled=&f{plugin} &6is not enabled so you may not import data from it! +mv-inventories.migrate.unsupportedplugin=&6Sorry, ''&f{plugin}&6'' is not supported for importing. +mv-inventories.migrate.confirmprompt=&6Are you sure you want to import data from &f{plugin}&6? This will override existing Multiverse-Inventories playerdata!!! +mv-inventories.migrate.success=&2Successfully imported data from &f{plugin}! +mv-inventories.migrate.failed=Failed to import data from &f{plugin}! + +# Deletegroup command +mv-inventories.deletegroup.confirmprompt=Are you sure you want to delete group &f{group}&6? +mv-inventories.deletegroup.success=&2Successfully deleted group: &f{group}! \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 07ebb670..493d7f9c 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,108 +1,8 @@ name: Multiverse-Inventories -main: com.onarandombox.multiverseinventories.MultiverseInventories +main: org.mvplugins.multiverse.inventories.MultiverseInventories version: ${version} api-version: 1.13 -author: dumptruckman +authors: ['dumptruckman', 'benwoo1110'] +website: 'https://dev.bukkit.org/projects/multiverse-inventories' depend: ['Multiverse-Core'] -softdepend: [MultiInv, WorldInventories, Multiverse-Adventure] - -commands: - mvinv: - description: Generic Multiverse-Inventories Command - usage: / - mvinvinfo: - description: Generic Multiverse-Inventories Command - usage: / - mvinvi: - description: Generic Multiverse-Inventories Command - usage: / - mvinvim: - description: Generic Multiverse-Inventories Command - usage: / - mvinvimport: - description: Generic Multiverse-Inventories Command - usage: / - mvinvlist: - description: Generic Multiverse-Inventories Command - usage: / - mvinvl: - description: Generic Multiverse-Inventories Command - usage: / - mvinvreload: - description: Generic Multiverse-Inventories Command - usage: / - mvinvaddshares: - description: Generic Multiverse-Inventories Command - usage: / - mvinvadds: - description: Generic Multiverse-Inventories Command - usage: / - mvinvas: - description: Generic Multiverse-Inventories Command - usage: / - mvinvraddshare: - description: Generic Multiverse-Inventories Command - usage: / - mvinvaddworld: - description: Generic Multiverse-Inventories Command - usage: / - mvinvaddw: - description: Generic Multiverse-Inventories Command - usage: / - mvinvaw: - description: Generic Multiverse-Inventories Command - usage: / - mvinvremoveshares: - description: Generic Multiverse-Inventories Command - usage: / - mvinvremoveshare: - description: Generic Multiverse-Inventories Command - usage: / - mvinvremoves: - description: Generic Multiverse-Inventories Command - usage: / - mvinvrmshares: - description: Generic Multiverse-Inventories Command - usage: / - mvinvrmshare: - description: Generic Multiverse-Inventories Command - usage: / - mvinvrms: - description: Generic Multiverse-Inventories Command - usage: / - mvinvrs: - description: Generic Multiverse-Inventories Command - usage: / - mvinvremoveworld: - description: Generic Multiverse-Inventories Command - usage: / - mvinvremovew: - description: Generic Multiverse-Inventories Command - usage: / - mvinvrmworld: - description: Generic Multiverse-Inventories Command - usage: / - mvinvrmw: - description: Generic Multiverse-Inventories Command - usage: / - mvinvrw: - description: Generic Multiverse-Inventories Command - usage: / - mvinvspawn: - description: Generic Multiverse-Inventories Command - usage: / - mvinvs: - description: Generic Multiverse-Inventories Command - usage: / - ispawn: - description: Generic Multiverse-Inventories Command - usage: / - gspawn: - description: Generic Multiverse-Inventories Command - usage: / - mvinvdebug: - description: Generic Multiverse-Inventories Command - usage: / - mvinvd: - description: Generic Multiverse-Inventories Command - usage: / +softdepend: [LuckPerms, MultiInv, WorldInventories, PerWorldInventory] diff --git a/src/test/java/com/onarandombox/multiverseinventories/FlatFileDataHelper.java b/src/test/java/com/onarandombox/multiverseinventories/FlatFileDataHelper.java deleted file mode 100644 index 85f1ba43..00000000 --- a/src/test/java/com/onarandombox/multiverseinventories/FlatFileDataHelper.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.onarandombox.multiverseinventories; - -import com.onarandombox.multiverseinventories.profile.ProfileDataSource; -import com.onarandombox.multiverseinventories.profile.container.ContainerType; - -import java.io.File; -import java.io.IOException; - -public class FlatFileDataHelper { - - private final FlatFileProfileDataSource data; - - public FlatFileDataHelper(ProfileDataSource data) { - if (!(data instanceof FlatFileProfileDataSource)) { - throw new ClassCastException("Must be instance of FlatFilePlayerData"); - } - this.data = (FlatFileProfileDataSource) data; - } - - public File getPlayerFile(ContainerType type, String dataName, String playerName) throws IOException { - return data.getPlayerFile(type, dataName, playerName); - } -} diff --git a/src/test/java/com/onarandombox/multiverseinventories/TestCommands.java b/src/test/java/com/onarandombox/multiverseinventories/TestCommands.java deleted file mode 100644 index 5be18d5e..00000000 --- a/src/test/java/com/onarandombox/multiverseinventories/TestCommands.java +++ /dev/null @@ -1,165 +0,0 @@ -/****************************************************************************** - * Multiverse 2 Copyright (c) the Multiverse Team 2011. * - * Multiverse 2 is licensed under the BSD License. * - * For more information please check the README.md file included * - * with this project. * - ******************************************************************************/ - -package com.onarandombox.multiverseinventories; - -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.util.TestInstanceCreator; -import org.bukkit.Server; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.plugin.Plugin; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.io.File; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class TestCommands { - TestInstanceCreator creator; - Server mockServer; - CommandSender mockCommandSender; - - @Before - public void setUp() throws Exception { - creator = new TestInstanceCreator(); - assertTrue(creator.setUp()); - mockServer = creator.getServer(); - mockCommandSender = creator.getCommandSender(); - } - - @After - public void tearDown() throws Exception { - creator.tearDown(); - } - - @Test - public void testDebugReload() { - // Pull a core instance from the server. - Plugin plugin = mockServer.getPluginManager().getPlugin("Multiverse-Inventories"); - MultiverseInventories inventories = (MultiverseInventories) plugin; - - // Make sure Core is not null - assertNotNull(plugin); - - // Make sure Core is enabled - assertTrue(plugin.isEnabled()); - - // Make a fake server folder to fool MV into thinking a world folder exists. - File serverDirectory = new File(creator.getPlugin().getServerFolder(), "world"); - serverDirectory.mkdirs(); - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - Command mockCoreCommand = mock(Command.class); - when(mockCoreCommand.getName()).thenReturn("mv"); - - // Assert debug mode is off - assertEquals(0, inventories.getMVIConfig().getGlobalDebug()); - - // Send the debug command. - String[] debugArgs = new String[]{"debug", "3"}; - plugin.onCommand(mockCommandSender, mockCoreCommand, "", debugArgs); - - assertEquals(3, inventories.getMVIConfig().getGlobalDebug()); - - // Send the debug command. - String[] reloadArgs = new String[] { "reload" }; - plugin.onCommand(mockCommandSender, mockCommand, "", reloadArgs); - - assertEquals(3, inventories.getMVIConfig().getGlobalDebug()); - } - - @Test - public void testInfoCommand() { - // Pull a core instance from the server. - Plugin plugin = mockServer.getPluginManager().getPlugin("Multiverse-Inventories"); - MultiverseInventories inventories = (MultiverseInventories) plugin; - - // Make sure Core is not null - assertNotNull(plugin); - - // Make sure Core is enabled - assertTrue(plugin.isEnabled()); - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - // Send the debug command. - String[] debugArgs = new String[]{ "info", "default"}; - plugin.onCommand(mockCommandSender, mockCommand, "", debugArgs); - } - - @Test - public void testToggleCommand() { - // Pull a core instance from the server. - Plugin plugin = mockServer.getPluginManager().getPlugin("Multiverse-Inventories"); - MultiverseInventories inventories = (MultiverseInventories) plugin; - - // Make sure Core is not null - assertNotNull(plugin); - - // Make sure Core is enabled - assertTrue(plugin.isEnabled()); - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - assertFalse(inventories.getMVIConfig().getOptionalShares().contains(Sharables.ECONOMY)); - // Send the debug command. - String[] cmdArgs = new String[]{ "toggle", "economy" }; - plugin.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - assertTrue(inventories.getMVIConfig().getOptionalShares().contains(Sharables.ECONOMY)); - cmdArgs = new String[]{ "reload" }; - plugin.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - assertTrue(inventories.getMVIConfig().getOptionalShares().contains(Sharables.ECONOMY)); - cmdArgs = new String[]{ "toggle", "economy" }; - plugin.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - assertFalse(inventories.getMVIConfig().getOptionalShares().contains(Sharables.ECONOMY)); - } - - @Test - public void testGroupNoWorlds() { - // Pull a core instance from the server. - Plugin plugin = mockServer.getPluginManager().getPlugin("Multiverse-Inventories"); - MultiverseInventories inventories = (MultiverseInventories) plugin; - - // Make sure Core is not null - assertNotNull(plugin); - - // Make sure Core is enabled - assertTrue(plugin.isEnabled()); - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - String[] cmdArgs = new String[]{ "rmworld", "world", "default" }; - plugin.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - cmdArgs = new String[]{ "rmworld", "world_nether", "default" }; - plugin.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - cmdArgs = new String[]{ "rmworld", "world_the_end", "default" }; - plugin.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - cmdArgs = new String[]{ "reload" }; - plugin.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - cmdArgs = new String[]{ "info", "default" }; - plugin.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - } -} diff --git a/src/test/java/com/onarandombox/multiverseinventories/TestCommentedYamlConfiguration.java b/src/test/java/com/onarandombox/multiverseinventories/TestCommentedYamlConfiguration.java deleted file mode 100644 index 04b489f5..00000000 --- a/src/test/java/com/onarandombox/multiverseinventories/TestCommentedYamlConfiguration.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.onarandombox.multiverseinventories; - -import com.onarandombox.multiverseinventories.util.CommentedYamlConfiguration; -import org.junit.Test; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.PrintWriter; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.Arrays; - -import static org.junit.Assert.assertEquals; - -public class TestCommentedYamlConfiguration { - private static final char LINE_SEPARATOR = '\n'; - private static final File TEST_CONFIG = new File("bin/test/testconfig.yml"); - - private static final String TEST_CONTENTS_1 = "# A Test Yaml File" + LINE_SEPARATOR + LINE_SEPARATOR + - "test: 123" + LINE_SEPARATOR + - "a_map:" + LINE_SEPARATOR + - " something: yep" + LINE_SEPARATOR + - " something_else: 42" + LINE_SEPARATOR + - " one_more_something_else: 24" + LINE_SEPARATOR + - " a_list:" + LINE_SEPARATOR + - " - 1" + LINE_SEPARATOR + - " - 2" + LINE_SEPARATOR + - " a_child_map:" + LINE_SEPARATOR + - " another_map:" + LINE_SEPARATOR + - " test: 123" + LINE_SEPARATOR + - " two_steps_back:" + LINE_SEPARATOR + - " test: true" + LINE_SEPARATOR + - "back_to_root: true" + LINE_SEPARATOR; - - private static final String COMMENTED_TEST_CONTENTS_1 = "# A Test Yaml File" + LINE_SEPARATOR + - LINE_SEPARATOR + - "# Yay" + LINE_SEPARATOR + - "test: 123" + LINE_SEPARATOR + - LINE_SEPARATOR + - "# They seem to be" + LINE_SEPARATOR + - "# Working" + LINE_SEPARATOR + - "a_map:" + LINE_SEPARATOR + - " # Yeah, they're working" + LINE_SEPARATOR + - " something: yep" + LINE_SEPARATOR + - " something_else: 42" + LINE_SEPARATOR + - " one_more_something_else: 24" + LINE_SEPARATOR + - LINE_SEPARATOR + - " # Aww yeah, comments on a list" + LINE_SEPARATOR + - " a_list:" + LINE_SEPARATOR + - " - 1" + LINE_SEPARATOR + - " - 2" + LINE_SEPARATOR + - " a_child_map:" + LINE_SEPARATOR + - " # Comments on a child child map" + LINE_SEPARATOR + - " another_map:" + LINE_SEPARATOR + - " test: 123" + LINE_SEPARATOR + - LINE_SEPARATOR + - " # Two steps back comments" + LINE_SEPARATOR + - " two_steps_back:" + LINE_SEPARATOR + - " test: true" + LINE_SEPARATOR + - LINE_SEPARATOR + - "# Back to root comments" + LINE_SEPARATOR + - "back_to_root: true" + LINE_SEPARATOR; - - private CommentedYamlConfiguration createConfig(boolean doComments) { - TEST_CONFIG.getParentFile().mkdirs(); - try (PrintWriter out = new PrintWriter(TEST_CONFIG)) { - out.println(TEST_CONTENTS_1); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - - CommentedYamlConfiguration testConfig = new CommentedYamlConfiguration(TEST_CONFIG, doComments); - - testConfig.getConfig().options().header("A Test Yaml File\n"); - - testConfig.addComment("test", Arrays.asList("# Yay")); - testConfig.addComment("a_map", Arrays.asList("# They seem to be", "# Working")); - testConfig.addComment("a_map.something", Arrays.asList("# Yeah, they're working")); - testConfig.addComment("a_map.a_list", Arrays.asList("# Aww yeah, comments on a list")); - testConfig.addComment("a_map.a_child_map.another_map", Arrays.asList("# Comments on a child child map")); - testConfig.addComment("a_map.two_steps_back", Arrays.asList("# Two steps back comments")); - testConfig.addComment("back_to_root", Arrays.asList("# Back to root comments")); - - return testConfig; - } - - @Test - public void testNoComments() throws Exception { - CommentedYamlConfiguration testConfig = createConfig(false); - testConfig.save(); - - String uncommentedConfigFile = new String(Files.readAllBytes(TEST_CONFIG.getCanonicalFile().toPath()), StandardCharsets.UTF_8); - assertEquals(TEST_CONTENTS_1, uncommentedConfigFile); - } - - @Test - public void testWithComments() throws Exception { - CommentedYamlConfiguration testConfig = createConfig(true); - testConfig.save(); - - String commentedConfigFile = new String(Files.readAllBytes(TEST_CONFIG.getCanonicalFile().toPath()), StandardCharsets.UTF_8); - assertEquals(COMMENTED_TEST_CONTENTS_1, commentedConfigFile); - } -} diff --git a/src/test/java/com/onarandombox/multiverseinventories/TestPerformance.java b/src/test/java/com/onarandombox/multiverseinventories/TestPerformance.java deleted file mode 100644 index 60afbc57..00000000 --- a/src/test/java/com/onarandombox/multiverseinventories/TestPerformance.java +++ /dev/null @@ -1,319 +0,0 @@ -package com.onarandombox.multiverseinventories; - -import com.onarandombox.multiverseinventories.event.ShareHandlingEvent; -import com.onarandombox.multiverseinventories.profile.PlayerProfile; -import com.onarandombox.multiverseinventories.share.Sharable; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.util.TestInstanceCreator; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Server; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Player; -import org.bukkit.event.player.PlayerChangedWorldEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; -import org.bukkit.plugin.Plugin; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.Map; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class TestPerformance { - TestInstanceCreator creator; - Server mockServer; - CommandSender mockCommandSender; - MultiverseInventories inventories; - InventoriesListener listener; - - @Before - public void setUp() throws Exception { - creator = new TestInstanceCreator(); - assertTrue(creator.setUp()); - mockServer = creator.getServer(); - mockCommandSender = creator.getCommandSender(); - // Pull a core instance from the server. - Plugin plugin = mockServer.getPluginManager().getPlugin("Multiverse-Inventories"); - // Make sure Core is not null - assertNotNull(plugin); - inventories = (MultiverseInventories) plugin; - Field field = MultiverseInventories.class.getDeclaredField("inventoriesListener"); - field.setAccessible(true); - listener = (InventoriesListener) field.get(inventories); - // Make sure Core is enabled - assertTrue(inventories.isEnabled()); - } - - @After - public void tearDown() throws Exception { - creator.tearDown(); - } - - public void changeWorld(Player player, String fromWorld, String toWorld) { - Location location = new Location(mockServer.getWorld(toWorld), 0.0, 70.0, 0.0); - player.teleport(location); - assertEquals(location, player.getLocation()); - listener.playerChangedWorld(new PlayerChangedWorldEvent(player, mockServer.getWorld(fromWorld))); - } - - public void addToInventory(PlayerInventory inventory, Map items) { - for (Map.Entry invEntry : items.entrySet()) { - inventory.setItem(invEntry.getKey(), invEntry.getValue()); - } - } - - private Player getPreparedPlayer() { - Player player = inventories.getServer().getPlayerExact("dumptruckman"); - - Map fillerItems = new HashMap(); - for (int i = 0; i < PlayerStats.INVENTORY_SIZE; i++) { - ItemStack item = new ItemStack(Material.STONE_BRICKS, 64); - Enchantment mockEnchantment = mock(Enchantment.class); - when(mockEnchantment.getName()).thenReturn("Protection"); - item.addUnsafeEnchantment(mockEnchantment, 3); - mockEnchantment = mock(Enchantment.class); - when(mockEnchantment.getName()).thenReturn("Respiration"); - item.addUnsafeEnchantment(mockEnchantment, 3); - mockEnchantment = mock(Enchantment.class); - when(mockEnchantment.getName()).thenReturn("Smite"); - item.addUnsafeEnchantment(mockEnchantment, 3); - fillerItems.put(i, item); - } - addToInventory(player.getInventory(), fillerItems); - player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, 50, 3)); - player.addPotionEffect(new PotionEffect(PotionEffectType.FAST_DIGGING, 50, 3)); - player.addPotionEffect(new PotionEffect(PotionEffectType.FIRE_RESISTANCE, 50, 3)); - return player; - } - - @Test - public void testOverallWorldChangePerformance() { - int numTests = 100; - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - WorldGroup newGroup = inventories.getGroupManager().newEmptyGroup("test"); - newGroup.getShares().mergeShares(Sharables.allOf()); - newGroup.addWorld("world2"); - inventories.getGroupManager().updateGroup(newGroup); - - newGroup = inventories.getGroupManager().newEmptyGroup("test2"); - newGroup.getShares().mergeShares(Sharables.allOf()); - newGroup.addWorld("world"); - inventories.getGroupManager().updateGroup(newGroup); - newGroup = inventories.getGroupManager().newEmptyGroup("test3"); - newGroup.getShares().mergeShares(Sharables.allOf()); - newGroup.addWorld("world"); - inventories.getGroupManager().updateGroup(newGroup); - newGroup = inventories.getGroupManager().newEmptyGroup("test4"); - newGroup.getShares().mergeShares(Sharables.allOf()); - newGroup.addWorld("world"); - inventories.getGroupManager().updateGroup(newGroup); - newGroup = inventories.getGroupManager().newEmptyGroup("test5"); - newGroup.getShares().mergeShares(Sharables.allOf()); - newGroup.addWorld("world"); - inventories.getGroupManager().updateGroup(newGroup); - - // Verify removal - assertFalse(inventories.getGroupManager().getDefaultGroup().getWorlds().contains("world2")); - String[] cmdArgs = new String[]{"info", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - Player player = getPreparedPlayer(); - - long startTime = 0; - long endTime = 0; - double[] timeTaken = new double[numTests]; - double total = 0; - - for (int i = 0; i < numTests; i++) { - startTime = System.nanoTime(); - changeWorld(player, "world", "world2"); - endTime = System.nanoTime(); - timeTaken[i] = (endTime - startTime) / 1000000D; - total += timeTaken[i]; - changeWorld(player, "world2", "world"); - } - double cachedAverage = (total / numTests); - - timeTaken = new double[numTests]; - total = 0; - for (int i = 0; i < numTests; i++) { - startTime = System.nanoTime(); - changeWorld(player, "world", "world2"); - endTime = System.nanoTime(); - timeTaken[i] = (endTime - startTime) / 1000000D; - total += timeTaken[i]; - changeWorld(player, "world2", "world"); - inventories.reloadConfig(); - } - double uncachedAverage = (total / numTests); - - timeTaken = new double[numTests]; - total = 0; - for (int i = 0; i < numTests; i++) { - startTime = System.nanoTime(); - new WorldChangeShareHandler(inventories, player, "world", "world2"); - endTime = System.nanoTime(); - timeTaken[i] = (endTime - startTime) / 1000000D; - total += timeTaken[i]; - } - double groupCollectionAverage = (total / numTests); - - timeTaken = new double[numTests]; - total = 0; - ShareHandlingEvent event = new WorldChangeShareHandler(inventories, player, "world", "world2").createEvent(); - for (int i = 0; i < numTests; i++) { - startTime = System.nanoTime(); - ShareHandlingUpdater.updateProfile(inventories, player, event.getWriteProfiles().get(0)); - endTime = System.nanoTime(); - timeTaken[i] = (endTime - startTime) / 1000000D; - total += timeTaken[i]; - } - double profileUpdateAverage = (total / numTests); - - timeTaken = new double[numTests]; - total = 0; - event = new WorldChangeShareHandler(inventories, player, "world", "world2").createEvent(); - for (int i = 0; i < numTests; i++) { - startTime = System.nanoTime(); - ShareHandlingUpdater.updatePlayer(inventories, player, event.getReadProfiles().get(0)); - endTime = System.nanoTime(); - timeTaken[i] = (endTime - startTime) / 1000000D; - total += timeTaken[i]; - } - double playerUpdateAverage = (total / numTests); - - System.out.println("Average Time for group collection: " + groupCollectionAverage + "ms"); - System.out.println("Average Time for cached world change: " + cachedAverage + "ms"); - System.out.println("Average Time for cached profile update: " + profileUpdateAverage + "ms"); - System.out.println("Average Time for cached player update: " + playerUpdateAverage + "ms"); - System.out.println("Average Time for uncached world change: " + uncachedAverage + "ms"); - } - - @Test - public void testIndividualSharesPerformance() { - int numTests = 100; - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - WorldGroup newGroup = inventories.getGroupManager().newEmptyGroup("test"); - newGroup.getShares().mergeShares(Sharables.allOf()); - newGroup.addWorld("world2"); - inventories.getGroupManager().updateGroup(newGroup); - - // Verify removal - assertFalse(inventories.getGroupManager().getDefaultGroup().getWorlds().contains("world2")); - String[] cmdArgs = new String[]{"info", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - Player player = getPreparedPlayer(); - changeWorld(player, "world", "world2"); - changeWorld(player, "world2", "world"); - - Map averageUpdatePlayer = new HashMap(); - Map averageUpdateProfile = new HashMap(); - PlayerProfile profile = inventories.getGroupManager().getDefaultGroup().getGroupProfileContainer().getPlayerData(player); - for (Sharable share : Sharables.all()) { - if (share.isOptional()) { - continue; - } - long startTime = 0; - long endTime = 0; - double[] timeTaken = new double[numTests]; - double total = 0; - for (int i = 0; i < numTests; i++) { - startTime = System.nanoTime(); - share.getHandler().updateProfile(profile, player); - endTime = System.nanoTime(); - timeTaken[i] = (endTime - startTime) / 1000000D; - total += timeTaken[i]; - } - averageUpdateProfile.put(share, (total / numTests)); - timeTaken = new double[numTests]; - total = 0; - for (int i = 0; i < numTests; i++) { - startTime = System.nanoTime(); - share.getHandler().updatePlayer(player, profile); - endTime = System.nanoTime(); - timeTaken[i] = (endTime - startTime) / 1000000D; - total += timeTaken[i]; - } - averageUpdatePlayer.put(share, (total / numTests)); - } - - for (Sharable share : Sharables.all()) { - System.out.println("Average Time for " + share.getNames()[0] + ".updatePlayer(): " + averageUpdatePlayer.get(share)); - System.out.println("Average Time for " + share.getNames()[0] + ".updateProfile(): " + averageUpdateProfile.get(share)); - } - } - - @Test - public void testLargeGroupCollectionPerformance() { - int numTests = 100; - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - WorldGroup newGroup = inventories.getGroupManager().newEmptyGroup("test"); - newGroup.getShares().mergeShares(Sharables.allOf()); - newGroup.addWorld("world2"); - inventories.getGroupManager().addGroup(newGroup, true); - - // Verify removal - assertFalse(inventories.getGroupManager().getDefaultGroup().getWorlds().contains("world2")); - String[] cmdArgs = new String[]{"info", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - Player player = getPreparedPlayer(); - - long startTime = 0; - long endTime = 0; - double[] timeTaken = new double[numTests]; - double total = 0; - - for (int i = 0; i < numTests; i++) { - startTime = System.nanoTime(); - changeWorld(player, "world", "world2"); - endTime = System.nanoTime(); - timeTaken[i] = (endTime - startTime) / 1000000D; - total += timeTaken[i]; - changeWorld(player, "world2", "world"); - } - double average = (total / numTests); - - timeTaken = new double[numTests]; - total = 0; - for (int i = 0; i < numTests; i++) { - startTime = System.nanoTime(); - changeWorld(player, "world", "world2"); - endTime = System.nanoTime(); - timeTaken[i] = (endTime - startTime) / 1000000D; - total += timeTaken[i]; - changeWorld(player, "world2", "world"); - cmdArgs = new String[]{"reload"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - } - System.out.println("Average Time for cached world change: " + average + "ms"); - System.out.println("Average Time for uncached world change: " + (total / numTests) + "ms"); - } -} diff --git a/src/test/java/com/onarandombox/multiverseinventories/TestPlayerNameChange.java b/src/test/java/com/onarandombox/multiverseinventories/TestPlayerNameChange.java deleted file mode 100644 index f33bdf31..00000000 --- a/src/test/java/com/onarandombox/multiverseinventories/TestPlayerNameChange.java +++ /dev/null @@ -1,204 +0,0 @@ -package com.onarandombox.multiverseinventories; - -import com.onarandombox.multiverseinventories.profile.GlobalProfile; -import com.onarandombox.multiverseinventories.util.MockPlayerFactory; -import com.onarandombox.multiverseinventories.util.TestInstanceCreator; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Server; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.event.player.AsyncPlayerPreLoginEvent; -import org.bukkit.event.player.PlayerChangedWorldEvent; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.event.player.PlayerTeleportEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; -import org.bukkit.plugin.Plugin; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.lang.reflect.Field; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class TestPlayerNameChange { - TestInstanceCreator creator; - Server mockServer; - CommandSender mockCommandSender; - MultiverseInventories inventories; - InventoriesListener listener; - - @Before - public void setUp() throws Exception { - creator = new TestInstanceCreator(); - assertTrue(creator.setUp()); - mockServer = creator.getServer(); - mockCommandSender = creator.getCommandSender(); - // Pull a core instance from the server. - Plugin plugin = mockServer.getPluginManager().getPlugin("Multiverse-Inventories"); - // Make sure Core is not null - assertNotNull(plugin); - inventories = (MultiverseInventories) plugin; - Field field = MultiverseInventories.class.getDeclaredField("inventoriesListener"); - field.setAccessible(true); - listener = (InventoriesListener) field.get(inventories); - // Make sure Core is enabled - assertTrue(inventories.isEnabled()); - } - - @After - public void tearDown() throws Exception { - creator.tearDown(); - } - - public void changeWorld(Player player, String fromWorld, String toWorld) { - Location oldLocation = player.getLocation(); - Location location = new Location(mockServer.getWorld(toWorld), 0.0, 70.0, 0.0); - player.teleport(location); - assertEquals(location, player.getLocation()); - listener.playerTeleport(new PlayerTeleportEvent(player, oldLocation, location)); - listener.playerChangedWorld(new PlayerChangedWorldEvent(player, mockServer.getWorld(fromWorld))); - } - - public void addToInventory(PlayerInventory inventory, Map items) { - for (Map.Entry invEntry : items.entrySet()) { - inventory.setItem(invEntry.getKey(), invEntry.getValue()); - } - } - - public static Map getFillerInv() { - Map fillerItems = new HashMap(); - fillerItems.put(3, new ItemStack(Material.BOW, 1)); - fillerItems.put(13, new ItemStack(Material.DIRT, 64)); - fillerItems.put(36, new ItemStack(Material.IRON_HELMET, 1)); - ItemStack book = new ItemStack(Material.WRITTEN_BOOK, 1); - fillerItems.put(1, book); - ItemStack leather = new ItemStack(Material.LEATHER_BOOTS, 1); - fillerItems.put(2, leather); - return fillerItems; - } - - public static Map getFillerInv2() { - Map fillerItems = new HashMap(); - fillerItems.put(3, new ItemStack(Material.DIAMOND_PICKAXE, 1)); - fillerItems.put(13, new ItemStack(Material.STONE, 64)); - fillerItems.put(36, new ItemStack(Material.IRON_HELMET, 1)); - ItemStack book = new ItemStack(Material.BOOK, 1); - fillerItems.put(1, book); - ItemStack leather = new ItemStack(Material.GOLDEN_BOOTS, 1); - fillerItems.put(2, leather); - return fillerItems; - } - - public void doPlayerJoin(Player player) throws UnknownHostException { - this.listener.playerPreLogin(new AsyncPlayerPreLoginEvent(player.getName(), - InetAddress.getLocalHost(), player.getUniqueId())); - this.listener.playerJoin(new PlayerJoinEvent(player, null)); - } - - public void doPlayerQuit(Player player) { - this.listener.playerQuit(new PlayerQuitEvent(player, null)); - } - - public void changePlayerName(Player player, String targetName) { - assertNotNull(player); - - String oldName = player.getName(); - UUID oldUUID = player.getUniqueId(); - - MockPlayerFactory.changeName(player, targetName); - - String newName = player.getName(); - UUID newUUID = player.getUniqueId(); - - assertEquals(oldName, "dumptruckman"); - assertEquals(newName, "benwoo1110"); - assertEquals(oldUUID, newUUID); - } - - public GlobalProfile getAndCheckGlobalProfile(Player player) { - GlobalProfile globalProfile = this.inventories.getData().getGlobalProfile(player.getName(), player.getUniqueId()); - assertEquals(globalProfile.getLastKnownName(), player.getName()); - assertEquals(globalProfile.getPlayerName(), player.getName()); - assertEquals(globalProfile.getPlayerUUID(), player.getUniqueId()); - - return globalProfile; - } - - /** - * Ensure that player data is migrated on name change. - */ - @Test - public void TestPlayerNameChangeMigration() throws UnknownHostException { - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - Command mockCoreCommand = mock(Command.class); - when(mockCoreCommand.getName()).thenReturn("mv"); - - // Assert debug mode is off - assertEquals(0, this.inventories.getMVIConfig().getGlobalDebug()); - - // Send the debug command. - String[] cmdArgs = new String[]{"debug", "3"}; - this.inventories.onCommand(this.mockCommandSender, mockCoreCommand, "", cmdArgs); - - cmdArgs = new String[]{"reload"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - // Assert debug mode is on - assertEquals(3, inventories.getMVIConfig().getGlobalDebug()); - - // Getting player - Player player = this.mockServer.getPlayerExact("dumptruckman"); - assertNotNull(player); - - doPlayerJoin(player); - GlobalProfile globalProfile = getAndCheckGlobalProfile(player); - - // Set inv for world - addToInventory(player.getInventory(), getFillerInv()); - String worldInvData = player.getInventory().toString(); - - changeWorld(player, "world", "world2"); - assertNotSame(player.getInventory().toString(), worldInvData); - - // Set inv for world2 - addToInventory(player.getInventory(), getFillerInv2()); - String world2InvData = player.getInventory().toString(); - - doPlayerQuit(player); - changePlayerName(player, "benwoo1110"); - doPlayerJoin(player); - - globalProfile = getAndCheckGlobalProfile(player); - assertNotSame(player.getInventory().toString(), worldInvData); - assertEquals(player.getInventory().toString(), world2InvData); - - // Go back to world_nether which is in group default - // i.e. Should be the same inv as world. - changeWorld(player, "world2", "world_nether"); - assertNotSame(player.getInventory().toString(), world2InvData); - assertEquals(player.getInventory().toString(), worldInvData); - - // Go back to world - changeWorld(player, "world_nether", "world"); - assertNotSame(player.getInventory().toString(), world2InvData); - assertEquals(player.getInventory().toString(), worldInvData); - } -} diff --git a/src/test/java/com/onarandombox/multiverseinventories/TestResetWorld.java b/src/test/java/com/onarandombox/multiverseinventories/TestResetWorld.java deleted file mode 100644 index a55e38ed..00000000 --- a/src/test/java/com/onarandombox/multiverseinventories/TestResetWorld.java +++ /dev/null @@ -1,142 +0,0 @@ -package com.onarandombox.multiverseinventories; - -import com.onarandombox.MultiverseAdventure.event.MVAResetFinishedEvent; -import com.onarandombox.multiverseinventories.util.TestInstanceCreator; -import org.bukkit.Color; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Server; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.event.player.PlayerChangedWorldEvent; -import org.bukkit.event.player.PlayerTeleportEvent; -import org.bukkit.event.world.WorldUnloadEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; -import org.bukkit.inventory.meta.BookMeta; -import org.bukkit.inventory.meta.LeatherArmorMeta; -import org.bukkit.plugin.Plugin; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.lang.reflect.Field; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class TestResetWorld { - TestInstanceCreator creator; - Server mockServer; - CommandSender mockCommandSender; - MultiverseInventories inventories; - InventoriesListener listener; - AdventureListener aListener; - - @Before - public void setUp() throws Exception { - creator = new TestInstanceCreator(); - assertTrue(creator.setUp()); - mockServer = creator.getServer(); - mockCommandSender = creator.getCommandSender(); - // Pull a core instance from the server. - Plugin plugin = mockServer.getPluginManager().getPlugin("Multiverse-Inventories"); - // Make sure Core is not null - assertNotNull(plugin); - inventories = (MultiverseInventories) plugin; - Field field = MultiverseInventories.class.getDeclaredField("adventureListener"); - field.setAccessible(true); - aListener = new AdventureListener(inventories); - field.set(inventories, aListener); - field = MultiverseInventories.class.getDeclaredField("inventoriesListener"); - field.setAccessible(true); - listener = (InventoriesListener) field.get(inventories); - // Make sure Core is enabled - assertTrue(inventories.isEnabled()); - } - - @After - public void tearDown() throws Exception { - creator.tearDown(); - } - - public void changeWorld(Player player, String fromWorld, String toWorld) { - Location oldLocation = player.getLocation(); - Location location = new Location(mockServer.getWorld(toWorld), 0.0, 70.0, 0.0); - player.teleport(location); - assertEquals(location, player.getLocation()); - listener.playerTeleport(new PlayerTeleportEvent(player, oldLocation, location)); - listener.playerChangedWorld(new PlayerChangedWorldEvent(player, mockServer.getWorld(fromWorld))); - } - - public void addToInventory(PlayerInventory inventory, Map items) { - for (Map.Entry invEntry : items.entrySet()) { - inventory.setItem(invEntry.getKey(), invEntry.getValue()); - } - } - - @Test - public void testWorldReset() { - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - Command mockCoreCommand = mock(Command.class); - when(mockCoreCommand.getName()).thenReturn("mv"); - - // Assert debug mode is off - assertEquals(0, inventories.getMVIConfig().getGlobalDebug()); - - // Send the debug command. - String[] cmdArgs = new String[]{"debug", "3"}; - inventories.onCommand(mockCommandSender, mockCoreCommand, "", cmdArgs); - - // remove world2 from default group - cmdArgs = new String[]{"rmworld", "world2", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - Player player = inventories.getServer().getPlayerExact("dumptruckman"); - - changeWorld(player, "world", "world2"); - Map fillerItems = new HashMap(); - fillerItems.put(3, new ItemStack(Material.BOW, 1)); - fillerItems.put(13, new ItemStack(Material.DIRT, 64)); - fillerItems.put(36, new ItemStack(Material.IRON_HELMET, 1)); - ItemStack book = new ItemStack(Material.WRITTEN_BOOK, 1); - BookMeta bookMeta = (BookMeta) book.getItemMeta(); - bookMeta.setAuthor("dumptruckman"); - bookMeta.setPages("This is my freaking", "book", "man"); - bookMeta.setDisplayName("Super Book"); - book.setItemMeta(bookMeta); - fillerItems.put(1, book); - ItemStack leather = new ItemStack(Material.LEATHER_BOOTS, 1); - LeatherArmorMeta leatherMeta = (LeatherArmorMeta) leather.getItemMeta(); - leatherMeta.setColor(Color.PURPLE); - leatherMeta.setLore(Arrays.asList("Aww fuck yeah", "Lore")); - leather.setItemMeta(leatherMeta); - fillerItems.put(2, leather); - addToInventory(player.getInventory(), fillerItems); - String originalInventory = player.getInventory().toString(); - - changeWorld(player, "world2", "world"); - String newInventory = player.getInventory().toString(); - - assertNotSame(originalInventory, newInventory); - - listener.worldUnload(new WorldUnloadEvent(mockServer.getWorld("world2"))); - aListener.worldReset(new MVAResetFinishedEvent("world2")); - changeWorld(player, "world", "world2"); - String inventoryAfterReset = player.getInventory().toString(); - - assertEquals(newInventory, inventoryAfterReset); - } -} diff --git a/src/test/java/com/onarandombox/multiverseinventories/TestWSharableAPI.java b/src/test/java/com/onarandombox/multiverseinventories/TestWSharableAPI.java deleted file mode 100644 index ac44ae38..00000000 --- a/src/test/java/com/onarandombox/multiverseinventories/TestWSharableAPI.java +++ /dev/null @@ -1,207 +0,0 @@ -package com.onarandombox.multiverseinventories; - -import com.onarandombox.multiverseinventories.profile.PlayerProfile; -import com.onarandombox.multiverseinventories.share.ProfileEntry; -import com.onarandombox.multiverseinventories.share.Sharable; -import com.onarandombox.multiverseinventories.share.SharableHandler; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.util.TestInstanceCreator; -import org.bukkit.Location; -import org.bukkit.Server; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.event.player.PlayerChangedWorldEvent; -import org.bukkit.event.player.PlayerTeleportEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; -import org.bukkit.plugin.Plugin; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.Map; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class TestWSharableAPI { - TestInstanceCreator creator; - Server mockServer; - CommandSender mockCommandSender; - MultiverseInventories inventories; - InventoriesListener listener; - - @Before - public void setUp() throws Exception { - creator = new TestInstanceCreator(); - assertTrue(creator.setUp()); - mockServer = creator.getServer(); - mockCommandSender = creator.getCommandSender(); - // Pull a core instance from the server. - Plugin plugin = mockServer.getPluginManager().getPlugin("Multiverse-Inventories"); - // Make sure Core is not null - assertNotNull(plugin); - inventories = (MultiverseInventories) plugin; - Field field = MultiverseInventories.class.getDeclaredField("inventoriesListener"); - field.setAccessible(true); - listener = (InventoriesListener) field.get(inventories); - // Make sure Core is enabled - assertTrue(inventories.isEnabled()); - } - - @After - public void tearDown() throws Exception { - creator.tearDown(); - } - - public void changeWorld(Player player, String fromWorld, String toWorld) { - Location oldLocation = player.getLocation(); - Location location = new Location(mockServer.getWorld(toWorld), 0.0, 70.0, 0.0); - player.teleport(location); - assertEquals(location, player.getLocation()); - listener.playerTeleport(new PlayerTeleportEvent(player, oldLocation, location)); - listener.playerChangedWorld(new PlayerChangedWorldEvent(player, mockServer.getWorld(fromWorld))); - } - - public void addToInventory(PlayerInventory inventory, Map items) { - for (Map.Entry invEntry : items.entrySet()) { - inventory.setItem(invEntry.getKey(), invEntry.getValue()); - } - } - - public final static Sharable CUSTOM = new Sharable.Builder("custom", Double.class, - new SharableHandler() { - @Override - public void updateProfile(PlayerProfile profile, Player player) { - profile.set(CUSTOM, (double) player.getMaximumNoDamageTicks()); - } - - @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { - Double value = profile.get(CUSTOM); - if (value == null) { - // Specify default value - player.setMaximumNoDamageTicks(0); - return false; - } - player.setMaximumNoDamageTicks((int) Double.doubleToLongBits(value)); - return true; - } - }).defaultSerializer(new ProfileEntry(false, "custom")).build(); - public final static Sharable OPTIONAL = new Sharable.Builder("optional", Integer.class, - new SharableHandler() { - @Override - public void updateProfile(PlayerProfile profile, Player player) { - profile.set(CUSTOM, player.getLastDamage()); - } - - @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { - Double value = profile.get(CUSTOM); - if (value == null) { - // Specify default value - player.setLastDamage(0); - return false; - } - player.setLastDamage(value); - return true; - } - }).defaultSerializer(new ProfileEntry(false, "optional")).optional().build(); - - public final static Sharable CUSTOM_MAP = new Sharable.Builder("custom_map", Map.class, - new SharableHandler() { - @Override - public void updateProfile(PlayerProfile profile, Player player) { - Map data = new HashMap(); - data.put("maxNoDamageTick", player.getMaximumNoDamageTicks()); - data.put("displayName", player.getDisplayName()); - profile.set(CUSTOM_MAP, data); - } - - @Override - public boolean updatePlayer(Player player, PlayerProfile profile) { - Map data = profile.get(CUSTOM_MAP); - if (data == null) { - // Specify default value - player.setMaximumNoDamageTicks(0); - player.setDisplayName("poop"); - return false; - } - player.setMaximumNoDamageTicks((Integer) data.get("maxNoDamageTick")); - player.setDisplayName(data.get("displayName").toString()); - return true; - } - }).defaultSerializer(new ProfileEntry(false, "custom_map")).build(); - - @Test - public void testSharableAPI() { - - assertTrue(Sharables.all().contains(CUSTOM)); - assertTrue(Sharables.all().contains(OPTIONAL)); - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - Command mockCoreCommand = mock(Command.class); - when(mockCoreCommand.getName()).thenReturn("mv"); - - // Assert debug mode is off - assertEquals(0, inventories.getMVIConfig().getGlobalDebug()); - - // Send the debug command. - String[] cmdArgs = new String[]{"debug", "3"}; - inventories.onCommand(mockCommandSender, mockCoreCommand, "", cmdArgs); - - WorldGroup newGroup = inventories.getGroupManager().newEmptyGroup("test"); - newGroup.getShares().mergeShares(Sharables.allOf()); - newGroup.addWorld("world2"); - newGroup.getShares().remove(OPTIONAL); - inventories.getGroupManager().updateGroup(newGroup); - - // Verify removal - assertFalse(inventories.getGroupManager().getDefaultGroup().getWorlds().contains("world2")); - cmdArgs = new String[]{"info", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertEquals(3, inventories.getMVIConfig().getGlobalDebug()); - - Player player = inventories.getServer().getPlayerExact("dumptruckman"); - //changeWorld(player, "world", "world2"); - //changeWorld(player, "world2", "world"); - - //cmdArgs = new String[]{"addshare", "-optional", "default"}; - //inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - addToInventory(player.getInventory(), TestWorldChanged.getFillerInv()); - player.setMaximumNoDamageTicks(10); - int lastDamage = 10; - player.setLastDamage(lastDamage); - assertEquals(10, player.getMaximumNoDamageTicks()); - String originalInventory = player.getInventory().toString(); - - changeWorld(player, "world", "world_nether"); - String newInventory = player.getInventory().toString(); - assertEquals(originalInventory, newInventory); - assertEquals(10, player.getMaximumNoDamageTicks()); - assertEquals(lastDamage, player.getLastDamage(), 0); - - changeWorld(player, "world_nether", "world2"); - assertEquals(0, player.getMaximumNoDamageTicks()); - assertNotSame(originalInventory, newInventory); - assertEquals(lastDamage, player.getLastDamage(), 0); - changeWorld(player, "world2", "world"); - assertEquals(10, player.getMaximumNoDamageTicks()); - assertEquals(originalInventory, newInventory); - assertEquals(lastDamage, player.getLastDamage(), 0); - } - -} diff --git a/src/test/java/com/onarandombox/multiverseinventories/TestWorldChanged.java b/src/test/java/com/onarandombox/multiverseinventories/TestWorldChanged.java deleted file mode 100644 index 4c17bbf6..00000000 --- a/src/test/java/com/onarandombox/multiverseinventories/TestWorldChanged.java +++ /dev/null @@ -1,539 +0,0 @@ -package com.onarandombox.multiverseinventories; - -import com.dumptruckman.bukkit.configuration.json.JsonConfiguration; -import com.onarandombox.multiverseinventories.profile.container.ContainerType; -import com.onarandombox.multiverseinventories.share.Sharables; -import com.onarandombox.multiverseinventories.share.Shares; -import com.onarandombox.multiverseinventories.util.TestInstanceCreator; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Server; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.event.player.PlayerChangedWorldEvent; -import org.bukkit.event.player.PlayerTeleportEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; -import org.bukkit.plugin.Plugin; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.Map; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class TestWorldChanged { - TestInstanceCreator creator; - Server mockServer; - CommandSender mockCommandSender; - MultiverseInventories inventories; - InventoriesListener listener; - - @Before - public void setUp() throws Exception { - creator = new TestInstanceCreator(); - assertTrue(creator.setUp()); - mockServer = creator.getServer(); - mockCommandSender = creator.getCommandSender(); - // Pull a core instance from the server. - Plugin plugin = mockServer.getPluginManager().getPlugin("Multiverse-Inventories"); - // Make sure Core is not null - assertNotNull(plugin); - inventories = (MultiverseInventories) plugin; - Field field = MultiverseInventories.class.getDeclaredField("inventoriesListener"); - field.setAccessible(true); - listener = (InventoriesListener) field.get(inventories); - // Make sure Core is enabled - assertTrue(inventories.isEnabled()); - } - - @After - public void tearDown() throws Exception { - creator.tearDown(); - } - - public void changeWorld(Player player, String fromWorld, String toWorld) { - Location oldLocation = player.getLocation(); - Location location = new Location(mockServer.getWorld(toWorld), 0.0, 70.0, 0.0); - player.teleport(location); - assertEquals(location, player.getLocation()); - listener.playerTeleport(new PlayerTeleportEvent(player, oldLocation, location)); - listener.playerChangedWorld(new PlayerChangedWorldEvent(player, mockServer.getWorld(fromWorld))); - } - - public void addToInventory(PlayerInventory inventory, Map items) { - for (Map.Entry invEntry : items.entrySet()) { - inventory.setItem(invEntry.getKey(), invEntry.getValue()); - } - } - - public static Map getFillerInv() { - Map fillerItems = new HashMap(); - fillerItems.put(3, new ItemStack(Material.BOW, 1)); - fillerItems.put(13, new ItemStack(Material.DIRT, 64)); - fillerItems.put(36, new ItemStack(Material.IRON_HELMET, 1)); - ItemStack book = new ItemStack(Material.WRITTEN_BOOK, 1); - fillerItems.put(1, book); - ItemStack leather = new ItemStack(Material.LEATHER_BOOTS, 1); - fillerItems.put(2, leather); - return fillerItems; - } - - @Test - public void testBasicWorldChange() throws IOException { - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - Command mockCoreCommand = mock(Command.class); - when(mockCoreCommand.getName()).thenReturn("mv"); - - // Assert debug mode is off - assertEquals(0, inventories.getMVIConfig().getGlobalDebug()); - - // Send the debug command. - String[] cmdArgs = new String[]{"debug", "3"}; - inventories.onCommand(mockCommandSender, mockCoreCommand, "", cmdArgs); - - // remove world2 from default group - cmdArgs = new String[]{"rmworld", "world2", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - // Verify removal - assertFalse(inventories.getGroupManager().getDefaultGroup().getWorlds().contains("world2")); - cmdArgs = new String[]{"info", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertEquals(3, inventories.getMVIConfig().getGlobalDebug()); - - Player player = inventories.getServer().getPlayerExact("dumptruckman"); - - addToInventory(player.getInventory(), getFillerInv()); - String originalInventory = player.getInventory().toString(); - - changeWorld(player, "world", "world_nether"); - - String newInventory = player.getInventory().toString(); - assertEquals(originalInventory, newInventory); - - changeWorld(player, "world_nether", "world2"); - - assertNotSame(originalInventory, newInventory); - - cmdArgs = new String[]{"reload"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - changeWorld(player, "world2", "world"); - - newInventory = player.getInventory().toString(); - assertEquals(originalInventory, newInventory); - } - - @Test - public void testNegativeSharables() { - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - Command mockCoreCommand = mock(Command.class); - when(mockCoreCommand.getName()).thenReturn("mv"); - - // Assert debug mode is off - assertEquals(0, inventories.getMVIConfig().getGlobalDebug()); - - // Send the debug command. - String[] cmdArgs = new String[]{"debug", "3"}; - inventories.onCommand(mockCommandSender, mockCoreCommand, "", cmdArgs); - - // remove world2 from default group - cmdArgs = new String[]{"rmworld", "world2", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - // add inventory shares (inv and armor) to default group - cmdArgs = new String[]{"addshares", "-saturation", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - Shares shares = Sharables.allOf(); - shares.remove(Sharables.SATURATION); - assertTrue(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(shares)); - - // Reload to ensure things are saving to config.yml - cmdArgs = new String[]{"reload"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertTrue(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(shares)); - - // Verify removal - assertFalse(inventories.getGroupManager().getDefaultGroup().getWorlds().contains("world2")); - cmdArgs = new String[]{"info", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertEquals(3, inventories.getMVIConfig().getGlobalDebug()); - - Player player = inventories.getServer().getPlayerExact("dumptruckman"); - - float satTest = 0.349F; - player.setSaturation(satTest); - double hpTest = 13D; - player.setHealth(hpTest); - addToInventory(player.getInventory(), getFillerInv()); - String originalInventory = player.getInventory().toString(); - - changeWorld(player, "world", "world_nether"); - String newInventory = player.getInventory().toString(); - - // Inventory and health should be same, saturation different (from original values) - assertEquals(originalInventory, newInventory); - assertNotSame(satTest, player.getSaturation()); - assertEquals(hpTest, player.getHealth(), 0); - - changeWorld(player, "world_nether", "world2"); - newInventory = player.getInventory().toString(); - - // Inventory, health and saturation should be different (from original values) - assertNotSame(originalInventory, newInventory); - assertNotSame(satTest, player.getSaturation()); - assertNotSame(hpTest, player.getHealth()); - - cmdArgs = new String[]{"reload"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - changeWorld(player, "world2", "world"); - newInventory = player.getInventory().toString(); - - // Inventory, health and saturation should be same (from original values) - assertEquals(originalInventory, newInventory); - assertEquals(satTest, player.getSaturation(), 0); - assertEquals(hpTest, player.getHealth(), 0); - - changeWorld(player, "world", "world2"); - newInventory = player.getInventory().toString(); - - // Inventory, health and saturation should be different (from original values) - assertNotSame(originalInventory, newInventory); - assertNotSame(satTest, player.getSaturation()); - assertNotSame(hpTest, player.getHealth()); - - cmdArgs = new String[]{"reload"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - changeWorld(player, "world2", "world"); - - newInventory = player.getInventory().toString(); - assertEquals(originalInventory, newInventory); - } - - @Test - public void testGroupedSharesWorldChange() throws Exception { - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - Command mockCoreCommand = mock(Command.class); - when(mockCoreCommand.getName()).thenReturn("mv"); - - // Assert debug mode is off - assertEquals(0, inventories.getMVIConfig().getGlobalDebug()); - - // Send the debug command. - String[] cmdArgs = new String[]{"debug", "3"}; - inventories.onCommand(mockCommandSender, mockCoreCommand, "", cmdArgs); - - // remove world2 from default group - cmdArgs = new String[]{"rmworld", "world2", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - // remove all shares from default group - cmdArgs = new String[]{"rmshares", "all", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - // add inventory shares (inv and armor) to default group - cmdArgs = new String[]{"addshares", "inventory,saturation", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertFalse(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.all())); - assertTrue(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.INVENTORY)); - assertTrue(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.ARMOR)); - assertTrue(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.SATURATION)); - - // Reload to ensure things are saving to config.yml - cmdArgs = new String[]{"reload"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertFalse(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.all())); - assertTrue(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.INVENTORY)); - assertTrue(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.ARMOR)); - assertTrue(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.SATURATION)); - - // Verify removal - assertFalse(inventories.getGroupManager().getDefaultGroup().getWorlds().contains("world2")); - cmdArgs = new String[]{"info", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertEquals(3, inventories.getMVIConfig().getGlobalDebug()); - - Player player = inventories.getServer().getPlayerExact("dumptruckman"); - - float satTest = 0.349F; - player.setSaturation(satTest); - addToInventory(player.getInventory(), getFillerInv()); - String originalInventory = player.getInventory().toString(); - - // Changing to world within same group, nothing should change. - changeWorld(player, "world", "world_nether"); - - String newInventory = player.getInventory().toString(); - assertEquals(originalInventory, newInventory); - assertEquals(satTest, player.getSaturation(), 0); - - changeWorld(player, "world_nether", "world2"); - newInventory = player.getInventory().toString(); - - assertNotSame(originalInventory, newInventory); - assertNotSame(satTest, player.getSaturation()); - - cmdArgs = new String[]{"reload"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - changeWorld(player, "world2", "world"); - - newInventory = player.getInventory().toString(); - assertEquals(originalInventory, newInventory); - assertEquals(satTest, player.getSaturation(), 0); - - changeWorld(player, "world", "world2"); - originalInventory = player.getInventory().toString(); - - assertNotSame(originalInventory, newInventory); - assertNotSame(satTest, player.getSaturation()); - - FlatFileDataHelper dataHelper = new FlatFileDataHelper(inventories.getData()); - File playerFile = dataHelper.getPlayerFile(ContainerType.GROUP, "default", "dumptruckman"); - FileConfiguration playerConfig = JsonConfiguration.loadConfiguration(playerFile); - playerConfig.set("SURVIVAL." + DataStrings.PLAYER_INVENTORY_CONTENTS, null); - playerConfig.set("SURVIVAL." + DataStrings.PLAYER_ARMOR_CONTENTS, null); - try { - playerConfig.save(playerFile); - } catch (IOException e) { - e.printStackTrace(); - } - - cmdArgs = new String[]{"reload"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - changeWorld(player, "world2", "world"); - - newInventory = player.getInventory().toString(); - assertEquals(originalInventory, newInventory); - } - - @Test - public void testLastLocation() throws Exception { - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - Command mockCoreCommand = mock(Command.class); - when(mockCoreCommand.getName()).thenReturn("mv"); - - // Assert debug mode is off - assertEquals(0, inventories.getMVIConfig().getGlobalDebug()); - - // Send the debug command. - String[] cmdArgs = new String[]{"debug", "3"}; - inventories.onCommand(mockCommandSender, mockCoreCommand, "", cmdArgs); - - // remove world2 from default group - cmdArgs = new String[]{"rmworld", "world2", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - // remove all shares from default group - cmdArgs = new String[]{"rmshares", "all", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - // enable last_location share - cmdArgs = new String[]{"toggle", "last_location"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - // add last_location share to default group - cmdArgs = new String[]{"addshares", "last_location", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertFalse(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.all())); - assertTrue(inventories.getMVIConfig().getOptionalShares().isSharing(Sharables.LAST_LOCATION)); - assertTrue(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.LAST_LOCATION)); - - // Reload to ensure things are saving to config.yml - cmdArgs = new String[]{"reload"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertFalse(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.all())); - assertTrue(inventories.getMVIConfig().getOptionalShares().isSharing(Sharables.LAST_LOCATION)); - assertTrue(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.LAST_LOCATION)); - - // Verify removal - assertFalse(inventories.getGroupManager().getDefaultGroup().getWorlds().contains("world2")); - cmdArgs = new String[]{"info", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertEquals(3, inventories.getMVIConfig().getGlobalDebug()); - - Player player = inventories.getServer().getPlayerExact("dumptruckman"); - - // Move player within group and to a different location than spawn - changeWorld(player, "world", "world_nether"); - Location lastLocation = new Location(mockServer.getWorld("world_nether"), 10, 10, 10); - player.teleport(lastLocation); - - // Move player out of group - changeWorld(player, "world_nether", "world2"); - assertNotSame(lastLocation, player.getLocation()); - - // Move player back to group - changeWorld(player, "world2", "world_nether"); - assertEquals(lastLocation, player.getLocation()); - } - - @Test - public void testOptionalsForUngroupedWorlds() throws Exception { - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - Command mockCoreCommand = mock(Command.class); - when(mockCoreCommand.getName()).thenReturn("mv"); - - // Assert debug mode is off - assertEquals(0, inventories.getMVIConfig().getGlobalDebug()); - - // Assert UseOptionalsForUngroupedWorlds is set to true - assertTrue(inventories.getMVIConfig().usingOptionalsForUngrouped()); - - // Change UseOptionalsForUngroupedWorlds to false, then assert that it is false - inventories.getMVIConfig().setUsingOptionalsForUngrouped(false); - assertFalse(inventories.getMVIConfig().usingOptionalsForUngrouped()); - - // Send the debug command. - String[] cmdArgs = new String[]{"debug", "3"}; - inventories.onCommand(mockCommandSender, mockCoreCommand, "", cmdArgs); - - // remove world2 from default group - cmdArgs = new String[]{"rmworld", "world2", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - // remove all shares from default group - cmdArgs = new String[]{"rmshares", "all", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - // enable last_location share - cmdArgs = new String[]{"toggle", "last_location"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - // add last_location share to default group - cmdArgs = new String[]{"addshares", "last_location", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - // add inventory share to default group - cmdArgs = new String[]{"addshares", "inventory", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertFalse(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.all())); - assertTrue(inventories.getMVIConfig().getOptionalShares().isSharing(Sharables.LAST_LOCATION)); - assertTrue(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.LAST_LOCATION)); - - // Reload to ensure things are saving to config.yml - cmdArgs = new String[]{"reload"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertFalse(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.all())); - assertTrue(inventories.getMVIConfig().getOptionalShares().isSharing(Sharables.LAST_LOCATION)); - assertTrue(inventories.getGroupManager().getDefaultGroup().getShares().isSharing(Sharables.LAST_LOCATION)); - assertFalse(inventories.getMVIConfig().usingOptionalsForUngrouped()); - - // Verify removal - assertFalse(inventories.getGroupManager().getDefaultGroup().getWorlds().contains("world2")); - cmdArgs = new String[]{"info", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertEquals(3, inventories.getMVIConfig().getGlobalDebug()); - - Player player = inventories.getServer().getPlayerExact("dumptruckman"); - - // get inventories as strings - String emptyInventory = player.getInventory().toString(); - addToInventory(player.getInventory(), getFillerInv()); - String originalInventory = player.getInventory().toString(); - - // Move player within group and to a different location than spawn - changeWorld(player, "world", "world_nether"); - Location lastLocation = new Location(mockServer.getWorld("world_nether"), 10, 10, 10); - player.teleport(lastLocation); - - // make sure inventory is the same - assertEquals(originalInventory, player.getInventory().toString()); - - // Move player out of group - changeWorld(player, "world_nether", "world2"); - assertNotSame(lastLocation, player.getLocation()); - assertEquals(emptyInventory, player.getInventory().toString()); - - // Move player back to group - changeWorld(player, "world2", "world_nether"); - assertEquals(lastLocation, player.getLocation()); - assertEquals(originalInventory, player.getInventory().toString()); - - // Move player within group again - // Note: newLocation must match the location given made in changeWorld() - Location newLocation = new Location(mockServer.getWorld("world"), 0, 70, 0); - changeWorld(player, "world_nether", "world"); - assertEquals(originalInventory, player.getInventory().toString()); - // The following two assertions mean the same thing (they're redundant) - // but they help in understanding what is being tested. - assertNotSame(lastLocation, player.getLocation()); - assertEquals(newLocation, player.getLocation()); - - } - - @Test - public void testGroupingConflictChecker() { - - // Initialize a fake command - Command mockCommand = mock(Command.class); - when(mockCommand.getName()).thenReturn("mvinv"); - - // remove world2 from default group - String[] cmdArgs = new String[]{"rmworld", "world2", "default"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - WorldGroup group = inventories.getGroupManager().newEmptyGroup("test"); - group.addWorld("world"); - group.addWorld("world_nether"); - group.addWorld("world_the_end"); - group.addWorld("world2"); - group.getShares().setSharing(Sharables.allOf(), true); - inventories.getGroupManager().updateGroup(group); - cmdArgs = new String[]{"reload"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - assertTrue(inventories.getGroupManager().checkGroups().isEmpty()); - - cmdArgs = new String[]{"rmworld", "world_the_end", "test"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - cmdArgs = new String[]{"reload"}; - inventories.onCommand(mockCommandSender, mockCommand, "", cmdArgs); - - assertFalse(inventories.getGroupManager().checkGroups().isEmpty()); - - } -} diff --git a/src/test/java/com/onarandombox/multiverseinventories/util/MVTestLogFormatter.java b/src/test/java/com/onarandombox/multiverseinventories/util/MVTestLogFormatter.java deleted file mode 100644 index b568f14d..00000000 --- a/src/test/java/com/onarandombox/multiverseinventories/util/MVTestLogFormatter.java +++ /dev/null @@ -1,42 +0,0 @@ -/****************************************************************************** - * Multiverse 2 Copyright (c) the Multiverse Team 2011. * - * Multiverse 2 is licensed under the BSD License. * - * For more information please check the README.md file included * - * with this project. * - ******************************************************************************/ - -package com.onarandombox.multiverseinventories.util; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.logging.Formatter; -import java.util.logging.LogRecord; - -/** - * Formatter to format log-messages in tests - * - */ -public class MVTestLogFormatter extends Formatter { - private static final DateFormat df = new SimpleDateFormat("HH:mm:ss"); - - public String format(LogRecord record) { - StringBuilder ret = new StringBuilder(); - - ret.append("[").append(df.format(record.getMillis())).append("] [") - //.append(record.getLoggerName()).append("] [") - .append(record.getLevel().getLocalizedName()).append("] "); - ret.append(record.getMessage()); - ret.append('\n'); - - if (record.getThrown() != null) { - // An Exception was thrown! Let's print the StackTrace! - StringWriter writer = new StringWriter(); - record.getThrown().printStackTrace(new PrintWriter(writer)); - ret.append(writer); - } - - return ret.toString(); - } -} diff --git a/src/test/java/com/onarandombox/multiverseinventories/util/MockItemMeta.java b/src/test/java/com/onarandombox/multiverseinventories/util/MockItemMeta.java deleted file mode 100644 index b8e0b3ab..00000000 --- a/src/test/java/com/onarandombox/multiverseinventories/util/MockItemMeta.java +++ /dev/null @@ -1,94 +0,0 @@ -/****************************************************************************** - * Multiverse 2 Copyright (c) the Multiverse Team 2019. * - * Multiverse 2 is licensed under the BSD License. * - * For more information please check the README.md file included * - * with this project. * - ******************************************************************************/ -package com.onarandombox.multiverseinventories.util; - -import org.bukkit.Material; -import org.bukkit.inventory.ItemFactory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.BookMeta; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.inventory.meta.LeatherArmorMeta; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class MockItemMeta { - - private static final Map itemMetaData = new HashMap<>(); - - public static ItemFactory mockItemFactory() { - ItemFactory itemFactory = mock(ItemFactory.class); - - when(itemFactory.equals(any(), any())).thenReturn(true); - - doAnswer(invocation -> { - Material material = invocation.getArgument(0); - switch (material) { - case WRITTEN_BOOK: - return mockItemMeta(material, BookMeta.class); - case LEATHER_BOOTS: - return mockItemMeta(material, LeatherArmorMeta.class); - default: - return mockItemMeta(material, ItemMeta.class); - } - - }).when(itemFactory).getItemMeta(any(Material.class)); - - doReturn(true).when(itemFactory).isApplicable(any(ItemMeta.class), any(ItemStack.class)); - doReturn(true).when(itemFactory).isApplicable(any(ItemMeta.class), any(Material.class)); - - doAnswer(invocation -> invocation.getArgument(0)).when(itemFactory).asMetaFor(any(ItemMeta.class), any(ItemStack.class)); - doAnswer(invocation -> invocation.getArgument(0)).when(itemFactory).asMetaFor(any(ItemMeta.class), any(Material.class)); - - doAnswer(invocation -> invocation.getArgument(1)).when(itemFactory).updateMaterial(any(ItemMeta.class), any(Material.class)); - - return itemFactory; - } - - private static T mockItemMeta(Material type, Class itemMetaClass) { - Map data = new HashMap<>(); - - T itemMeta = mock(itemMetaClass, invocation -> { - String methodName = invocation.getMethod().getName(); - if (methodName.startsWith("set")) { - if (invocation.getArguments().length > 1) { - data.put(methodName.substring(3), Arrays.asList(invocation.getArguments())); - } else { - data.put(methodName.substring(3), invocation.getArguments()[0]); - } - } else if (methodName.startsWith("get")) { - return data.get(methodName.substring(3)); - } - - return null; - }); - - when(itemMeta.toString()).thenAnswer(i -> data.toString()); - - when(itemMeta.serialize()).thenAnswer(i -> data); - - when(itemMeta.clone()).thenReturn(itemMeta); - - MockItemMeta mockItemMeta = new MockItemMeta(type); - itemMetaData.put(itemMeta, mockItemMeta); - - return itemMeta; - } - - private final Material type; - - private MockItemMeta(Material type) { - this.type = type; - } -} diff --git a/src/test/java/com/onarandombox/multiverseinventories/util/MockPlayerFactory.java b/src/test/java/com/onarandombox/multiverseinventories/util/MockPlayerFactory.java deleted file mode 100644 index cd838ffe..00000000 --- a/src/test/java/com/onarandombox/multiverseinventories/util/MockPlayerFactory.java +++ /dev/null @@ -1,312 +0,0 @@ -/****************************************************************************** - * Multiverse 2 Copyright (c) the Multiverse Team 2020. * - * Multiverse 2 is licensed under the BSD License. * - * For more information please check the README.md file included * - * with this project. * - ******************************************************************************/ - -package com.onarandombox.multiverseinventories.util; - -import com.onarandombox.multiverseinventories.PlayerStats; -import org.bukkit.Location; -import org.bukkit.Server; -import org.bukkit.entity.Player; -import org.bukkit.event.player.PlayerTeleportEvent; -import org.bukkit.inventory.PlayerInventory; -import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyBoolean; -import static org.mockito.Mockito.anyDouble; -import static org.mockito.Mockito.anyFloat; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class MockPlayerFactory { - - private static final Map createdPlayers = new HashMap<>(); - private static final Map playerUIDs = new HashMap<>(); - - public static Player makeMockPlayer(String name, Server server) { - Player player = new MockPlayerFactory(name, UUID.randomUUID(), server).getMockPlayer(); - registerPlayer(player); - return player; - } - - public static Player makeMockPlayer(UUID uuid, Server server) { - Player player = new MockPlayerFactory(uuid.toString(), uuid, server).getMockPlayer(); - registerPlayer(player); - return player; - } - - public static Player getMockPlayer(String name) { - return createdPlayers.get(name); - } - - public static Player getOrCreateMockPlayer(String name, Server server) { - Player player = getMockPlayer(name); - if (player == null) { - player = makeMockPlayer(name, server); - } - return player; - } - - public static Player getMockPlayer(UUID uuid) { - return playerUIDs.get(uuid); - } - - public static Player getOrCreateMockPlayer(UUID uuid, Server server) { - Player player = getMockPlayer(uuid); - if (player == null) { - player = makeMockPlayer(uuid, server); - } - return player; - } - - public static void clearAllPlayers() { - createdPlayers.clear(); - playerUIDs.clear(); - } - - public static Collection getAllPlayers() { - return createdPlayers.values(); - } - - public static Player changeName(Player player, String newName) { - createdPlayers.remove(player.getName()); - when(player.getName()).thenReturn(newName); - - registerPlayer(player); - return player; - } - - private static void registerPlayer(Player player) { - createdPlayers.put(player.getName(), player); - playerUIDs.put(player.getUniqueId(), player); - } - - private final Player player; - private final PlayerData data = new PlayerData(); - - private MockPlayerFactory(String name, UUID uuid, Server server) { - player = mock(Player.class); - mockName(name); - mockUid(uuid); - mockServer(server); - mockPlayerData(); - } - - private Player getMockPlayer() { - return player; - } - - private void mockName(String name) { - when(player.getName()).thenReturn(name); - when(player.getDisplayName()).thenReturn(name); - when(player.getPlayerListName()).thenReturn(name); - } - - private void mockUid(UUID uuid) { - when(player.getUniqueId()).thenReturn(uuid); - } - - private void mockServer(Server server) { - when(player.getServer()).thenReturn(server); - } - - private void mockPlayerData() { - mockCompassTarget(); - mockExp(); - mockLevel(); - mockTotalExperience(); - mockExhaustion(); - mockSaturation(); - mockFoodLevel(); - mockBedSpawnLocation(); - mockInventory(); - mockEnderChest(); - mockHealth(); - mockMaximumNoDamageTicks(); - mockLastDamage(); - mockMaximumAir(); - mockPotionEffects(); - mockLocation(); - mockTeleport(); - } - - private void mockCompassTarget() { - when(player.getCompassTarget()).thenAnswer(i -> data.compassTarget); - doAnswer(invocation -> { - data.compassTarget = invocation.getArgument(0); - return null; - }).when(player).setCompassTarget(any(Location.class)); - } - - private void mockExp() { - when(player.getExp()).thenAnswer(i -> data.exp); - doAnswer(invocation -> { - data.exp = invocation.getArgument(0); - return null; - }).when(player).setExp(anyFloat()); - } - - private void mockLevel() { - when(player.getLevel()).thenAnswer(i -> data.level); - doAnswer(invocation -> { - data.level = invocation.getArgument(0); - return null; - }).when(player).setLevel(anyInt()); - } - - private void mockTotalExperience() { - when(player.getTotalExperience()).thenAnswer(i -> data.totalExperience); - doAnswer(invocation -> { - data.totalExperience = invocation.getArgument(0); - return null; - }).when(player).setTotalExperience(anyInt()); - } - - private void mockExhaustion() { - when(player.getExhaustion()).thenAnswer(i -> data.exhaustion); - doAnswer(invocation -> { - data.exhaustion = invocation.getArgument(0); - return null; - }).when(player).setExhaustion(anyFloat()); - } - - private void mockSaturation() { - when(player.getSaturation()).thenAnswer(i -> data.saturation); - doAnswer(invocation -> { - data.saturation = invocation.getArgument(0); - return null; - }).when(player).setSaturation(anyFloat()); - } - - private void mockFoodLevel() { - when(player.getFoodLevel()).thenAnswer(i -> data.foodLevel); - doAnswer(invocation -> { - data.foodLevel = invocation.getArgument(0); - return null; - }).when(player).setFoodLevel(anyInt()); - } - - private void mockBedSpawnLocation() { - when(player.getBedSpawnLocation()).thenAnswer(i -> data.bedSpawnLocation); - doAnswer(invocation -> { - data.bedSpawnLocation = invocation.getArgument(0); - return null; - }).when(player).setBedSpawnLocation(any(Location.class)); - doAnswer(invocation -> { - data.bedSpawnLocation = invocation.getArgument(0); - return null; - }).when(player).setBedSpawnLocation(any(Location.class), anyBoolean()); - } - - private void mockInventory() { - when(player.getInventory()).thenReturn(data.inventory); - } - - private void mockEnderChest() { - when(player.getEnderChest()).thenReturn(data.enderChest); - } - - private void mockHealth() { - when(player.getHealth()).thenAnswer(i -> data.health); - doAnswer(invocation -> { - data.health = invocation.getArgument(0); - return null; - }).when(player).setHealth(anyDouble()); - } - - private void mockMaximumNoDamageTicks() { - when(player.getMaximumNoDamageTicks()).thenAnswer(i -> data.maximumNoDamageTicks); - doAnswer(invocation -> { - data.maximumNoDamageTicks = invocation.getArgument(0); - return null; - }).when(player).setMaximumNoDamageTicks(anyInt()); - } - - private void mockLastDamage() { - when(player.getLastDamage()).thenAnswer(i -> data.lastDamage); - doAnswer(invocation -> { - data.lastDamage = invocation.getArgument(0); - return null; - }).when(player).setLastDamage(anyDouble()); - } - - private void mockMaximumAir() { - when(player.getMaximumAir()).thenAnswer(i -> data.maximumAir); - doAnswer(invocation -> { - data.maximumAir = invocation.getArgument(0); - return null; - }).when(player).setMaximumAir(anyInt()); - } - - private void mockPotionEffects() { - when(player.getActivePotionEffects()).thenAnswer(i -> new ArrayList<>(data.potionEffects.values())); - - doAnswer(invocation -> { - PotionEffect effect = invocation.getArgument(0); - data.potionEffects.put(effect.getType().getId(), effect); - return true; - }).when(player).addPotionEffect(any(PotionEffect.class)); - - doAnswer(invocation -> { - PotionEffectType type = invocation.getArgument(0); - data.potionEffects.remove(type.getId()); - return null; - }).when(player).removePotionEffect(any(PotionEffectType.class)); - } - - private void mockLocation() { - when(player.getLocation()).thenAnswer(i -> data.location); - when(player.getWorld()).thenAnswer(i -> data.location.getWorld()); - } - - private void mockTeleport() { - doAnswer(invocation -> { - data.location = invocation.getArgument(0); - return true; - }).when(player).teleport(any(Location.class)); - - doAnswer(invocation -> { - data.location = invocation.getArgument(0); - return true; - }).when(player).teleport(any(Location.class), any(PlayerTeleportEvent.TeleportCause.class)); - } - - private static class PlayerData { - Location compassTarget = null; - - float exp = PlayerStats.EXPERIENCE; - int level = PlayerStats.LEVEL; - int totalExperience = PlayerStats.TOTAL_EXPERIENCE; - float exhaustion = PlayerStats.EXHAUSTION; - float saturation = PlayerStats.SATURATION; - int foodLevel = PlayerStats.FOOD_LEVEL; - - Location bedSpawnLocation = null; - - PlayerInventory inventory = new MockPlayerInventory(); - PlayerInventory enderChest = new MockPlayerInventory(); - - double health = PlayerStats.HEALTH; - int maximumNoDamageTicks = 0; - double lastDamage = 0D; - - int maximumAir = 20; - - Map potionEffects = new HashMap<>(); - - Location location = new Location(MockWorldFactory.getWorld("world"), 0, 70, 0); - } -} diff --git a/src/test/java/com/onarandombox/multiverseinventories/util/MockPlayerInventory.java b/src/test/java/com/onarandombox/multiverseinventories/util/MockPlayerInventory.java deleted file mode 100644 index e178feab..00000000 --- a/src/test/java/com/onarandombox/multiverseinventories/util/MockPlayerInventory.java +++ /dev/null @@ -1,328 +0,0 @@ -package com.onarandombox.multiverseinventories.util; - -import com.onarandombox.multiverseinventories.PlayerStats; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.entity.HumanEntity; -import org.bukkit.event.inventory.InventoryType; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.Spliterator; -import java.util.function.Consumer; - -public class MockPlayerInventory implements PlayerInventory { - - ItemStack[] armorContents = new ItemStack[PlayerStats.ARMOR_SIZE]; - ItemStack[] inventoryContents = new ItemStack[PlayerStats.INVENTORY_SIZE]; - - @Override - public ItemStack[] getArmorContents() { - return armorContents; - } - - @Override - public ItemStack getHelmet() { - return armorContents[0]; - } - - @Override - public ItemStack getChestplate() { - return armorContents[1]; - } - - @Override - public ItemStack getLeggings() { - return armorContents[2]; - } - - @Override - public ItemStack getBoots() { - return armorContents[3]; - } - - @Override - public void setArmorContents(ItemStack[] itemStacks) { - this.armorContents = itemStacks; - } - - @Override - public void setHelmet(ItemStack itemStack) { - this.armorContents[0] = itemStack; - } - - @Override - public void setChestplate(ItemStack itemStack) { - this.armorContents[1] = itemStack; - } - - @Override - public void setLeggings(ItemStack itemStack) { - this.armorContents[2] = itemStack; - } - - @Override - public void setBoots(ItemStack itemStack) { - this.armorContents[3] = itemStack; - } - - @Override - public void setHeldItemSlot(int i) { - - } - - @Override - public ItemStack getItemInHand() { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void setItemInHand(ItemStack itemStack) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public int getHeldItemSlot() { - return 0; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public HumanEntity getHolder() { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public int getSize() { - return inventoryContents.length + armorContents.length; - } - - public String getName() { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public ItemStack getItem(int i) { - if (i >= 0 && i < PlayerStats.INVENTORY_SIZE) { - return inventoryContents[i]; - } else if (i >= PlayerStats.INVENTORY_SIZE && i < PlayerStats.INVENTORY_SIZE + PlayerStats.ARMOR_SIZE) { - return armorContents[i - PlayerStats.INVENTORY_SIZE]; - } else { - throw new ArrayIndexOutOfBoundsException(); - } - } - - @Override - public void setItem(int i, ItemStack itemStack) { - if (i >= 0 && i < PlayerStats.INVENTORY_SIZE) { - inventoryContents[i] = itemStack; - } else if (i >= PlayerStats.INVENTORY_SIZE && i < PlayerStats.INVENTORY_SIZE + PlayerStats.ARMOR_SIZE) { - armorContents[i - PlayerStats.INVENTORY_SIZE] = itemStack; - } else { - throw new ArrayIndexOutOfBoundsException(); - } - } - - @Override - public HashMap addItem(ItemStack... itemStacks) { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public HashMap removeItem(ItemStack... itemStacks) { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public ItemStack[] getContents() { - return this.inventoryContents; - } - - @Override - public void setContents(ItemStack[] itemStacks) { - this.inventoryContents = itemStacks; - } - - @Override - public boolean contains(Material material) { - return false; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public boolean contains(ItemStack itemStack) { - return false; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public boolean contains(Material material, int i) { - return false; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public boolean contains(ItemStack itemStack, int i) { - return false; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public HashMap all(Material material) { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public HashMap all(ItemStack itemStack) { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public int first(Material material) { - return 0; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public int first(ItemStack itemStack) { - return 0; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public int firstEmpty() { - return 0; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void remove(Material material) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void remove(ItemStack itemStack) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void clear(int i) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void clear() { - Arrays.fill(armorContents, null); - Arrays.fill(inventoryContents, null); - } - - @Override - public List getViewers() { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - public String getTitle() { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public InventoryType getType() { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public ListIterator iterator() { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public int getMaxStackSize() { - return 0; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public void setMaxStackSize(int i) { - //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public ListIterator iterator(int i) { - return null; //To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public boolean containsAtLeast(final ItemStack itemStack, final int i) { - return false; //To change body of implemented methods use File | Settings | File Templates. - } - - private static Map makeMap(ItemStack[] items) { - Map contents = new LinkedHashMap(items.length); - for (int i = 0; i < items.length; i++) { - if (items[i] != null && items[i].getType() != Material.AIR) { - contents.put(Integer.valueOf(i).toString(), items[i]); - } - } - return contents; - } - - public String toString() { - return "{\"inventoryContents\":" + makeMap(getContents()) - + ",\"armorContents\":" + makeMap(getArmorContents()) - + "}"; - } - - // TODO update these once rest of MV-Inv is compatible. - - @Override - public ItemStack[] getExtraContents() { - return new ItemStack[0]; - } - - @Override - public void setExtraContents(ItemStack[] itemStacks) { - - } - - @Override - public ItemStack getItemInMainHand() { - return null; - } - - @Override - public void setItemInMainHand(ItemStack itemStack) { - - } - - @Override - public ItemStack getItemInOffHand() { - return null; - } - - @Override - public void setItemInOffHand(ItemStack itemStack) { - - } - - @Override - public ItemStack[] getStorageContents() { - return new ItemStack[0]; - } - - @Override - public void setStorageContents(ItemStack[] itemStacks) throws IllegalArgumentException { - - } - - @Override - public Location getLocation() { - return null; - } - - @Override - public void forEach(Consumer action) { - - } - - @Override - public Spliterator spliterator() { - return null; - } -} diff --git a/src/test/java/com/onarandombox/multiverseinventories/util/MockWorldFactory.java b/src/test/java/com/onarandombox/multiverseinventories/util/MockWorldFactory.java deleted file mode 100644 index 3dee7cbb..00000000 --- a/src/test/java/com/onarandombox/multiverseinventories/util/MockWorldFactory.java +++ /dev/null @@ -1,163 +0,0 @@ -/****************************************************************************** - * Multiverse 2 Copyright (c) the Multiverse Team 2011. * - * Multiverse 2 is licensed under the BSD License. * - * For more information please check the README.md file included * - * with this project. * - ******************************************************************************/ - -package com.onarandombox.multiverseinventories.util; - -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.World; -import org.bukkit.WorldType; -import org.bukkit.block.Block; -import org.bukkit.generator.ChunkGenerator; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class MockWorldFactory { - - private static final Map worldUIDS = new HashMap(); - private static final Map createdWorlds = new LinkedHashMap(); - - private MockWorldFactory() { - } - - private static void registerWorld(World world) { - worldUIDS.put(world.getUID(), world); - createdWorlds.put(world.getName(), world); - } - - private static World basics(String world, World.Environment env, WorldType type) { - World mockWorld = mock(World.class); - when(mockWorld.getName()).thenReturn(world); - when(mockWorld.getEnvironment()).thenReturn(env); - when(mockWorld.getWorldType()).thenReturn(type); - when(mockWorld.getSpawnLocation()).thenReturn(new Location(mockWorld, 0, 64, 0)); - when(mockWorld.getWorldFolder()).thenAnswer(new Answer() { - public File answer(InvocationOnMock invocation) throws Throwable { - if (!(invocation.getMock() instanceof World)) - return null; - - World thiss = (World) invocation.getMock(); - return new File(TestInstanceCreator.serverDirectory, thiss.getName()); - } - }); - when(mockWorld.getBlockAt(any(Location.class))).thenAnswer(new Answer() { - public Block answer(InvocationOnMock invocation) throws Throwable { - Location loc; - try { - loc = (Location) invocation.getArguments()[0]; - } catch (Exception e) { - return null; - } - Material blockType = Material.AIR; - Block mockBlock = mock(Block.class); - if (loc.getBlockY() < 64) { - blockType = Material.DIRT; - } - - when(mockBlock.getType()).thenReturn(blockType); - when(mockBlock.getWorld()).thenReturn(loc.getWorld()); - when(mockBlock.getX()).thenReturn(loc.getBlockX()); - when(mockBlock.getY()).thenReturn(loc.getBlockY()); - when(mockBlock.getZ()).thenReturn(loc.getBlockZ()); - when(mockBlock.getLocation()).thenReturn(loc); - when(mockBlock.isEmpty()).thenReturn(blockType == Material.AIR); - return mockBlock; - } - }); - when(mockWorld.getUID()).thenReturn(UUID.randomUUID()); - return mockWorld; - } - - private static World nullWorld(String world, World.Environment env, WorldType type) { - World mockWorld = mock(World.class); - when(mockWorld.getName()).thenReturn(world); - when(mockWorld.getEnvironment()).thenReturn(env); - when(mockWorld.getWorldType()).thenReturn(type); - when(mockWorld.getSpawnLocation()).thenReturn(new Location(mockWorld, 0, 64, 0)); - when(mockWorld.getWorldFolder()).thenAnswer(new Answer() { - public File answer(InvocationOnMock invocation) throws Throwable { - if (!(invocation.getMock() instanceof World)) - return null; - - World thiss = (World) invocation.getMock(); - return new File(TestInstanceCreator.serverDirectory, thiss.getName()); - } - }); - when(mockWorld.getBlockAt(any(Location.class))).thenAnswer(new Answer() { - public Block answer(InvocationOnMock invocation) throws Throwable { - Location loc; - try { - loc = (Location) invocation.getArguments()[0]; - } catch (Exception e) { - return null; - } - - Block mockBlock = mock(Block.class); - Material blockType = Material.AIR; - - when(mockBlock.getType()).thenReturn(blockType); - when(mockBlock.getWorld()).thenReturn(loc.getWorld()); - when(mockBlock.getX()).thenReturn(loc.getBlockX()); - when(mockBlock.getY()).thenReturn(loc.getBlockY()); - when(mockBlock.getZ()).thenReturn(loc.getBlockZ()); - when(mockBlock.getLocation()).thenReturn(loc); - when(mockBlock.isEmpty()).thenReturn(blockType == Material.AIR); - return mockBlock; - } - }); - return mockWorld; - } - - public static World makeNewMockWorld(String world, World.Environment env, WorldType type) { - World w = basics(world, env, type); - registerWorld(w); - return w; - } - - public static World makeNewNullMockWorld(String world, World.Environment env, WorldType type) { - World w = nullWorld(world, env, type); - registerWorld(w); - return w; - } - - public static World makeNewMockWorld(String world, World.Environment env, WorldType type, long seed, - ChunkGenerator generator) { - World mockWorld = basics(world, env, type); - when(mockWorld.getGenerator()).thenReturn(generator); - when(mockWorld.getSeed()).thenReturn(seed); - registerWorld(mockWorld); - return mockWorld; - } - - public static World getWorld(String name) { - return createdWorlds.get(name); - } - - public static World getWorld(UUID worldUID) { - return worldUIDS.get(worldUID); - } - - public static List getWorlds() { - return new ArrayList(createdWorlds.values()); - } - - public static void clearWorlds() { - createdWorlds.clear(); - } -} diff --git a/src/test/java/com/onarandombox/multiverseinventories/util/TestInstanceCreator.java b/src/test/java/com/onarandombox/multiverseinventories/util/TestInstanceCreator.java deleted file mode 100644 index 82fb2bc5..00000000 --- a/src/test/java/com/onarandombox/multiverseinventories/util/TestInstanceCreator.java +++ /dev/null @@ -1,412 +0,0 @@ -/****************************************************************************** - * Multiverse 2 Copyright (c) the Multiverse Team 2011. * - * Multiverse 2 is licensed under the BSD License. * - * For more information please check the README.md file included * - * with this project. * - ******************************************************************************/ - -package com.onarandombox.multiverseinventories.util; - -import com.onarandombox.MultiverseCore.MultiverseCore; -import com.onarandombox.MultiverseCore.listeners.MVEntityListener; -import com.onarandombox.MultiverseCore.listeners.MVPlayerListener; -import com.onarandombox.MultiverseCore.listeners.MVWeatherListener; -import com.onarandombox.MultiverseCore.utils.FileUtils; -import com.onarandombox.MultiverseCore.utils.TestingMode; -import com.onarandombox.MultiverseCore.utils.WorldManager; -import com.onarandombox.multiverseinventories.InventoriesListener; -import com.onarandombox.multiverseinventories.MultiverseInventories; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Material; -import org.bukkit.OfflinePlayer; -import org.bukkit.Server; -import org.bukkit.UnsafeValues; -import org.bukkit.World; -import org.bukkit.WorldCreator; -import org.bukkit.WorldType; -import org.bukkit.World.Environment; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemFactory; -import org.bukkit.permissions.Permission; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.PluginDescriptionFile; -import org.bukkit.plugin.PluginManager; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.plugin.java.JavaPluginLoader; -import org.bukkit.potion.PotionEffectType; -import org.bukkit.scheduler.BukkitScheduler; -import org.mockito.internal.util.reflection.ReflectionMemberAccessor; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -import java.io.File; -import java.lang.reflect.Field; -import java.util.*; -import java.util.logging.Level; -import java.util.logging.Logger; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyBoolean; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.anyLong; -import static org.mockito.Mockito.anyString; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.isA; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; - -public class TestInstanceCreator { - private MultiverseInventories plugin; - private MultiverseCore core; - private Server mockServer; - private CommandSender commandSender; - - public static final File invDirectory = new File("bin/test/server/plugins/inventories-test"); - public static final File coreDirectory = new File("bin/test/server/plugins/core-test"); - public static final File serverDirectory = new File("bin/test/server"); - public static final File worldsDirectory = new File("bin/test/server"); - - public boolean setUp() { - TestingMode.enable(); - try { - FileUtils.deleteFolder(invDirectory); - FileUtils.deleteFolder(serverDirectory); - assertFalse(invDirectory.exists()); - invDirectory.mkdirs(); - assertTrue(invDirectory.exists()); - - // Initialize the Mock server. - mockServer = mock(Server.class); - when(mockServer.getName()).thenReturn("TestBukkit"); - when(mockServer.getVersion()).thenReturn("git-TestBukkit-0 (MC: 1.18.1)"); - Logger.getLogger("Minecraft").setParent(Util.logger); - when(mockServer.getLogger()).thenReturn(Util.logger); - when(mockServer.getWorldContainer()).thenReturn(worldsDirectory); - - // Initialize the Mock Plugin Loader. - JavaPluginLoader mockPluginLoader = mock(JavaPluginLoader.class); - new ReflectionMemberAccessor().set(JavaPluginLoader.class.getDeclaredField("server"), mockPluginLoader, mockServer); - - // Return a fake PDF file. - PluginDescriptionFile pdf = spy(new PluginDescriptionFile("Multiverse-Inventories", "2.4-test", - "com.onarandombox.multiverseinventories.MultiverseInventories")); - when(pdf.getAuthors()).thenReturn(new ArrayList()); - plugin = spy(new MultiverseInventories(mockPluginLoader, pdf, invDirectory, new File(invDirectory, "testPluginFile"))); - doReturn(pdf).when(plugin).getDescription(); - doReturn(true).when(plugin).isEnabled(); - PluginDescriptionFile pdfCore = spy(new PluginDescriptionFile("Multiverse-Core", "2.2-Test", - "com.onarandombox.MultiverseCore.MultiverseCore")); - when(pdfCore.getAuthors()).thenReturn(new ArrayList()); - core = spy(new MultiverseCore(mockPluginLoader, pdf, coreDirectory, new File(coreDirectory, "testPluginFile"))); - doReturn(pdfCore).when(core).getDescription(); - doReturn(true).when(core).isEnabled(); - doReturn(Util.logger).when(core).getLogger(); - plugin.setServerFolder(serverDirectory); - doReturn(core).when(plugin).getCore(); - - // Let's let all MV files go to bin/test - doReturn(invDirectory).when(plugin).getDataFolder(); - // Let's let all MV files go to bin/test - doReturn(coreDirectory).when(core).getDataFolder(); - - // Add Core to the list of loaded plugins - JavaPlugin[] plugins = new JavaPlugin[]{plugin, core}; - - // Mock the Plugin Manager - PluginManager mockPluginManager = mock(PluginManager.class); - when(mockPluginManager.getPlugins()).thenReturn(plugins); - when(mockPluginManager.getPlugin("Multiverse-Inventories")).thenReturn(plugin); - when(mockPluginManager.getPlugin("Multiverse-Core")).thenReturn(core); - when(mockPluginManager.getPermission(anyString())).thenReturn(null); - - // Make some fake folders to fool the fake MV into thinking these worlds exist - File worldNormalFile = new File(plugin.getServerFolder(), "world"); - Util.log("Creating world-folder: " + worldNormalFile.getAbsolutePath()); - worldNormalFile.mkdirs(); - MockWorldFactory.makeNewMockWorld("world", Environment.NORMAL, WorldType.NORMAL); - File worldNetherFile = new File(plugin.getServerFolder(), "world_nether"); - Util.log("Creating world-folder: " + worldNetherFile.getAbsolutePath()); - worldNetherFile.mkdirs(); - MockWorldFactory.makeNewMockWorld("world_nether", Environment.NETHER, WorldType.NORMAL); - File worldSkylandsFile = new File(plugin.getServerFolder(), "world_the_end"); - Util.log("Creating world-folder: " + worldSkylandsFile.getAbsolutePath()); - worldSkylandsFile.mkdirs(); - MockWorldFactory.makeNewMockWorld("world_the_end", Environment.THE_END, WorldType.NORMAL); - File world2File = new File(plugin.getServerFolder(), "world2"); - Util.log("Creating world-folder: " + world2File.getAbsolutePath()); - world2File.mkdirs(); - MockWorldFactory.makeNewMockWorld("world2", Environment.NORMAL, WorldType.NORMAL); - - // Finish initializing. - when(plugin.getServer()).thenReturn(mockServer); - when(core.getServer()).thenReturn(mockServer); - when(mockServer.getPluginManager()).thenReturn(mockPluginManager); - Answer playerAnswer = invocationOnMock -> { - String name = invocationOnMock.getArgument(0); - if (name == null) return null; - return MockPlayerFactory.getOrCreateMockPlayer(name, mockServer); - }; - when(mockServer.getPlayerExact(anyString())).thenAnswer(playerAnswer); - when(mockServer.getOfflinePlayer(anyString())).thenAnswer(playerAnswer); - when(mockServer.getOfflinePlayers()).thenAnswer( - (Answer) invocation -> MockPlayerFactory.getAllPlayers().toArray(new Player[0])); - when(mockServer.getOnlinePlayers()).thenAnswer( - (Answer>) invocation -> MockPlayerFactory.getAllPlayers()); - Answer uuidPlayerAnswer = invocationOnMock -> { - UUID uuid = invocationOnMock.getArgument(0); - if (uuid == null) return null; - return MockPlayerFactory.getOrCreateMockPlayer(uuid, mockServer); - }; - doAnswer(uuidPlayerAnswer).when(mockServer).getPlayer(any(UUID.class)); - doAnswer(uuidPlayerAnswer).when(mockServer).getOfflinePlayer(any(UUID.class)); - - try { - PotionEffectType.registerPotionEffectType(mockPotionEffectType(1, "SPEED")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(2, "SLOW")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(3, "FAST_DIGGING")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(4, "SLOW_DIGGING")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(5, "INCREASE_DAMAGE")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(6, "HEAL")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(7, "HARM")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(8, "JUMP")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(9, "CONFUSION")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(10, "REGENERATION")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(11, "DAMAGE_RESISTANCE")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(12, "FIRE_RESISTANCE")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(13, "WATER_BREATHING")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(14, "INVISIBILITY")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(15, "BLINDNESS")); - PotionEffectType.registerPotionEffectType(mockPotionEffectType(16, "NIGHT_VISION")); - } catch (IllegalArgumentException ignore) { - // Already registered in this context. - } - - // Give the server some worlds - when(mockServer.getWorld(anyString())).thenAnswer(new Answer() { - public World answer(InvocationOnMock invocation) throws Throwable { - String arg; - try { - arg = (String) invocation.getArguments()[0]; - } catch (Exception e) { - return null; - } - return MockWorldFactory.getWorld(arg); - } - }); - - when(mockServer.getWorld(any(UUID.class))).thenAnswer(new Answer() { - @Override - public World answer(InvocationOnMock invocation) throws Throwable { - UUID arg; - try { - arg = (UUID) invocation.getArguments()[0]; - } catch (Exception e) { - return null; - } - return MockWorldFactory.getWorld(arg); - } - }); - - when(mockServer.getWorlds()).thenAnswer(new Answer>() { - public List answer(InvocationOnMock invocation) throws Throwable { - return MockWorldFactory.getWorlds(); - } - }); - - - - when(mockServer.createWorld(isA(WorldCreator.class))).thenAnswer( - new Answer() { - public World answer(InvocationOnMock invocation) throws Throwable { - WorldCreator arg; - try { - arg = (WorldCreator) invocation.getArguments()[0]; - } catch (Exception e) { - return null; - } - // Add special case for creating null worlds. - // Not sure I like doing it this way, but this is a special case - if (arg.name().equalsIgnoreCase("nullworld")) { - return MockWorldFactory.makeNewNullMockWorld(arg.name(), arg.environment(), arg.type()); - } - return MockWorldFactory.makeNewMockWorld(arg.name(), arg.environment(), arg.type()); - } - }); - - when(mockServer.unloadWorld(anyString(), anyBoolean())).thenReturn(true); - - // add mock scheduler - BukkitScheduler mockScheduler = mock(BukkitScheduler.class); - when(mockScheduler.scheduleSyncDelayedTask(any(Plugin.class), any(Runnable.class), anyLong())). - thenAnswer(new Answer() { - public Integer answer(InvocationOnMock invocation) throws Throwable { - Runnable arg; - try { - arg = (Runnable) invocation.getArguments()[1]; - } catch (Exception e) { - return null; - } - arg.run(); - return null; - } - }); - when(mockScheduler.scheduleSyncDelayedTask(any(Plugin.class), any(Runnable.class))). - thenAnswer(new Answer() { - public Integer answer(InvocationOnMock invocation) throws Throwable { - Runnable arg; - try { - arg = (Runnable) invocation.getArguments()[1]; - } catch (Exception e) { - return null; - } - arg.run(); - return null; - } - }); - when(mockServer.getScheduler()).thenReturn(mockScheduler); - - ItemFactory itemFactory = MockItemMeta.mockItemFactory(); - when(mockServer.getItemFactory()).thenReturn(itemFactory); - - - UnsafeValues unsafeValues = mock(UnsafeValues.class); - doAnswer(i -> Material.getMaterial(i.getArgument(0))).when(unsafeValues).getMaterial(any(), anyInt()); - when(mockServer.getUnsafe()).thenReturn(unsafeValues); - - // Set InventoriesListener - InventoriesListener il = spy(new InventoriesListener(plugin)); - Field inventoriesListenerField = MultiverseInventories.class.getDeclaredField("inventoriesListener"); - inventoriesListenerField.setAccessible(true); - inventoriesListenerField.set(plugin, il); - - // Set Core - Field coreField = MultiverseInventories.class.getDeclaredField("core"); - coreField.setAccessible(true); - coreField.set(plugin, core); - - // Set server - Field serverfield = JavaPlugin.class.getDeclaredField("server"); - serverfield.setAccessible(true); - serverfield.set(plugin, mockServer); - - // Set worldManager - WorldManager wm = spy(new WorldManager(core)); - Field worldmanagerfield = MultiverseCore.class.getDeclaredField("worldManager"); - worldmanagerfield.setAccessible(true); - worldmanagerfield.set(core, wm); - - // Set playerListener - MVPlayerListener pl = spy(new MVPlayerListener(core)); - Field playerlistenerfield = MultiverseCore.class.getDeclaredField("playerListener"); - playerlistenerfield.setAccessible(true); - playerlistenerfield.set(core, pl); - - // Set entityListener - MVEntityListener el = spy(new MVEntityListener(core)); - Field entitylistenerfield = MultiverseCore.class.getDeclaredField("entityListener"); - entitylistenerfield.setAccessible(true); - entitylistenerfield.set(core, el); - - // Set weatherListener - MVWeatherListener wl = spy(new MVWeatherListener(core)); - Field weatherlistenerfield = MultiverseCore.class.getDeclaredField("weatherListener"); - weatherlistenerfield.setAccessible(true); - weatherlistenerfield.set(core, wl); - - // Init our command sender - final Logger commandSenderLogger = Logger.getLogger("CommandSender"); - commandSenderLogger.setParent(Util.logger); - commandSender = mock(CommandSender.class); - doAnswer(new Answer() { - public Void answer(InvocationOnMock invocation) throws Throwable { - commandSenderLogger.info(ChatColor.stripColor((String) invocation.getArguments()[0])); - return null; - } - }).when(commandSender).sendMessage(anyString()); - when(commandSender.getServer()).thenReturn(mockServer); - when(commandSender.getName()).thenReturn("MockCommandSender"); - when(commandSender.isPermissionSet(anyString())).thenReturn(true); - when(commandSender.isPermissionSet(isA(Permission.class))).thenReturn(true); - when(commandSender.hasPermission(anyString())).thenReturn(true); - when(commandSender.hasPermission(isA(Permission.class))).thenReturn(true); - when(commandSender.addAttachment(plugin)).thenReturn(null); - when(commandSender.isOp()).thenReturn(true); - - Bukkit.setServer(mockServer); - - // Load Multiverse Core - core.onLoad(); - plugin.onLoad(); - - // Enable it. - core.onEnable(); - plugin.onEnable(); - - return true; - } catch (Exception e) { - e.printStackTrace(); - } - - return false; - } - - public boolean tearDown() { - PluginManager pluginManager = getServer().getPluginManager(); - Plugin plugin = pluginManager.getPlugin("Multiverse-Inventories"); - MultiverseInventories inventories = (MultiverseInventories) plugin; - inventories.onDisable(); - - MockPlayerFactory.clearAllPlayers(); - MockWorldFactory.clearWorlds(); - - plugin = getServer().getPluginManager().getPlugin("Multiverse-Core"); - MultiverseCore core = (MultiverseCore) plugin; - core.onDisable(); - - try { - Field serverField = Bukkit.class.getDeclaredField("server"); - serverField.setAccessible(true); - serverField.set(Class.forName("org.bukkit.Bukkit"), null); - } catch (Exception e) { - Util.log(Level.SEVERE, - "Error while trying to unregister the server from Bukkit. Has Bukkit changed?"); - e.printStackTrace(); - fail(e.getMessage()); - return false; - } - - // Dont remove so that we can see the data result after the test. - // FileUtils.deleteFolder(serverDirectory); - - return true; - } - - public MultiverseInventories getPlugin() { - return this.plugin; - } - - public Server getServer() { - return this.mockServer; - } - - public CommandSender getCommandSender() { - return commandSender; - } - - private PotionEffectType mockPotionEffectType(int id, String name) throws Exception { - PotionEffectType potionEffectType = mock(PotionEffectType.class); - Field idField = PotionEffectType.class.getDeclaredField("id"); - idField.setAccessible(true); - idField.set(potionEffectType, id); - when(potionEffectType.getId()).thenReturn(id); - when(potionEffectType.getName()).thenReturn(name); - return potionEffectType; - } -} diff --git a/src/test/java/com/onarandombox/multiverseinventories/util/Util.java b/src/test/java/com/onarandombox/multiverseinventories/util/Util.java deleted file mode 100644 index 90f4fea0..00000000 --- a/src/test/java/com/onarandombox/multiverseinventories/util/Util.java +++ /dev/null @@ -1,62 +0,0 @@ -/****************************************************************************** - * Multiverse 2 Copyright (c) the Multiverse Team 2011. * - * Multiverse 2 is licensed under the BSD License. * - * For more information please check the README.md file included * - * with this project. * - ******************************************************************************/ - -package com.onarandombox.multiverseinventories.util; - -import com.dumptruckman.minecraft.util.Logging; - -import java.util.logging.ConsoleHandler; -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.logging.LogRecord; - -public class Util { - private Util() {} - - //public static final Logger logger = Logger.getLogger("MV-Test"); - public static final Logger logger = Logging.getLogger(); - - static { - logger.setUseParentHandlers(false); - - Handler handler = new ConsoleHandler(); - handler.setFormatter(new MVTestLogFormatter()); - Handler[] handlers = logger.getHandlers(); - - for (Handler h : handlers) - logger.removeHandler(h); - - logger.addHandler(handler); - } - - public static void log(Throwable t) { - log(Level.WARNING, t.getLocalizedMessage(), t); - } - - public static void log(Level level, Throwable t) { - log(level, t.getLocalizedMessage(), t); - } - - public static void log(String message, Throwable t) { - log(Level.WARNING, message, t); - } - - public static void log(Level level, String message, Throwable t) { - LogRecord record = new LogRecord(level, message); - record.setThrown(t); - logger.log(record); - } - - public static void log(String message) { - log(Level.INFO, message); - } - - public static void log(Level level, String message) { - logger.log(level, message); - } -} diff --git a/src/test/java/com/onarandombox/multiverseinventories/util/WorldCreatorMatcher.java b/src/test/java/com/onarandombox/multiverseinventories/util/WorldCreatorMatcher.java deleted file mode 100644 index e60c1476..00000000 --- a/src/test/java/com/onarandombox/multiverseinventories/util/WorldCreatorMatcher.java +++ /dev/null @@ -1,55 +0,0 @@ -/****************************************************************************** - * Multiverse 2 Copyright (c) the Multiverse Team 2011. * - * Multiverse 2 is licensed under the BSD License. * - * For more information please check the README.md file included * - * with this project. * - ******************************************************************************/ - -package com.onarandombox.multiverseinventories.util; - -import org.bukkit.WorldCreator; -import org.mockito.ArgumentMatcher; - -public class WorldCreatorMatcher implements ArgumentMatcher { - private final WorldCreator worldCreator; - private boolean careAboutSeeds = false; - private boolean careAboutGenerators = false; - - public WorldCreatorMatcher(WorldCreator creator) { - Util.log("Creating NEW world matcher.(" + creator.name() + ")"); - this.worldCreator = creator; - } - - public void careAboutSeeds(boolean doICare) { - this.careAboutSeeds = doICare; - } - - public void careAboutGenerators(boolean doICare) { - this.careAboutGenerators = doICare; - } - - @Override - public boolean matches(WorldCreator creator) { - Util.log("Checking world creators."); - if (creator == null) { - Util.log("The given creator was null, but I was checking: " + this.worldCreator.name()); - return false; - } - Util.log("Checking Names...(" + creator.name() + ") vs (" + this.worldCreator.name() + ")"); - Util.log("Checking Envs...(" + creator.environment() + ") vs (" + this.worldCreator.environment() + ")"); - if (!creator.name().equals(this.worldCreator.name())) { - return false; - } else if (!creator.environment().equals(this.worldCreator.environment())) { - Util.log("Checking Environments..."); - return false; - } else if (careAboutSeeds && creator.seed() != this.worldCreator.seed()) { - Util.log("Checking Seeds..."); - return false; - } else if (careAboutGenerators && creator.generator().equals(this.worldCreator.generator())) { - Util.log("Checking Gens..."); - return false; - } - Util.log("Creators matched!!!"); - return true; - } -} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/DestinationTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/DestinationTest.kt new file mode 100644 index 00000000..119edd91 --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/DestinationTest.kt @@ -0,0 +1,31 @@ +package org.mvplugins.multiverse.inventories + +import org.mockbukkit.mockbukkit.entity.PlayerMock +import org.mvplugins.multiverse.core.destination.DestinationsProvider +import org.mvplugins.multiverse.core.world.WorldManager +import org.mvplugins.multiverse.core.world.options.CreateWorldOptions +import kotlin.test.BeforeTest +import kotlin.test.Test + +class DestinationTest : TestWithMockBukkit() { + + lateinit var destinationsProvider: DestinationsProvider + lateinit var player : PlayerMock + + @BeforeTest + fun setUp() { + destinationsProvider = serviceLocator.getService(DestinationsProvider::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("DestinationsProvider is not available as a service") } + val worldManager = serviceLocator.getActiveService(WorldManager::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("WorldManager is not available as a service") } + worldManager.createWorld(CreateWorldOptions.worldName("world")) + worldManager.createWorld(CreateWorldOptions.worldName("world2")) + player = server.addPlayer("Benji_0224") + } + + @Test + fun `Last location destination`() { + destinationsProvider.parseDestination("ll:world").peek { it.getLocation(player) } + destinationsProvider.parseDestination("ll:world2").peek { it.getLocation(player) } + } +} \ No newline at end of file diff --git a/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt new file mode 100644 index 00000000..36d7c644 --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/InjectionTest.kt @@ -0,0 +1,50 @@ +package org.mvplugins.multiverse.inventories + +import org.junit.jupiter.api.Test +import org.mvplugins.multiverse.inventories.commands.InventoriesCommand +import org.mvplugins.multiverse.inventories.config.InventoriesConfig +import org.mvplugins.multiverse.inventories.dataimport.DataImportManager +import org.mvplugins.multiverse.inventories.handleshare.ShareHandleListener +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource +import org.mvplugins.multiverse.inventories.profile.container.ProfileContainerStoreProvider +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager +import kotlin.test.assertEquals +import kotlin.test.assertNotNull + +class InjectionTest : TestWithMockBukkit() { + + @Test + fun `InventoriesCommand are available as a service`() { + assertEquals(28, serviceLocator.getAllActiveServices(InventoriesCommand::class.java).size) + } + + @Test + fun `InventoriesConfig is available as a service`() { + assertNotNull(serviceLocator.getActiveService(InventoriesConfig::class.java)) + } + + @Test + fun `InventoriesListener is available as a service`() { + assertNotNull(serviceLocator.getActiveService(ShareHandleListener::class.java)) + } + + @Test + fun `ProfileDataSource is available as a service`() { + assertNotNull(serviceLocator.getActiveService(ProfileDataSource::class.java)) + } + + @Test + fun `ProfileContainerStoreProvider is available as a service`() { + assertNotNull(serviceLocator.getActiveService(ProfileContainerStoreProvider::class.java)) + } + + @Test + fun `WorldGroupManager is available as a service`() { + assertNotNull(serviceLocator.getActiveService(WorldGroupManager::class.java)) + } + + @Test + fun `DataImportManager is available as a service`() { + assertNotNull(serviceLocator.getActiveService(DataImportManager::class.java)) + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/LocaleTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/LocaleTest.kt new file mode 100644 index 00000000..e2278ce0 --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/LocaleTest.kt @@ -0,0 +1,30 @@ +package org.mvplugins.multiverse.inventories + +import org.mockbukkit.mockbukkit.entity.PlayerMock +import org.mvplugins.multiverse.core.command.MVCommandManager +import org.mvplugins.multiverse.core.locale.message.Message +import org.mvplugins.multiverse.inventories.util.MVInvi18n +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals + +class LocaleTest : TestWithMockBukkit() { + + private lateinit var player: PlayerMock + private lateinit var commandManager: MVCommandManager + + @BeforeTest + fun setUp() { + commandManager = serviceLocator.getService(MVCommandManager::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("Locales is not available as a service") } + player = server.addPlayer("Benji_0224") + } + + @Test + fun `Test locale message`() { + assertEquals( + "a test-string from the resource", + Message.of(MVInvi18n.TEST_STRING).formatted(commandManager.locales, commandManager.getCommandIssuer(player)) + ) + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/MockBukkitTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/MockBukkitTest.kt new file mode 100644 index 00000000..b2bc5c84 --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/MockBukkitTest.kt @@ -0,0 +1,12 @@ +package org.mvplugins.multiverse.inventories + +import kotlin.test.Test +import kotlin.test.assertNotNull + +open class MockBukkitTest : TestWithMockBukkit() { + + @Test + fun `MockBukkit loads the plugin`() { + assertNotNull(multiverseInventories) + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt b/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt new file mode 100644 index 00000000..fe6ba014 --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/TestWithMockBukkit.kt @@ -0,0 +1,114 @@ +package org.mvplugins.multiverse.inventories + +import com.dumptruckman.minecraft.util.Logging +import org.bukkit.Location +import org.bukkit.configuration.MemorySection +import org.bukkit.configuration.file.YamlConfiguration +import org.bukkit.configuration.serialization.ConfigurationSerialization +import org.bukkit.inventory.ItemStack +import org.mockbukkit.mockbukkit.MockBukkit +import org.mockbukkit.mockbukkit.inventory.ItemStackMock +import org.mvplugins.multiverse.core.MultiverseCore +import org.mvplugins.multiverse.core.config.CoreConfig +import org.mvplugins.multiverse.core.inject.PluginServiceLocator +import org.mvplugins.multiverse.inventories.mock.MVServerMock +import java.io.File +import java.nio.file.Path +import kotlin.io.path.absolutePathString +import kotlin.test.AfterTest +import kotlin.test.BeforeTest +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +/** + * Basic abstract test class that sets up MockBukkit and MultiverseCore. + */ +abstract class TestWithMockBukkit { + + protected lateinit var server: MVServerMock + protected lateinit var multiverseCore: MultiverseCore + protected lateinit var multiverseInventories: MultiverseInventories + protected lateinit var serviceLocator : PluginServiceLocator + + @BeforeTest + fun setUpMockBukkit() { + ConfigurationSerialization.registerClass(ItemStackMock::class.java) + + server = MockBukkit.mock(MVServerMock()) + multiverseCore = MockBukkit.load(MultiverseCore::class.java) + multiverseCore.serviceLocator.getService(CoreConfig::class.java).globalDebug = 3 + multiverseInventories = MockBukkit.load(MultiverseInventories::class.java) + serviceLocator = multiverseInventories.serviceLocator + assertNotNull(server.commandMap) + } + + @AfterTest + fun tearDownMockBukkit() { + server.pluginManager.disablePlugin(multiverseInventories) + server.pluginManager.disablePlugin(multiverseCore) + MockBukkit.unmock() + Logging.warning("Unmocked") + } + + fun getResourceAsText(path: String): String? = object {}.javaClass.getResource(path)?.readText() + + fun writeResourceToConfigFile(resourcePath: String, configPath: String) { + val configResource = getResourceAsText(resourcePath) + assertNotNull(configResource) + val configFile = File(Path.of(multiverseInventories.dataFolder.absolutePath, configPath).absolutePathString()) + if (!configFile.exists()) { + configFile.parentFile.mkdirs() + configFile.createNewFile() + } + configFile.writeText(configResource) + } + + fun assertConfigEquals(expectedPath: String, actualPath: String) { + val actualString = multiverseInventories.dataFolder.toPath().resolve(actualPath).toFile().readText() + val expectedString = getResourceAsText(expectedPath) + assertNotNull(expectedString) + + val actualYaml = YamlConfiguration() + actualYaml.loadFromString(actualString) + val actualYamlKeys = HashSet(actualYaml.getKeys(true)) + + val expectedYaml = YamlConfiguration() + expectedYaml.loadFromString(expectedString) + val expectedYamlKeys = HashSet(expectedYaml.getKeys(true)) + + for (key in expectedYamlKeys) { + assertNotNull(actualYamlKeys.remove(key), "Key $key is missing in actual config") + val actualValue = actualYaml.get(key) + if (actualValue is MemorySection) { + continue + } + assertEquals(expectedYaml.get(key), actualYaml.get(key), "Value for $key is different.") + } + for (key in actualYamlKeys) { + assertNull(actualYaml.get(key), "Key $key is present in actual config when it should be empty.") + } + + assertEquals(0, actualYamlKeys.size, + "Actual config has more keys than expected config. The following keys are missing: $actualYamlKeys") + } + + fun assertLocationEquals(expected: Location?, actual: Location?) { + assertEquals(expected?.world, actual?.world, "Worlds don't match for location comparison ($expected, $actual)") + assertEquals(expected?.x, actual?.x, "X values don't match for location comparison ($expected, $actual)") + assertEquals(expected?.y, actual?.y, "Y values don't match for location comparison ($expected, $actual)") + assertEquals(expected?.z, actual?.z, "Z values don't match for location comparison ($expected, $actual)") + assertEquals(expected?.yaw, actual?.yaw, "Yaw values don't match for location comparison ($expected, $actual)") + assertEquals(expected?.pitch, actual?.pitch, "Pitch values don't match for location comparison ($expected, $actual)") + } + + fun assertInventoryEquals(expected: Array, actual: Array) { + for (i in expected.indices) { + if (expected[i]?.isEmpty ?: true && expected[i]?.isEmpty ?: true) { + continue + } + assertEquals(expected[i], actual[i]) + } + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/commands/AbstractCommandTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/commands/AbstractCommandTest.kt new file mode 100644 index 00000000..325fd281 --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/commands/AbstractCommandTest.kt @@ -0,0 +1,18 @@ +package org.mvplugins.multiverse.inventories.commands + +import org.mockbukkit.mockbukkit.command.ConsoleCommandSenderMock +import org.mockbukkit.mockbukkit.entity.PlayerMock +import org.mvplugins.multiverse.inventories.TestWithMockBukkit +import kotlin.test.BeforeTest + +abstract class AbstractCommandTest : TestWithMockBukkit() { + + protected lateinit var console: ConsoleCommandSenderMock + protected lateinit var player: PlayerMock + + @BeforeTest + fun setUpCommand() { + console = server.consoleSender + player = server.addPlayer("benwoo1110"); + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/commands/ToggleCommandTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/commands/ToggleCommandTest.kt new file mode 100644 index 00000000..8cc08158 --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/commands/ToggleCommandTest.kt @@ -0,0 +1,37 @@ +package org.mvplugins.multiverse.inventories.commands + +import org.mvplugins.multiverse.inventories.config.InventoriesConfig +import org.mvplugins.multiverse.inventories.share.Sharables +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class ToggleCommandTest : AbstractCommandTest() { + + private lateinit var config : InventoriesConfig + + @BeforeTest + fun setUp() { + config = serviceLocator.getActiveService(InventoriesConfig::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("InventoriesConfig is not available as a service") } + } + + @Test + fun `Toggle last_location on and off`() { + assertFalse(config.activeOptionalShares.contains(Sharables.LAST_LOCATION)) + server.dispatchCommand(console, "mvinv toggle last_location") + assertTrue(config.activeOptionalShares.contains(Sharables.LAST_LOCATION)) + server.dispatchCommand(console, "mvinv toggle last_location") + assertFalse(config.activeOptionalShares.contains(Sharables.LAST_LOCATION)) + } + + @Test + fun `Toggle economy on and off`() { + assertFalse(config.activeOptionalShares.contains(Sharables.ECONOMY)) + server.dispatchCommand(console, "mvinv toggle economy") + assertTrue(config.activeOptionalShares.contains(Sharables.ECONOMY)) + server.dispatchCommand(console, "mvinv toggle economy") + assertFalse(config.activeOptionalShares.contains(Sharables.ECONOMY)) + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/config/ConfigTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/config/ConfigTest.kt new file mode 100644 index 00000000..8ea2d001 --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/config/ConfigTest.kt @@ -0,0 +1,40 @@ +package org.mvplugins.multiverse.inventories.config + +import org.mvplugins.multiverse.inventories.TestWithMockBukkit +import java.io.File +import java.nio.file.Path +import kotlin.io.path.absolutePathString +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertTrue + +class ConfigTest : TestWithMockBukkit() { + + private lateinit var config : InventoriesConfig + private lateinit var configFile : File + + @BeforeTest + fun setUp() { + configFile = File(Path.of(multiverseInventories.dataFolder.absolutePath, "config.yml").absolutePathString()) + if (configFile.exists()) configFile.delete() + + config = serviceLocator.getActiveService(InventoriesConfig::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("InventoriesConfig is not available as a service") } + + assertTrue(config.load().isSuccess) + assertTrue(config.save().isSuccess) + } + + @Test + fun `Config is fresh`() { + assertConfigEquals("/config/fresh_config.yml", "config.yml") + } + + @Test + fun `Migrate from old config`() { + writeResourceToConfigFile("/config/old_config.yml", "config.yml") + assertTrue(config.load().isSuccess) + assertTrue(config.save().isSuccess) + assertConfigEquals("/config/migrated_config.yml", "config.yml") + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/GameModeChangeTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/GameModeChangeTest.kt new file mode 100644 index 00000000..1003cd3c --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/GameModeChangeTest.kt @@ -0,0 +1,162 @@ +package org.mvplugins.multiverse.inventories.handleshare + +import org.bukkit.Bukkit +import org.bukkit.Location +import org.bukkit.Material +import org.bukkit.inventory.ItemStack +import org.mvplugins.multiverse.core.world.WorldManager +import org.mvplugins.multiverse.core.world.options.CreateWorldOptions +import org.mvplugins.multiverse.inventories.TestWithMockBukkit +import org.mvplugins.multiverse.inventories.config.InventoriesConfig +import org.mvplugins.multiverse.inventories.share.Sharables +import org.mvplugins.multiverse.inventories.util.PlayerStats +import kotlin.arrayOfNulls +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class GameModeChangeTest : TestWithMockBukkit() { + + private lateinit var inventoriesConfig: InventoriesConfig + private var survivalItems = arrayOfNulls(PlayerStats.INVENTORY_SIZE) + private var creativeItems = arrayOfNulls(PlayerStats.INVENTORY_SIZE) + + @BeforeTest + fun setUp() { + inventoriesConfig = serviceLocator.getActiveService(InventoriesConfig::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("InventoriesConfig is not available as a service") + } + val worldManager = serviceLocator.getActiveService(WorldManager::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("WorldManager is not available as a service") + } + + writeResourceToConfigFile("/gameplay/gamemode_change_groups.yml", "groups.yml") + multiverseInventories.reloadConfig() + inventoriesConfig.enableGamemodeShareHandling = true + + survivalItems[0] = ItemStack.of(Material.STONE_BRICKS, 64) + survivalItems[1] = ItemStack.of(Material.SAND, 64) + + creativeItems[0] = ItemStack.of(Material.HOPPER, 64) + creativeItems[1] = ItemStack.of(Material.CAMPFIRE, 64) + + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world")).isSuccess) + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("ungrouped")).isSuccess) + } + + @Test + fun `Test change game mode for grouped worlds with total_xp disabled share`() { + val player = server.addPlayer("Benji_0224") + assertTrue(player.teleport(Bukkit.getWorld("world")!!.spawnLocation)) + + player.inventory.contents = survivalItems.clone() + player.totalExperience = 123 + + player.gameMode = org.bukkit.GameMode.CREATIVE + Thread.sleep(5) + assertInventoryEquals(arrayOfNulls(PlayerStats.INVENTORY_SIZE), player.inventory.contents) + assertEquals(123, player.totalExperience) + + player.inventory.contents = creativeItems.clone() + player.totalExperience = 321 + + player.gameMode = org.bukkit.GameMode.SURVIVAL + Thread.sleep(5) + assertInventoryEquals(survivalItems, player.inventory.contents) + assertEquals(321, player.totalExperience) + + player.gameMode = org.bukkit.GameMode.CREATIVE + Thread.sleep(5) + assertInventoryEquals(creativeItems, player.inventory.contents) + assertEquals(321, player.totalExperience) + } + + @Test + fun `Test change game mode for ungrouped worlds`() { + val player = server.addPlayer("Benji_0224") + assertTrue(player.teleport(Bukkit.getWorld("ungrouped")!!.spawnLocation)) + + player.inventory.contents = survivalItems.clone() + player.totalExperience = 123 + + player.gameMode = org.bukkit.GameMode.CREATIVE + Thread.sleep(5) + assertInventoryEquals(arrayOfNulls(PlayerStats.INVENTORY_SIZE), player.inventory.contents) + assertEquals(0, player.totalExperience) + + player.inventory.contents = creativeItems.clone() + player.totalExperience = 321 + + player.gameMode = org.bukkit.GameMode.SURVIVAL + Thread.sleep(5) + assertInventoryEquals(survivalItems, player.inventory.contents) + assertEquals(123, player.totalExperience) + + player.gameMode = org.bukkit.GameMode.CREATIVE + Thread.sleep(5) + assertInventoryEquals(creativeItems, player.inventory.contents) + assertEquals(321, player.totalExperience) + } + + @Test + fun `Test change game mode for ungrouped worlds with last_location`() { + inventoriesConfig.defaultUngroupedWorlds = false + inventoriesConfig.useOptionalsForUngroupedWorlds = true + inventoriesConfig.activeOptionalShares = Sharables.fromSharables(Sharables.LAST_LOCATION) + + val survivalLocation = Location(Bukkit.getWorld("ungrouped")!!, 1.0, 2.0, 3.0) + val creativeLocation = Location(Bukkit.getWorld("ungrouped")!!, 4.0, 5.0, 6.0) + + val player = server.addPlayer("Benji_0224") + assertTrue(player.teleport(survivalLocation.clone())) + player.inventory.contents = survivalItems.clone() + + player.gameMode = org.bukkit.GameMode.CREATIVE + assertInventoryEquals(arrayOfNulls(PlayerStats.INVENTORY_SIZE), player.inventory.contents) + assertLocationEquals(survivalLocation, player.location) + + assertTrue(player.teleport(creativeLocation.clone())) + player.inventory.contents = creativeItems.clone() + + player.gameMode = org.bukkit.GameMode.SURVIVAL + assertInventoryEquals(survivalItems, player.inventory.contents) + assertLocationEquals(survivalLocation, player.location) + + player.gameMode = org.bukkit.GameMode.CREATIVE + assertInventoryEquals(creativeItems, player.inventory.contents) + assertLocationEquals(creativeLocation, player.location) + } + + @Test + fun `Test change game mode for ungrouped worlds with useOptionalsForUngroupedWorlds disabled`() { + inventoriesConfig.defaultUngroupedWorlds = false + inventoriesConfig.useOptionalsForUngroupedWorlds = false + inventoriesConfig.activeOptionalShares = Sharables.fromSharables(Sharables.LAST_LOCATION) + + val survivalLocation = Location(Bukkit.getWorld("ungrouped")!!, 1.0, 2.0, 3.0) + val creativeLocation = Location(Bukkit.getWorld("ungrouped")!!, 4.0, 5.0, 6.0) + + val player = server.addPlayer("Benji_0224") + assertTrue(player.teleport(survivalLocation.clone())) + player.inventory.contents = survivalItems.clone() + + player.gameMode = org.bukkit.GameMode.CREATIVE + Thread.sleep(5) + assertInventoryEquals(arrayOfNulls(PlayerStats.INVENTORY_SIZE), player.inventory.contents) + assertLocationEquals(survivalLocation, player.location) + + assertTrue(player.teleport(creativeLocation.clone())) + player.inventory.contents = creativeItems.clone() + + player.gameMode = org.bukkit.GameMode.SURVIVAL + Thread.sleep(5) + assertInventoryEquals(survivalItems, player.inventory.contents) + assertLocationEquals(creativeLocation, player.location) + + player.gameMode = org.bukkit.GameMode.CREATIVE + Thread.sleep(5) + assertInventoryEquals(creativeItems, player.inventory.contents) + assertLocationEquals(creativeLocation, player.location) + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt new file mode 100644 index 00000000..30f2285b --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandlingUpdaterTest.kt @@ -0,0 +1,52 @@ +package org.mvplugins.multiverse.inventories.handleshare + +import org.mockbukkit.mockbukkit.entity.PlayerMock +import org.mvplugins.multiverse.inventories.TestWithMockBukkit +import org.mvplugins.multiverse.inventories.profile.ProfileDataSource +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes +import org.mvplugins.multiverse.inventories.profile.key.ContainerType +import org.mvplugins.multiverse.inventories.share.Sharables +import org.mvplugins.multiverse.inventories.util.FutureNow +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals + +class ShareHandlingUpdaterTest : TestWithMockBukkit() { + + private lateinit var profileDataSource: ProfileDataSource + private lateinit var player: PlayerMock + + @BeforeTest + fun setUp() { + player = server.addPlayer("benthecat10") + profileDataSource = serviceLocator.getService(ProfileDataSource::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("ProfileDataSource is not available as a service") + } + } + + @Test + fun `Test updating profile`() { + player.health = 4.4 + player.maxHealth = 15.1 + + val playerProfileKey = ProfileKey.of(ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, player.uniqueId) + ShareHandlingUpdater.updateProfile(multiverseInventories, player, PersistingProfile(Sharables.enabledOf(), playerProfileKey)) + val playerProfile = FutureNow.get(profileDataSource.getPlayerProfile(playerProfileKey)) + assertEquals(4.4, playerProfile.get(Sharables.HEALTH)) + assertEquals(15.1, playerProfile.get(Sharables.MAX_HEALTH)) + } + + @Test + fun `Test updating player`() { + val playerProfile = FutureNow.get(profileDataSource.getPlayerProfile( + ProfileKey.of(ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, player.uniqueId))) + playerProfile.set(Sharables.HEALTH, 4.4) + playerProfile.set(Sharables.MAX_HEALTH, 15.1) + + ShareHandlingUpdater.updatePlayer(multiverseInventories, player, PersistingProfile(Sharables.enabledOf(), playerProfile)) + + assertEquals(4.4, player.health) + assertEquals(15.1, player.maxHealth) + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeTest.kt new file mode 100644 index 00000000..97b816bd --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/handleshare/WorldChangeTest.kt @@ -0,0 +1,139 @@ +package org.mvplugins.multiverse.inventories.handleshare + +import org.bukkit.Bukkit +import org.bukkit.Location +import org.bukkit.Material +import org.bukkit.inventory.ItemStack +import org.mvplugins.multiverse.core.world.WorldManager +import org.mvplugins.multiverse.core.world.options.CreateWorldOptions +import org.mvplugins.multiverse.inventories.TestWithMockBukkit +import org.mvplugins.multiverse.inventories.config.InventoriesConfig +import org.mvplugins.multiverse.inventories.share.Sharables +import kotlin.test.* + +class WorldChangeTest : TestWithMockBukkit() { + + private lateinit var inventoriesConfig: InventoriesConfig + + @BeforeTest + fun setUp() { + inventoriesConfig = serviceLocator.getActiveService(InventoriesConfig::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("InventoriesConfig is not available as a service") + } + val worldManager = serviceLocator.getActiveService(WorldManager::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("WorldManager is not available as a service") } + + writeResourceToConfigFile("/gameplay/world_change_groups.yml", "groups.yml") + multiverseInventories.reloadConfig() + + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world1")).isSuccess) + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world2")).isSuccess) + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world3")).isSuccess) + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world4")).isSuccess) + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("ungrouped")).isSuccess) + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("default")).isSuccess) + } + + @Test + fun `World change between groups`() { + val player = server.addPlayer("Benji_0224") + server.getWorld("world1")?.let { player.teleport(it.spawnLocation) } + val stack = ItemStack.of(Material.STONE_BRICKS, 64) + player.inventory.setItem(0, stack) + player.health = 5.5 + player.totalExperience = 10 + + server.getWorld("world2")?.let { player.teleport(it.spawnLocation) } + assertEquals(stack, player.inventory.getItem(0)) + assertEquals(5.5, player.health) + assertEquals(10, player.totalExperience) + + server.getWorld("world4")?.let { player.teleport(it.spawnLocation) } + assertNotEquals(stack, player.inventory.getItem(0)) + assertNotEquals(5.5, player.health) + assertEquals(player.totalExperience, 0) + + server.getWorld("world2")?.let { player.teleport(it.spawnLocation) } + assertEquals(stack, player.inventory.getItem(0)) + assertEquals(5.5, player.health) + assertEquals(player.totalExperience, 0) + } + + @Test + fun `World change between group and ungrouped worlds`() { + val player = server.addPlayer("Benji_0224") + + server.getWorld("world3")?.let { player.teleport(it.spawnLocation) } + val stack = ItemStack.of(Material.STONE_BRICKS, 64) + player.inventory.setItem(0, stack) + player.health = 5.5 + player.totalExperience = 10 + + server.getWorld("ungrouped")?.let { player.teleport(it.spawnLocation) } + assertEquals(ItemStack.empty(), player.inventory.getItem(0)) + assertEquals(20.0, player.health) + assertEquals(0, player.totalExperience) + + val stack2 = ItemStack.of(Material.HOPPER, 23) + player.inventory.setItem(0, stack2) + player.health = 2.5 + player.totalExperience = 5 + + server.getWorld("world3")?.let { player.teleport(it.spawnLocation) } + assertEquals(stack, player.inventory.getItem(0)) + assertEquals(5.5, player.health) + assertEquals(10, player.totalExperience) + + server.getWorld("ungrouped")?.let { player.teleport(it.spawnLocation) } + assertEquals(stack2, player.inventory.getItem(0)) + assertEquals(2.5, player.health) + assertEquals(5, player.totalExperience) + } + + @Test + fun `World change with defaultUngroupedWorlds enabled`() { + inventoriesConfig.defaultUngroupedWorlds = true + inventoriesConfig.save() + + val player = server.addPlayer("Benji_0224") + + server.getWorld("default")?.let { player.teleport(it.spawnLocation) } + val stack = ItemStack.of(Material.WATER_BUCKET, 64) + player.inventory.setItem(0, stack) + player.health = 5.5 + player.totalExperience = 10 + + server.getWorld("ungrouped")?.let { player.teleport(it.spawnLocation) } + assertEquals(stack, player.inventory.getItem(0)) + assertEquals(5.5, player.health) + assertEquals(10, player.totalExperience) + + server.getWorld("default")?.let { player.teleport(it.spawnLocation) } + assertEquals(stack, player.inventory.getItem(0)) + assertEquals(5.5, player.health) + assertEquals(10, player.totalExperience) + } + + @Test + fun `World change with last_location optional share`() { + inventoriesConfig.activeOptionalShares = Sharables.fromSharables(Sharables.LAST_LOCATION) + + val world1Loc = Location(Bukkit.getWorld("world1")!!, 1.0, 2.0, 3.0) + val world2Loc = Location(Bukkit.getWorld("world2")!!, 4.0, 5.0, 6.0) + + val player = server.addPlayer("Benji_0224") + assertTrue(player.teleport(world1Loc.clone())) + assertLocationEquals(world1Loc, player.location) + + player.teleport(Bukkit.getWorld("world2")!!.spawnLocation.clone()) + assertLocationEquals(Bukkit.getWorld("world2")!!.spawnLocation, player.location) + player.teleport(world2Loc.clone()) + assertLocationEquals(world2Loc, player.location) + + player.teleport(Bukkit.getWorld("world1")!!.spawnLocation.clone()) + assertLocationEquals(world1Loc, player.location) + + player.teleport(Bukkit.getWorld("world2")!!.spawnLocation.clone()) + assertLocationEquals(world2Loc, player.location) + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/mock/MVServerMock.java b/src/test/java/org/mvplugins/multiverse/inventories/mock/MVServerMock.java new file mode 100644 index 00000000..de3c872f --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/mock/MVServerMock.java @@ -0,0 +1,53 @@ +package org.mvplugins.multiverse.inventories.mock; + +import org.bukkit.World; +import org.bukkit.WorldCreator; +import org.jetbrains.annotations.NotNull; +import org.mockbukkit.mockbukkit.ServerMock; +import org.mockbukkit.mockbukkit.command.CommandMapMock; +import org.mockbukkit.mockbukkit.world.WorldMock; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +public class MVServerMock extends ServerMock { + + private final File worldContainer; + + public MVServerMock() throws IOException { + super(); + this.worldContainer = Files.createTempDirectory("world-container").toFile(); + this.worldContainer.deleteOnExit(); + System.out.println("Created test world folder: " + this.worldContainer.getAbsolutePath()); + } + + // This is required for acf reflection to work + @Override + public @NotNull CommandMapMock getCommandMap() { + return super.getCommandMap(); + } + + @Override + public @NotNull File getWorldContainer() { + return this.worldContainer; + } + + @Override + public World createWorld(@NotNull WorldCreator creator) { + WorldMock world = new MVWorldMock(creator); + world.getWorldFolder().mkdirs(); + createFile(new File(world.getWorldFolder(), "uid.dat")); + createFile(new File(world.getWorldFolder(), "level.dat")); + addWorld(world); + return world; + } + + private void createFile(File file) { + try { + file.createNewFile(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/mock/MVWorldMock.java b/src/test/java/org/mvplugins/multiverse/inventories/mock/MVWorldMock.java new file mode 100644 index 00000000..2082645e --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/mock/MVWorldMock.java @@ -0,0 +1,28 @@ +package org.mvplugins.multiverse.inventories.mock; + +import org.bukkit.WorldCreator; +import org.jetbrains.annotations.NotNull; +import org.mockbukkit.mockbukkit.MockBukkit; +import org.mockbukkit.mockbukkit.world.WorldMock; + +import java.io.File; + +public class MVWorldMock extends WorldMock { + + private final File worldFolder; + + public MVWorldMock(@NotNull WorldCreator creator) { + super(creator); + this.worldFolder = new File(MockBukkit.getMock().getWorldContainer(), getName()); + } + + @Override + public @NotNull File getWorldFolder() { + return this.worldFolder; + } + + @Override + public String toString() { + return "MVWorldMock{'name': '" + this.getName() + "'}"; + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt new file mode 100644 index 00000000..2a604235 --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/FilePerformanceTest.kt @@ -0,0 +1,169 @@ +package org.mvplugins.multiverse.inventories.profile + +import com.dumptruckman.minecraft.util.Logging +import org.bukkit.GameMode +import org.bukkit.Material +import org.bukkit.enchantments.Enchantment +import org.bukkit.inventory.ItemStack +import org.bukkit.potion.PotionEffect +import org.bukkit.potion.PotionEffectType +import org.junit.jupiter.api.Test +import org.mvplugins.multiverse.core.utils.CoreLogging +import org.mvplugins.multiverse.core.world.WorldManager +import org.mvplugins.multiverse.core.world.options.CreateWorldOptions +import org.mvplugins.multiverse.inventories.TestWithMockBukkit +import org.mvplugins.multiverse.inventories.profile.data.PlayerProfile +import org.mvplugins.multiverse.inventories.profile.key.ContainerType +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes +import org.mvplugins.multiverse.inventories.share.Sharables +import org.mvplugins.multiverse.inventories.util.FutureNow +import java.util.* +import java.util.concurrent.CompletableFuture +import java.util.concurrent.Future +import java.util.function.Consumer +import kotlin.test.BeforeTest +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class FilePerformanceTest : TestWithMockBukkit() { + + private lateinit var worldManager: WorldManager + private lateinit var profileDataSource: ProfileDataSource + private lateinit var profileCacheManager: ProfileCacheManager + + @BeforeTest + fun setUp() { + worldManager = serviceLocator.getActiveService(WorldManager::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("WorldManager is not available as a service") } + profileDataSource = serviceLocator.getService(ProfileDataSource::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("ProfileDataSource is not available as a service") } + profileCacheManager = serviceLocator.getService(ProfileCacheManager::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("ProfileCacheManager is not available as a service") } + CoreLogging.setDebugLevel(0); + Logging.setDebugLevel(0) + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world")).isSuccess) + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world2")).isSuccess) + } + + @Test + fun `Test 1K global profiles`() { + val startTime = System.nanoTime() + val futures = ArrayList>(1000) + for (i in 0..1000) { + futures.add(profileDataSource.modifyGlobalProfile(GlobalProfileKey.of(UUID.randomUUID(), ""), { globalProfile -> + globalProfile.setLoadOnLogin(true) + })) + } + for (future in futures) { + future.get() + } + Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") + } + + @Test + fun `Test 1K player profiles`() { + server.setPlayers(1000) + val startTime = System.nanoTime() + val futures = ArrayList>(1000) + for (i in 0..999) { + val player = server.getPlayer(i) + for (gameMode in GameMode.entries) { + val playerProfile = FutureNow.get(profileDataSource.getPlayerProfile( + ProfileKey.of(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId))) + playerProfile.set(Sharables.HEALTH, 5.0) + playerProfile.set(Sharables.OFF_HAND, ItemStack(Material.STONE_BRICKS, 10)) + playerProfile.set(Sharables.INVENTORY, arrayOf( + ItemStack(Material.STONE_BRICKS, 10), + ItemStack(Material.ACACIA_LOG, 10), + createItemStack(Material.BOW, 1, { itemStack -> + itemStack.addEnchantment(Enchantment.UNBREAKING, 2) + }), + ItemStack(Material.WATER_BUCKET, 64) + )) + playerProfile.set(Sharables.POTIONS, arrayOf( + PotionEffect(PotionEffectType.POISON, 100, 1), + PotionEffect(PotionEffectType.SPEED, 50, 1), + )) + futures.add(profileDataSource.updatePlayerProfile(playerProfile)) + } + } + for (future in futures) { + future.get() + } + Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") + profileCacheManager.clearAllCache() + + val startTime2 = System.nanoTime() + val futures2 = ArrayList>(1000) + for (i in 0..999) { + val player = server.getPlayer(i) + for (gameMode in GameMode.entries) { + futures2.add(profileDataSource.getPlayerProfile( + ProfileKey.of(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId))) + } + } + for (future in futures2) { + val playerProfile = future.get() + assertEquals(5.0, playerProfile.get(Sharables.HEALTH)) + assertEquals(ItemStack(Material.STONE_BRICKS, 10), playerProfile.get(Sharables.OFF_HAND)) + } + Logging.info("Time taken: " + (System.nanoTime() - startTime2) / 1000000 + "ms") + + val startTime3 = System.nanoTime() + val futures3 = ArrayList>(1000) + for (i in 0..999) { + val player = server.getPlayer(i) + for (gameMode in GameMode.entries) { + futures3.add(profileDataSource.deletePlayerProfile( + ProfileKey.of(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId))) + val playerProfile = FutureNow.get(profileDataSource.getPlayerProfile( + ProfileKey.of(ContainerType.WORLD, "world", ProfileTypes.forGameMode(gameMode), player.uniqueId))) + assertNull(playerProfile.get(Sharables.HEALTH)) + assertNull(playerProfile.get(Sharables.OFF_HAND)) + } + } + for (future in futures3) { + future.get() + } + Logging.info("Time taken: " + (System.nanoTime() - startTime3) / 1000000 + "ms") + + val cacheStats = profileCacheManager.getCacheStats() + Logging.info(cacheStats.values.toString()) + for (cacheStat in cacheStats) { + Logging.info(cacheStat.key + ": " + cacheStat.value.averageLoadPenalty() / 1000000 + "ms") + } + } + + private fun createItemStack(material: Material, amount: Int = 1, modify: Consumer): ItemStack { + val itemStack = ItemStack(material, amount) + modify.accept(itemStack) + return itemStack + } + + @Test + fun `50 players join the server consecutively`() { + val startTime = System.nanoTime() + server.setPlayers(50) + Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") + } + + @Test + fun `Teleport 50 players consecutively`() { + for (i in 0..49) { + writeResourceToConfigFile("/playerdata.json", "worlds/world2/Player$i.json") + } + server.setPlayers(50) + val startTime = System.nanoTime() + for (player in server.playerList.onlinePlayers) { + server.getWorld("world2")?.let { player.teleport(it.spawnLocation) } + } + Logging.info("Time taken: " + (System.nanoTime() - startTime) / 1000000 + "ms") + val cacheStats = profileCacheManager.getCacheStats() + for (cacheStat in cacheStats) { + Logging.info(cacheStat.key + ": " + cacheStat.value.averageLoadPenalty() / 1000000 + "ms") + } + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/PlayerNameChangeTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/PlayerNameChangeTest.kt new file mode 100644 index 00000000..9bc9cbb6 --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/PlayerNameChangeTest.kt @@ -0,0 +1,87 @@ +package org.mvplugins.multiverse.inventories.profile + +import org.bukkit.Material +import org.bukkit.inventory.ItemStack +import org.mockbukkit.mockbukkit.entity.PlayerMock +import org.mvplugins.multiverse.core.world.WorldManager +import org.mvplugins.multiverse.core.world.options.CreateWorldOptions +import org.mvplugins.multiverse.inventories.TestWithMockBukkit +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager +import org.mvplugins.multiverse.inventories.profile.key.GlobalProfileKey +import org.mvplugins.multiverse.inventories.util.FutureNow +import java.nio.file.Path +import kotlin.test.* + +class PlayerNameChangeTest : TestWithMockBukkit() { + + private lateinit var profileDataSource: ProfileDataSource + private lateinit var playerNamesMapper: PlayerNamesMapper + private lateinit var player: PlayerMock + + @BeforeTest + fun setUp() { + profileDataSource = serviceLocator.getService(ProfileDataSource::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("ProfileDataSource is not available as a service") } + playerNamesMapper = serviceLocator.getService(PlayerNamesMapper::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("PlayerNamesMapper is not available as a service") } + + val worldManager = serviceLocator.getActiveService(WorldManager::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("WorldManager is not available as a service") } + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world")).isSuccess) + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world_nether")).isSuccess) + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("test")).isSuccess) + + val worldGroupManager = serviceLocator.getActiveService(WorldGroupManager::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("WorldGroupManager is not available as a service") } + writeResourceToConfigFile("/gameplay/name_change_groups.yml", "groups.yml") + assertTrue(worldGroupManager.load().isSuccess) + + player = server.addPlayer("Benji_0224") + assertEquals(GlobalProfileKey.of(player.uniqueId, "Benji_0224"), playerNamesMapper.getKey("Benji_0224").orNull) + } + + @Test + fun `Player name changes`() { + val stack = ItemStack.of(Material.STONE_BRICKS, 5) + player.health = 5.0 + player.inventory.setItem(0, stack) + + server.getWorld("world_nether")?.let { player.teleport(it.spawnLocation) } + assertEquals(5.0, player.health) + assertEquals(stack, player.inventory.getItem(0)) + + server.getWorld("test")?.let { player.teleport(it.spawnLocation) } + assertNotEquals(5.0, player.health) + assertNotEquals(stack, player.inventory.getItem(0)) + + assertTrue(player.disconnect()) + player.name = "benthecat10" + assertTrue(player.reconnect()) + + server.getWorld("world")?.let { player.teleport(it.spawnLocation) } + assertEquals(5.0, player.health) + assertEquals(stack, player.inventory.getItem(0)) + + Thread.sleep(100) // wait for files to save + + // check files + assertTrue(Path.of(multiverseInventories.dataFolder.absolutePath, "worlds", "world", "benthecat10.json").toFile().exists()) + assertTrue(Path.of(multiverseInventories.dataFolder.absolutePath, "worlds", "world_nether", "benthecat10.json").toFile().exists()) + assertTrue(Path.of(multiverseInventories.dataFolder.absolutePath, "worlds", "test", "benthecat10.json").toFile().exists()) + assertTrue(Path.of(multiverseInventories.dataFolder.absolutePath, "groups", "default", "benthecat10.json").toFile().exists()) + assertTrue(Path.of(multiverseInventories.dataFolder.absolutePath, "groups", "test", "benthecat10.json").toFile().exists()) + + assertFalse(Path.of(multiverseInventories.dataFolder.absolutePath, "worlds", "world", "Benji_0224.json").toFile().exists()) + assertFalse(Path.of(multiverseInventories.dataFolder.absolutePath, "worlds", "world_nether", "Benji_0224.json").toFile().exists()) + assertFalse(Path.of(multiverseInventories.dataFolder.absolutePath, "worlds", "test", "Benji_0224.json").toFile().exists()) + assertFalse(Path.of(multiverseInventories.dataFolder.absolutePath, "groups", "default", "Benji_0224.json").toFile().exists()) + assertFalse(Path.of(multiverseInventories.dataFolder.absolutePath, "groups", "test", "Benji_0224.json").toFile().exists()) + + // check player profile + assertEquals("benthecat10", FutureNow.get(profileDataSource.getGlobalProfile(GlobalProfileKey.of(player)))?.lastKnownName) + + // check name mapper updated + assertEquals(GlobalProfileKey.of(player.uniqueId, "benthecat10"), playerNamesMapper.getKey("benthecat10").orNull) + assertNull(playerNamesMapper.getKey("Benji_0224").orNull) + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileContainerTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileContainerTest.kt new file mode 100644 index 00000000..7736f73c --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileContainerTest.kt @@ -0,0 +1,7 @@ +package org.mvplugins.multiverse.inventories.profile + +import org.mvplugins.multiverse.inventories.TestWithMockBukkit + +class ProfileContainerTest : TestWithMockBukkit() { + //TODO +} \ No newline at end of file diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSourceTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSourceTest.kt new file mode 100644 index 00000000..ad99ee16 --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/ProfileDataSourceTest.kt @@ -0,0 +1,31 @@ +package org.mvplugins.multiverse.inventories.profile + +import com.dumptruckman.minecraft.util.Logging +import org.junit.jupiter.api.Test +import org.mvplugins.multiverse.inventories.TestWithMockBukkit +import org.mvplugins.multiverse.inventories.profile.key.ContainerType +import org.mvplugins.multiverse.inventories.profile.key.ProfileKey +import org.mvplugins.multiverse.inventories.profile.key.ProfileTypes +import kotlin.test.BeforeTest + +class ProfileDataSourceTest : TestWithMockBukkit() { + + private lateinit var profileDataSource: ProfileDataSource + + @BeforeTest + fun setUp() { + profileDataSource = serviceLocator.getService(ProfileDataSource::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("ProfileDataSource is not available as a service") } + } + + @Test + fun `getPlayerData called twice`() { + server.setPlayers(1) + writeResourceToConfigFile("/playerdata.json", "worlds/world/Player0.json") + val key = ProfileKey.of(ContainerType.WORLD, "world", ProfileTypes.SURVIVAL, server.getPlayer("Player0")) + profileDataSource.getPlayerProfile(key).thenAccept { profile -> Logging.info(profile.toString()) } + profileDataSource.getPlayerProfile(key).thenAccept { profile -> Logging.info(profile.toString()) } + profileDataSource.getPlayerProfile(key).thenAccept { profile -> Logging.info(profile.toString()) } + Logging.info("Getting player data...") + } +} diff --git a/src/test/java/org/mvplugins/multiverse/inventories/profile/WorldGroupManagerTest.kt b/src/test/java/org/mvplugins/multiverse/inventories/profile/WorldGroupManagerTest.kt new file mode 100644 index 00000000..0348423e --- /dev/null +++ b/src/test/java/org/mvplugins/multiverse/inventories/profile/WorldGroupManagerTest.kt @@ -0,0 +1,50 @@ +package org.mvplugins.multiverse.inventories.profile + +import org.mvplugins.multiverse.core.world.WorldManager +import org.mvplugins.multiverse.core.world.options.CreateWorldOptions +import org.mvplugins.multiverse.inventories.TestWithMockBukkit +import org.mvplugins.multiverse.inventories.profile.group.WorldGroupManager +import org.mvplugins.multiverse.inventories.share.Sharables +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class WorldGroupManagerTest : TestWithMockBukkit() { + + private lateinit var worldGroupManager: WorldGroupManager + + @BeforeTest + fun setUp() { + worldGroupManager = + serviceLocator.getActiveService(WorldGroupManager::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("WorldGroupManager is not available as a service") + } + + val worldManager = serviceLocator.getActiveService(WorldManager::class.java).takeIf { it != null } ?: run { + throw IllegalStateException("WorldManager is not available as a service") + } + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world")).isSuccess) + assertTrue(worldManager.createWorld(CreateWorldOptions.worldName("world_nether")).isSuccess) + } + + @Test + fun `First run creates default group`() { + writeResourceToConfigFile("/group/empty.yml", "groups.yml") + assertTrue(worldGroupManager.load().isSuccess) + worldGroupManager.createDefaultGroup() + assertEquals("default", worldGroupManager.defaultGroup.name) + assertEquals(setOf("world", "world_nether"), worldGroupManager.defaultGroup.worlds) + assertConfigEquals("/group/default_group.yml", "groups.yml") + } + + @Test + fun `Create a new group`() { + val group = worldGroupManager.newEmptyGroup("test") + group.addWorld("test1") + group.addWorld("test2") + group.shares.setSharing(Sharables.ALL_INVENTORY, true) + worldGroupManager.updateGroup(group) + assertConfigEquals("/group/test_group.yml", "groups.yml") + } +} diff --git a/src/test/resources/config/fresh_config.yml b/src/test/resources/config/fresh_config.yml new file mode 100644 index 00000000..6335cd6c --- /dev/null +++ b/src/test/resources/config/fresh_config.yml @@ -0,0 +1,29 @@ +share-handling: + enable-bypass-permissions: false + enable-gamemode-share-handling: false + default-ungrouped-worlds: false + use-optionals-for-ungrouped-worlds: true + active-optional-shares: [] + +sharables: + use-improved-respawn-location-detection: true + reset-last-location-on-death: false + apply-last-location-for-all-teleports: true + use-byte-serialization-for-inventory-data: false + +performance: + apply-playerdata-on-join: false + always-write-world-profile: true + preload-data-on-join: + worlds: [] + groups: [] + cache: + player-file-cache-size: 2000 + player-file-cache-expiry: 60 + player-profile-cache-size: 6000 + player-profile-cache-expiry: 60 + global-profile-cache-size: 500 + global-profile-cache-expiry: 60 + +first-run: true +version: 5.0 diff --git a/src/test/resources/config/migrated_config.yml b/src/test/resources/config/migrated_config.yml new file mode 100644 index 00000000..20f7f48e --- /dev/null +++ b/src/test/resources/config/migrated_config.yml @@ -0,0 +1,30 @@ +share-handling: + enable-bypass-permissions: true + enable-gamemode-share-handling: true + default-ungrouped-worlds: true + use-optionals-for-ungrouped-worlds: false + active-optional-shares: + - last_location + +sharables: + use-improved-respawn-location-detection: true + reset-last-location-on-death: false + apply-last-location-for-all-teleports: true + use-byte-serialization-for-inventory-data: false + +performance: + apply-playerdata-on-join: true + always-write-world-profile: true + preload-data-on-join: + worlds: [] + groups: [] + cache: + player-file-cache-size: 2000 + player-file-cache-expiry: 60 + player-profile-cache-size: 6000 + player-profile-cache-expiry: 60 + global-profile-cache-size: 500 + global-profile-cache-expiry: 60 + +first-run: true +version: 5.0 diff --git a/src/test/resources/config/old_config.yml b/src/test/resources/config/old_config.yml new file mode 100644 index 00000000..c45b8251 --- /dev/null +++ b/src/test/resources/config/old_config.yml @@ -0,0 +1,35 @@ +# Multiverse-Inventories Settings + +settings: + # This is the locale you wish to use. + locale: en + + # If this is true it will generate world groups for you based on MV worlds. + first_run: true + + # If this is set to true, it will enable bypass permissions (Check the wiki for more info.) + use_bypass: true + + # If set to true, any world not listed in a group will automatically use the settings for the default group! + default_ungrouped_worlds: true + + # The default and suggested setting for this is FALSE. + # False means Multiverse-Inventories will not attempt to load or save any player data when they log in and out. + # That means that MINECRAFT will handle that exact thing JUST LIKE IT DOES NORMALLY. + # Changing this to TRUE will have Multiverse-Inventories save player data when they log out and load it when they log in. + # The biggest potential drawback here is that if your server crashes, player stats/inventories may be lost/rolled back! + save_load_on_log_in_out: true + + # If this is set to true, players will have different inventories/stats for each game mode. + # Please note that old data migrated to the version that has this feature will have their data copied for both game modes. + use_game_mode_profiles: true +shares: + # When set to true, optional shares WILL be utilized in cases where a group does not cover their uses for a world. + # An example of this in action would be an ungrouped world using last_location. When this is true, players will return to their last location in that world. + # When set to false, optional shares WILL NOt be utilized in these cases, effectively disabling it for ungrouped worlds. + optionals_for_ungrouped_worlds: false + + # You must specify optional shares you wish to use here or they will be ignored. + # The only built in optional shares are "economy" and "last_location". + use_optionals: + - last_location diff --git a/src/test/resources/gameplay/gamemode_change_groups.yml b/src/test/resources/gameplay/gamemode_change_groups.yml new file mode 100644 index 00000000..2ff14aa6 --- /dev/null +++ b/src/test/resources/gameplay/gamemode_change_groups.yml @@ -0,0 +1,8 @@ +groups: + group1: + worlds: + - world + shares: + - hit_points + disabled-shares: + - total_xp diff --git a/src/test/resources/gameplay/name_change_groups.yml b/src/test/resources/gameplay/name_change_groups.yml new file mode 100644 index 00000000..4d8e448c --- /dev/null +++ b/src/test/resources/gameplay/name_change_groups.yml @@ -0,0 +1,12 @@ +groups: + default: + worlds: + - world + - world_nether + shares: + - all + test: + worlds: + - test + shares: + - all \ No newline at end of file diff --git a/src/test/resources/gameplay/world_change_groups.yml b/src/test/resources/gameplay/world_change_groups.yml new file mode 100644 index 00000000..7a53b916 --- /dev/null +++ b/src/test/resources/gameplay/world_change_groups.yml @@ -0,0 +1,28 @@ +groups: + default: + worlds: + - default + shares: + - all + group1: + worlds: + - world1 + - world2 + shares: + - inventory_contents + - hit_points + disabled-shares: + - total_xp + group2: + worlds: + - world3 + shares: + - inventory_contents + - hit_points + group3: + worlds: + - world1 + - world2 + - world3 + shares: + - off_hand diff --git a/src/test/resources/group/default_group.yml b/src/test/resources/group/default_group.yml new file mode 100644 index 00000000..5aaab393 --- /dev/null +++ b/src/test/resources/group/default_group.yml @@ -0,0 +1,7 @@ +groups: + default: + worlds: + - world + - world_nether + shares: + - all diff --git a/src/test/resources/group/empty.yml b/src/test/resources/group/empty.yml new file mode 100644 index 00000000..e69de29b diff --git a/src/test/resources/group/test_group.yml b/src/test/resources/group/test_group.yml new file mode 100644 index 00000000..cfc105c2 --- /dev/null +++ b/src/test/resources/group/test_group.yml @@ -0,0 +1,10 @@ +groups: + test: + worlds: + - test2 + - test1 + shares: + - inventory_contents + - armor_contents + - ender_chest + - off_hand diff --git a/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker deleted file mode 100644 index 1f0955d4..00000000 --- a/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker +++ /dev/null @@ -1 +0,0 @@ -mock-maker-inline diff --git a/src/test/resources/playerdata.json b/src/test/resources/playerdata.json new file mode 100644 index 00000000..3de7e0a9 --- /dev/null +++ b/src/test/resources/playerdata.json @@ -0,0 +1 @@ +{"SURVIVAL":{"inventoryContents":{"0":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"REDSTONE_TORCH"},"1":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"REDSTONE_BLOCK"},"2":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"COMPARATOR"},"3":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"LEVER"},"4":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"STONE_BUTTON"},"5":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"REDSTONE"},"8":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"REDSTONE_BLOCK"}},"lastLocation":{"==":"org.bukkit.Location","world":"world","x":-39.5,"y":72.0,"z":0.5,"pitch":0.0,"yaw":0.0},"armorContents":{},"potions":[],"bedSpawnLocation":{"==":"org.bukkit.Location","world":"world","x":-40.0,"y":72.0,"z":0.0,"pitch":0.0,"yaw":0.0},"enderChestContents":{},"offHandItem":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"AIR","amount":0},"stats":{"ex":"0.0","ma":"300","mhp":"20.0","fl":"20","el":"0","hp":"20.0","xp":"0.0","txp":"0","sa":"5.0","ft":"-20","fd":"0.0","ra":"300"}},"CREATIVE":{"inventoryContents":{"22":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"23":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"24":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"25":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"26":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"27":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"28":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"29":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"30":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"31":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"10":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"32":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"11":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"33":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"12":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"DIAMOND","amount":40},"34":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"13":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"35":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"14":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"36":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"DIAMOND_BOOTS"},"15":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"37":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"DIAMOND_LEGGINGS"},"16":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"38":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"DIAMOND_CHESTPLATE"},"17":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"39":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"DIAMOND_HELMET"},"18":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"19":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"0":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"END_STONE","amount":10},"1":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"BONE","amount":2},"2":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"ARROW"},"3":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"CRAFTING_TABLE","amount":63},"4":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"5":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"6":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"7":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"8":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"9":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"40":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"20":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"21":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"}},"lastLocation":{"==":"org.bukkit.Location","world":"world","x":-39.227095053544765,"y":72.0,"z":3.5331800520689565,"pitch":26.250015,"yaw":128.1001},"armorContents":{"0":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"DIAMOND_BOOTS"},"1":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"DIAMOND_LEGGINGS"},"2":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"DIAMOND_CHESTPLATE"},"3":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"DIAMOND_HELMET"}},"potions":[],"bedSpawnLocation":{"==":"org.bukkit.Location","world":"world","x":-40.0,"y":72.0,"z":0.0,"pitch":0.0,"yaw":0.0},"enderChestContents":{},"offHandItem":{"==":"org.bukkit.inventory.ItemStack","v":4189,"type":"SHIELD"},"stats":{"ex":"1.0499994","ma":"300","mhp":"20.0","fl":"17","el":"0","hp":"3.3333358764648438","xp":"0.0","txp":"0","sa":"0.0","ft":"-20","fd":"0.0","ra":"300"}}} \ No newline at end of file