diff --git a/build.gradle b/build.gradle index f901c8bb..2e5f862d 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,10 @@ configure(apiDependencies) { } dependencies { + // 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 // TODO update to correct version once we have it published externalPlugin 'org.mvplugins.multiverse.core:multiverse-core:5.0.0-SNAPSHOT' diff --git a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java index bfe54972..4fcf906c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/MultiverseInventories.java @@ -25,6 +25,7 @@ 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.core.commandtools.MVCommandManager; @@ -96,6 +97,8 @@ public final void onEnable() { initializeDependencyInjection(); 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!")); diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java index 56726d40..8c06652d 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfig.java @@ -167,6 +167,14 @@ public Try setApplyLastLocationForAllTeleports(boolean applyLastLocationFo 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); + } + /** * Tells whether Multiverse-Inventories should save on player logout. * diff --git a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java index b892784e..30cb011e 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/config/InventoriesConfigNodes.java @@ -135,6 +135,13 @@ public Object serialize(Shares sharables, Class aClass) { .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 byte serialization for inventory data.") + .defaultValue(false) + .name("use-byte-serialization-for-inventory-data") + .build()); + private final ConfigHeaderNode performanceHeader = node(ConfigHeaderNode.builder("performance") .comment("") .comment("") diff --git a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java index 1504bcb0..f57c155c 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/handleshare/ShareHandler.java @@ -115,6 +115,7 @@ private void updatePersistingProfile(PersistingProfile persistingProfile, Profil } private void logHandlingComplete(double timeTaken, ShareHandlingEvent event) { - Logging.fine("=== %s complete for %s | time taken: %4.4f ms ===", player.getName(), event.getEventName(), timeTaken); + 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/share/InventorySerializer.java b/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java index 513274b2..5c982c64 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/InventorySerializer.java @@ -1,5 +1,6 @@ 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; @@ -29,11 +30,14 @@ public Object serialize(ItemStack[] itemStacks) { return mapSlots(itemStacks); } - private Map mapSlots(ItemStack[] itemStacks) { - Map result = new HashMap<>(itemStacks.length); + 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]); + Object serialize = ItemStackConverter.serialize(itemStacks[i]); + if (serialize != null) { + result.put(Integer.toString(i), serialize); + } } } return result; @@ -46,7 +50,16 @@ private ItemStack[] unmapSlots(Object obj) { } for (int i = 0; i < inventory.length; i++) { Object value = invMap.get(Integer.toString(i)); - inventory[i] = value instanceof ItemStack item ? item : new ItemStack(Material.AIR); + 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/org/mvplugins/multiverse/inventories/share/Sharables.java b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java index 0eac7f95..01aa4714 100644 --- a/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java +++ b/src/main/java/org/mvplugins/multiverse/inventories/share/Sharables.java @@ -187,7 +187,7 @@ public boolean updatePlayer(Player player, ProfileData profile) { 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. 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..a15b8cda --- /dev/null +++ b/src/main/java/org/mvplugins/multiverse/inventories/util/ItemStackConverter.java @@ -0,0 +1,60 @@ +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 { + + public 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); + } + + @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 (config != null && config.getUseByteSerializationForInventoryData() && hasByteSerializeSupport) { + if (itemStack.getType() == Material.AIR) { + return null; + } + return Try.of(() -> Base64.getEncoder().encodeToString(itemStack.serializeAsBytes())) + .onFailure(e -> Logging.severe("Could not serialize item stack: %s", e.getMessage())) + .getOrNull(); + } + // let ConfigurationSerialization handle it + return itemStack; + } + + private ItemStackConverter() { + // no instantiation + } +} diff --git a/src/test/resources/config/fresh_config.yml b/src/test/resources/config/fresh_config.yml index ceec69b5..77727f68 100644 --- a/src/test/resources/config/fresh_config.yml +++ b/src/test/resources/config/fresh_config.yml @@ -9,6 +9,7 @@ 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: save-playerdata-on-quit: false diff --git a/src/test/resources/config/migrated_config.yml b/src/test/resources/config/migrated_config.yml index 31f7575e..167bb13b 100644 --- a/src/test/resources/config/migrated_config.yml +++ b/src/test/resources/config/migrated_config.yml @@ -10,6 +10,7 @@ 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: save-playerdata-on-quit: true