diff --git a/build.gradle.kts b/build.gradle.kts index 76c4fec4..0e34b925 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,6 +15,7 @@ repositories { dependencies { compileOnly(libs.placeholderapi) paperweight.paperDevBundle("${libs.versions.minecraft.get()}+") + compileOnly(libs.h2) } tasks.runServer { @@ -38,7 +39,8 @@ tasks.withType().configureEach { tasks.processResources { val props = mapOf("version" to version, - "mcVersion" to libs.versions.minecraft.get()) + "mcVersion" to libs.versions.minecraft.get(), + "h2Version" to libs.versions.h2.get()) filesMatching("plugin.yml") { expand(props) } @@ -47,4 +49,5 @@ tasks.processResources { tasks.register("resetAndRun") { delete("run/plugins/$rootProject.name") finalizedBy("runServer") + description = "Resets the plugin's data directory and then runs a server" } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 50f761dd..2f6f2070 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,6 +3,7 @@ minecraft = "26.1.2" placeholderapi = "2.11.6" run-paper = "3.0.2" paperweight = "2.0.0-beta.21" +h2 = "2.4.240" [plugins] paperweight = { id = "io.papermc.paperweight.userdev", version.ref = "paperweight" } @@ -10,3 +11,4 @@ run-paper = { id = "xyz.jpenilla.run-paper", version.ref = "run-paper" } [libraries] placeholderapi = { module = "me.clip:placeholderapi", version.ref = "placeholderapi" } +h2 = { module = "com.h2database:h2", version.ref = "h2" } diff --git a/src/main/java/com/catadmirer/infuseSMP/EffectConstants.java b/src/main/java/com/catadmirer/infuseSMP/EffectConstants.java index b05cc881..d2d99cc3 100644 --- a/src/main/java/com/catadmirer/infuseSMP/EffectConstants.java +++ b/src/main/java/com/catadmirer/infuseSMP/EffectConstants.java @@ -19,16 +19,16 @@ public static Material menuBackgroundColor(int effectId) { case EffectIds.EMERALD -> Material.LIME_STAINED_GLASS_PANE; case EffectIds.ENDER -> Material.PURPLE_STAINED_GLASS_PANE; case EffectIds.FEATHER -> Material.WHITE_STAINED_GLASS_PANE; - case EffectIds.FIRE -> Material.ORANGE_STAINED_GLASS_PANE; - case EffectIds.FROST -> Material.LIGHT_BLUE_STAINED_GLASS_PANE; - case EffectIds.HASTE -> Material.ORANGE_STAINED_GLASS_PANE; - case EffectIds.HEART -> Material.RED_STAINED_GLASS_PANE; + case EffectIds.FIRE, + EffectIds.HASTE -> Material.ORANGE_STAINED_GLASS_PANE; + case EffectIds.FROST, + EffectIds.SPEED -> Material.LIGHT_BLUE_STAINED_GLASS_PANE; + case EffectIds.HEART, + EffectIds.REGEN, + EffectIds.STRENGTH, + EffectIds.THIEF -> Material.RED_STAINED_GLASS_PANE; case EffectIds.INVIS -> Material.LIGHT_GRAY_STAINED_GLASS_PANE; case EffectIds.OCEAN -> Material.BLUE_STAINED_GLASS_PANE; - case EffectIds.REGEN -> Material.RED_STAINED_GLASS_PANE; - case EffectIds.SPEED -> Material.LIGHT_BLUE_STAINED_GLASS_PANE; - case EffectIds.STRENGTH -> Material.RED_STAINED_GLASS_PANE; - case EffectIds.THIEF -> Material.RED_STAINED_GLASS_PANE; case EffectIds.THUNDER -> Material.YELLOW_STAINED_GLASS_PANE; default -> null; }; @@ -88,21 +88,21 @@ public static Color potionColor(int effectId) { */ public static BossBar.Color ritualColor(int effectId) { return switch (effectId) { - case EffectIds.APOPHIS -> BossBar.Color.PURPLE; + case EffectIds.APOPHIS, + EffectIds.ENDER, + EffectIds.INVIS -> BossBar.Color.PURPLE; case EffectIds.EMERALD -> BossBar.Color.GREEN; - case EffectIds.ENDER -> BossBar.Color.PURPLE; case EffectIds.FEATHER -> BossBar.Color.WHITE; - case EffectIds.FIRE -> BossBar.Color.RED; - case EffectIds.FROST -> BossBar.Color.BLUE; - case EffectIds.HASTE -> BossBar.Color.YELLOW; - case EffectIds.HEART -> BossBar.Color.RED; - case EffectIds.INVIS -> BossBar.Color.PURPLE; - case EffectIds.OCEAN -> BossBar.Color.BLUE; + case EffectIds.FIRE, + EffectIds.HEART, + EffectIds.STRENGTH, + EffectIds.THIEF -> BossBar.Color.RED; + case EffectIds.FROST, + EffectIds.OCEAN -> BossBar.Color.BLUE; + case EffectIds.HASTE, + EffectIds.SPEED, + EffectIds.THUNDER -> BossBar.Color.YELLOW; case EffectIds.REGEN -> BossBar.Color.PINK; - case EffectIds.SPEED -> BossBar.Color.YELLOW; - case EffectIds.STRENGTH -> BossBar.Color.RED; - case EffectIds.THIEF -> BossBar.Color.RED; - case EffectIds.THUNDER -> BossBar.Color.YELLOW; default -> null; }; } diff --git a/src/main/java/com/catadmirer/infuseSMP/EquipEffect.java b/src/main/java/com/catadmirer/infuseSMP/EquipEffect.java index 56174105..d3fdc4bc 100644 --- a/src/main/java/com/catadmirer/infuseSMP/EquipEffect.java +++ b/src/main/java/com/catadmirer/infuseSMP/EquipEffect.java @@ -60,11 +60,11 @@ public void safeEquip(Player player, InfuseEffect effect) { */ private boolean equipEffect(Player player, InfuseEffect effect, String slot) { // Checking for an effect in the slot. - InfuseEffect currentEffect = plugin.getDataManager().getEffect(player.getUniqueId(), slot); + InfuseEffect currentEffect = plugin.getDataManager().getEffect(player, slot); if (currentEffect != null) return false; // Equipping the effect to the slot. - plugin.getDataManager().setEffect(player.getUniqueId(), slot, effect); + plugin.getDataManager().setEffect(player, slot, effect); new EffectEquipEvent(player, effect, slot).callEvent(); Message msg = new Message(MessageType.EFFECT_EQUIPPED); @@ -111,8 +111,8 @@ public void onPlayerConsume(PlayerItemConsumeEvent event) { @EventHandler public void onPlayerDeath(PlayerDeathEvent event) { Player player = event.getEntity(); - InfuseEffect effect1 = plugin.getDataManager().getEffect(player.getUniqueId(), "1"); - InfuseEffect effect2 = plugin.getDataManager().getEffect(player.getUniqueId(), "2"); + InfuseEffect effect1 = plugin.getDataManager().getEffect(player, "1"); + InfuseEffect effect2 = plugin.getDataManager().getEffect(player, "2"); String dropMode = plugin.getMainConfig().effectDrops(); Random rand = new Random(); switch (dropMode.toLowerCase()) { @@ -153,10 +153,10 @@ public void onPlayerDeath(PlayerDeathEvent event) { @EventHandler public void onPlayerJoin(PlayerJoinEvent event) { Player player = event.getPlayer(); - InfuseEffect effect = plugin.getDataManager().getEffect(player.getUniqueId(), "1"); + InfuseEffect effect = plugin.getDataManager().getEffect(player, "1"); if (effect != null) new EffectEquipEvent(player, effect, "1").callEvent(); - effect = plugin.getDataManager().getEffect(player.getUniqueId(), "2"); + effect = plugin.getDataManager().getEffect(player, "2"); if (effect != null) new EffectEquipEvent(player, effect, "2").callEvent(); } @@ -168,11 +168,11 @@ public void onPlayerJoin(PlayerJoinEvent event) { */ private void dropEffect(Player player, String slot) { // Getting the equipped effect from the data file. - InfuseEffect effect = plugin.getDataManager().getEffect(player.getUniqueId(), slot); + InfuseEffect effect = plugin.getDataManager().getEffect(player, slot); if (effect == null) return; // Removing the effect from the player. - plugin.getDataManager().removeEffect(player.getUniqueId(), slot); + plugin.getDataManager().removeEffect(player, slot); new EffectUnequipEvent(player, effect, slot).callEvent(); // Dropping the effect item at the player's location diff --git a/src/main/java/com/catadmirer/infuseSMP/GlobalLoop.java b/src/main/java/com/catadmirer/infuseSMP/GlobalLoop.java index 8d9c3f3e..788716ce 100644 --- a/src/main/java/com/catadmirer/infuseSMP/GlobalLoop.java +++ b/src/main/java/com/catadmirer/infuseSMP/GlobalLoop.java @@ -30,8 +30,8 @@ public void stop() { public void run() { for (Player player : Bukkit.getOnlinePlayers()) { // Getting the player's equipped effects - InfuseEffect lEffect = plugin.getDataManager().getEffect(player.getUniqueId(), "1"); - InfuseEffect rEffect = plugin.getDataManager().getEffect(player.getUniqueId(), "2"); + InfuseEffect lEffect = plugin.getDataManager().getEffect(player, "1"); + InfuseEffect rEffect = plugin.getDataManager().getEffect(player, "2"); // Applying passive effects to the player if (lEffect != null) { diff --git a/src/main/java/com/catadmirer/infuseSMP/HitTracker.java b/src/main/java/com/catadmirer/infuseSMP/HitTracker.java index 04887ad3..18a4a4e7 100644 --- a/src/main/java/com/catadmirer/infuseSMP/HitTracker.java +++ b/src/main/java/com/catadmirer/infuseSMP/HitTracker.java @@ -16,7 +16,7 @@ public class HitTracker implements Listener { private final Infuse plugin; private final Map hitTracker = new HashMap<>(); - Queue decayQueue = new ConcurrentLinkedQueue<>(); + final Queue decayQueue = new ConcurrentLinkedQueue<>(); public HitTracker(Infuse plugin) { this.plugin = plugin; @@ -89,7 +89,7 @@ public void onPlayerHit(EntityDamageByEntityEvent event) { decayQueue.remove(); decayTask.run(); } - }, hitCounterDecaySeconds * 20); + }, hitCounterDecaySeconds * 20L); } /** diff --git a/src/main/java/com/catadmirer/infuseSMP/Infuse.java b/src/main/java/com/catadmirer/infuseSMP/Infuse.java index 44bc4640..aadc6e8b 100644 --- a/src/main/java/com/catadmirer/infuseSMP/Infuse.java +++ b/src/main/java/com/catadmirer/infuseSMP/Infuse.java @@ -6,6 +6,9 @@ import com.catadmirer.infuseSMP.extraeffects.*; import com.catadmirer.infuseSMP.managers.*; import com.catadmirer.infuseSMP.placeholders.InfusePlaceholders; +import com.catadmirer.infuseSMP.playerdata.DataManager; +import com.catadmirer.infuseSMP.playerdata.H2DataManager; +import com.catadmirer.infuseSMP.playerdata.YamlDataManager; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonObject; @@ -41,7 +44,7 @@ public class Infuse extends JavaPlugin implements Listener { public static final Logger LOGGER = LoggerFactory.getLogger("Infuse"); public static final NamespacedKey EFFECT_KEY = new NamespacedKey("infuse", "effect_key"); - private final DataManager dataManager; + private DataManager dataManager; private final MainConfig mainConfig; private final GlobalLoop loop; private final RecipeManager recipeManager; @@ -54,7 +57,6 @@ public static Infuse getInstance() { public Infuse() { new ApophisManager(this); this.mainConfig = new MainConfig(this); - this.dataManager = new DataManager(this); this.loop = new GlobalLoop(this); this.recipeManager = new RecipeManager(this); this.particleManager = new ParticleManager(this); @@ -74,13 +76,26 @@ public void onEnable() { // Loading the config mainConfig.load(); - - // Loading the data manager - dataManager.load(); // Applying config updates MessageConfig.applyUpdates(); mainConfig.applyUpdates(); + + // Loading the data manager + // If no valid data manager is found, disable the plugin + dataManager = switch (mainConfig.storageMode().toLowerCase()) { + case "h2" -> new H2DataManager(this); + case "yaml" -> new YamlDataManager(this); + default -> null; + }; + + if (dataManager == null) { + LOGGER.error("Could not read a valid storage type from the config! Disabling Infuse."); + Bukkit.getPluginManager().disablePlugin(this); + return; + } + + dataManager.load(); dataManager.applyUpdates(); // Initializing the recipe manager @@ -139,7 +154,7 @@ private void registerCommands() { getCommand("draw").setExecutor(new Draw()); - getCommand("controls").setExecutor((sender, command, label, args) -> { + getCommand("controls").setExecutor((sender, _, _, args) -> { // Making sure only players can run the command if (!(sender instanceof Player player)) { sender.sendMessage(new Message(MessageType.ERROR_NOT_PLAYER).toComponent()); @@ -162,11 +177,11 @@ private void registerCommands() { } // Setting the control mode for the player - dataManager.setControlMode(player.getUniqueId(), choice); + dataManager.setControlMode(player, choice); player.addAttachment(this, "ability.use", choice.equals("command")); return true; }); - getCommand("controls").setTabCompleter((sender, command, label, args) -> { + getCommand("controls").setTabCompleter((_, _, _, args) -> { if (args.length == 1) { return Stream.of("command", "offhand").filter(opt -> opt.startsWith(args[0])).toList(); } @@ -242,9 +257,7 @@ public void onPlayerDeath(PlayerDeathEvent event) { if (dropHead) { ItemStack playerHead = new ItemStack(Material.PLAYER_HEAD); - playerHead.editMeta(SkullMeta.class, meta -> { - meta.setOwningPlayer(player); - }); + playerHead.editMeta(SkullMeta.class, meta -> meta.setOwningPlayer(player)); player.getWorld().dropItem(player.getLocation(), playerHead); } } @@ -261,9 +274,7 @@ private String getLatestVersion() { .uri(URI.create("https://api.modrinth.com/v2/project/infusesmp/version")) .build(); - HttpClient client = HttpClient.newHttpClient(); - - try { + try (HttpClient client = HttpClient.newHttpClient()) { HttpResponse response = client.send(request, BodyHandlers.ofString()); // Handling http error codes @@ -303,9 +314,8 @@ private void onJoin(PlayerJoinEvent event) { InfuseEffect.getRegisteredEffects().values().stream().map(recipeManager::getRecipeKey).forEach(player::discoverRecipe); // Telling the player their current control mode - String controlMode = dataManager.getControlMode(player.getUniqueId()); - if (controlMode == null) controlMode = "Offhand"; - boolean offhandEnabled = controlMode.equalsIgnoreCase("Offhand"); + String controlMode = dataManager.getControlMode(player); + boolean offhandEnabled = controlMode.equalsIgnoreCase("offhand"); player.addAttachment(this, "ability.use", !offhandEnabled); Message msg = new Message(MessageType.JOIN_ABILITY_NOTIFY); diff --git a/src/main/java/com/catadmirer/infuseSMP/MainConfig.java b/src/main/java/com/catadmirer/infuseSMP/MainConfig.java index 8a97e541..c93c8775 100644 --- a/src/main/java/com/catadmirer/infuseSMP/MainConfig.java +++ b/src/main/java/com/catadmirer/infuseSMP/MainConfig.java @@ -4,7 +4,6 @@ import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.plugin.Plugin; import java.io.File; import java.io.IOException; @@ -24,8 +23,6 @@ public MainConfig(Infuse plugin) { /** * Reloads the configuration. - * - * @return Whether the configuration was loaded successfully. */ public boolean load() { // Not doing anything if the plugin isn't enabled @@ -48,8 +45,7 @@ public boolean load() { } catch (InvalidConfigurationException e) { Infuse.LOGGER.warn("{} contains an invalid YAML configuration. Verify the contents of the file.", file.getName()); } catch (IOException e) { - Infuse.LOGGER.error("Could not find {}. Check that it exists.", file.getName()); - e.printStackTrace(); + Infuse.LOGGER.error("Could not find {}. Check that it exists.", file.getName(), e); } return false; @@ -57,8 +53,6 @@ public boolean load() { /** * Writes the config to the file. - * - * @return Whether or not the config was successfully written. */ public boolean save() { // Not doing anything if the plugin isn't enabled @@ -150,9 +144,8 @@ public boolean enableThief() { /** * Gets the amount of each effect that can be crafted - * + * * @param effect The effect to check - * * @return The number of effects that can be crafted of the specified {@link InfuseEffect}. */ public int getCraftLimit(InfuseEffect effect) { @@ -224,22 +217,6 @@ public float emeraldPercentExpToShare() { return Math.clamp((float) config.getDouble("emerald.percent_xp_to_share"), 0, 1); } - public void applyUpdates() { - if (!config.contains("invis_deaths")) config.set("invis_deaths", null); - if (!config.contains("invis.hide_kills")) config.set("invis.hide_kills", false); - if (!config.contains("invis.hide_deaths")) config.set("invis.hide_deaths", false); - if (!config.contains("haste.enchantment.looting_level")) config.set("haste.enchantment.looting_level", 5); - if (!config.contains("haste.enchantment.fortune_level")) config.set("haste.enchantment.fortune_level", 5); - if (!config.contains("haste.enchantment.efficiency_level")) config.set("haste.enchantment.efficiency_level", 10); - if (!config.contains("haste.enchantment.unbreaking_level")) config.set("haste.enchantment.unbreaking_level", 5); - if (!config.contains("hit_counter_decay_seconds")) config.set("hit_counter_decay_seconds", 15); - if (!config.contains("emerald.xp_stolen_per_hit")) config.set("emerald.xp_stolen_per_hit", 15); - if (!config.contains("emerald.xp_stolen_percent")) config.set("emerald.xp_stolen_percent", 1); - if (!config.contains("emerald.percent_xp_to_share")) config.set("emerald.percent_xp_to_share", 0.5); - - save(); - } - public int emeraldLootingLevel() { return config.getInt("emerald.enchantment.looting_level"); } @@ -255,4 +232,34 @@ public int hasteEfficiencyLevel() { public int hasteUnbreakingLevel() { return config.getInt("haste.enchantment.unbreaking_level"); } + + public String storageMode() { + return config.getString("storage_mode", "yaml"); + } + + public void applyUpdates() { + if (!config.contains("invis_deaths")) config.set("invis_deaths", null); + if (!config.contains("invis.hide_kills")) config.set("invis.hide_kills", false); + if (!config.contains("invis.hide_deaths")) config.set("invis.hide_deaths", false); + if (!config.contains("haste.enchantment.looting_level")) config.set("haste.enchantment.looting_level", 5); + if (!config.contains("haste.enchantment.fortune_level")) config.set("haste.enchantment.fortune_level", 5); + if (!config.contains("haste.enchantment.efficiency_level")) config.set("haste.enchantment.efficiency_level", 10); + if (!config.contains("haste.enchantment.unbreaking_level")) config.set("haste.enchantment.unbreaking_level", 5); + if (!config.contains("hit_counter_decay_seconds")) config.set("hit_counter_decay_seconds", 15); + if (!config.contains("emerald.xp_stolen_per_hit")) config.set("emerald.xp_stolen_per_hit", 15); + if (!config.contains("emerald.xp_stolen_percent")) config.set("emerald.xp_stolen_percent", 1); + if (!config.contains("emerald.percent_xp_to_share")) config.set("emerald.percent_xp_to_share", 0.5); + if (!config.contains("storage_mode")) { + config.set("storage_mode", "YAML"); + config.setComments("storage_mode", List.of("# Sets where the data will be stored.", + "# IMPORTANT: Data is not transferred between nodes. To move data to different storage types, export it to YAML then import it.", + "# (Exported data is the same format as the YAML storage mode, so no exporting is needed if you already use the YAML config.)", + "# This config IS NOT UPDATED when you use `/infuse reload`. You need to RESTART THE SERVER to change this config", + "# Supported values:", + "# - \"H2\"", + "# - \"YAML\"")); + } + + save(); + } } diff --git a/src/main/java/com/catadmirer/infuseSMP/Message.java b/src/main/java/com/catadmirer/infuseSMP/Message.java index 09585c96..3ae4f075 100644 --- a/src/main/java/com/catadmirer/infuseSMP/Message.java +++ b/src/main/java/com/catadmirer/infuseSMP/Message.java @@ -9,7 +9,7 @@ public class Message { private String message; - private List placeholders; + private final List placeholders; public Message(MessageType messageType) { message = MessageConfig.getMessage(messageType); @@ -53,7 +53,7 @@ public Component toComponent() { throw new IllegalStateException("Not all placeholders have been registered."); } - return MiniMessage.miniMessage().deserialize("" + toString()); + return MiniMessage.miniMessage().deserialize("" + this); } /** @@ -67,7 +67,7 @@ public static Component toComponent(String message) { return MiniMessage.miniMessage().deserialize("" + message); } - public static enum MessageType { + public enum MessageType { EFFECT_BROADCAST(List.of("player", "item", "x", "y", "z", "dimension"), "🧪 %player% is cooking up the %item% at %x%, %y%, %z%... %dimension%"), DISCORD_BROADCAST(List.of("player", "item", "x", "y", "z", "dimension"), "%player% is cooking up the %item% at %x%, %y%, %z% in %dimension% @everyone"), EFFECT_FINISHED(List.of("item"), "%item% has been brewed!"), diff --git a/src/main/java/com/catadmirer/infuseSMP/MessageConfig.java b/src/main/java/com/catadmirer/infuseSMP/MessageConfig.java index 041e75d2..a527f1af 100644 --- a/src/main/java/com/catadmirer/infuseSMP/MessageConfig.java +++ b/src/main/java/com/catadmirer/infuseSMP/MessageConfig.java @@ -52,8 +52,8 @@ public static boolean load(Plugin plugin) { * Creating the config file. If it doesn't exist, it loads the default config. If the file does * exist, it will only replace it if the parameter is true. * - * @param replace Whether or not to replace the config file with the default configs. - * @return Whether or not the file was created successfully. + * @param replace Whether to replace the config file with the default configs. + * @return Whether the file was created successfully. */ public static boolean createFile(Plugin plugin, boolean replace) { // Creating the file if it doesn't exist. diff --git a/src/main/java/com/catadmirer/infuseSMP/PlayerSwapHandItemsListener.java b/src/main/java/com/catadmirer/infuseSMP/PlayerSwapHandItemsListener.java index b22b2f96..170edb78 100644 --- a/src/main/java/com/catadmirer/infuseSMP/PlayerSwapHandItemsListener.java +++ b/src/main/java/com/catadmirer/infuseSMP/PlayerSwapHandItemsListener.java @@ -2,8 +2,7 @@ import com.catadmirer.infuseSMP.effects.InfuseEffect; import com.catadmirer.infuseSMP.managers.CooldownManager; -import com.catadmirer.infuseSMP.managers.DataManager; - +import com.catadmirer.infuseSMP.playerdata.DataManager; import java.util.UUID; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -19,7 +18,7 @@ public PlayerSwapHandItemsListener(DataManager dataManager) { /** * Listens for when the player swaps the items in their main and offhand. - * When they do so, it will be used to activate their left or right spark based on whether or not they are crouching. + * When they do so, it will be used to activate their left or right spark based on whether they are crouching. * * @param event The {@link PlayerSwapHandItemsEvent} to process */ @@ -27,11 +26,11 @@ public PlayerSwapHandItemsListener(DataManager dataManager) { public void onPlayerSwapHandItems(PlayerSwapHandItemsEvent event) { Player player = event.getPlayer(); UUID playerUUID = player.getUniqueId(); - String data = dataManager.getControlMode(playerUUID); + String data = dataManager.getControlMode(player); if (data.equals("offhand")) { // Getting the effect equipped in each slot - InfuseEffect lEffect = dataManager.getEffect(player.getUniqueId(), "1"); - InfuseEffect rEffect = dataManager.getEffect(player.getUniqueId(), "2"); + InfuseEffect lEffect = dataManager.getEffect(player, "1"); + InfuseEffect rEffect = dataManager.getEffect(player, "2"); // Activating the left effect's spark if the player was sneaking and the effect wasn't on cooldown. if (lEffect != null && !player.isSneaking() && !CooldownManager.isOnCooldown(playerUUID, lEffect.getKey())) { diff --git a/src/main/java/com/catadmirer/infuseSMP/commands/Abilities.java b/src/main/java/com/catadmirer/infuseSMP/commands/Abilities.java index 943b2d8f..3fa3a5f8 100644 --- a/src/main/java/com/catadmirer/infuseSMP/commands/Abilities.java +++ b/src/main/java/com/catadmirer/infuseSMP/commands/Abilities.java @@ -3,15 +3,14 @@ import com.catadmirer.infuseSMP.Infuse; import com.catadmirer.infuseSMP.Message; import com.catadmirer.infuseSMP.Message.MessageType; - -import java.util.UUID; - import com.catadmirer.infuseSMP.effects.InfuseEffect; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.jspecify.annotations.NullMarked; +@NullMarked public class Abilities implements CommandExecutor { private final Infuse plugin; @@ -25,8 +24,6 @@ public boolean onCommand(CommandSender sender, Command command, String label, St return true; } - final UUID playerUUID = player.getUniqueId(); - // Finding which slot to activate the spark for. String slot; if (label.contains("lspark")) { @@ -39,7 +36,7 @@ public boolean onCommand(CommandSender sender, Command command, String label, St } // Getting the name of the equipped effect. - InfuseEffect equippedEffect = plugin.getDataManager().getEffect(playerUUID, slot); + InfuseEffect equippedEffect = plugin.getDataManager().getEffect(player, slot); // Handling if the slot is empty. if (equippedEffect == null) { diff --git a/src/main/java/com/catadmirer/infuseSMP/commands/ClearEffects.java b/src/main/java/com/catadmirer/infuseSMP/commands/ClearEffects.java index 1b8e3b76..da462d87 100644 --- a/src/main/java/com/catadmirer/infuseSMP/commands/ClearEffects.java +++ b/src/main/java/com/catadmirer/infuseSMP/commands/ClearEffects.java @@ -2,7 +2,7 @@ import com.catadmirer.infuseSMP.Message; import com.catadmirer.infuseSMP.Message.MessageType; -import com.catadmirer.infuseSMP.managers.DataManager; +import com.catadmirer.infuseSMP.playerdata.DataManager; import org.bukkit.Bukkit; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; @@ -28,8 +28,8 @@ public boolean onCommand(@NonNull CommandSender sender, @NonNull Command command Player target = Bukkit.getPlayer(args[0]); if (target != null) { - dataManager.removeEffect(target.getUniqueId(), "1"); - dataManager.removeEffect(target.getUniqueId(), "2"); + dataManager.removeEffect(target, "1"); + dataManager.removeEffect(target, "2"); } return true; diff --git a/src/main/java/com/catadmirer/infuseSMP/commands/DrainCommand.java b/src/main/java/com/catadmirer/infuseSMP/commands/DrainCommand.java index 45c11276..cd798b4f 100644 --- a/src/main/java/com/catadmirer/infuseSMP/commands/DrainCommand.java +++ b/src/main/java/com/catadmirer/infuseSMP/commands/DrainCommand.java @@ -36,7 +36,7 @@ public boolean onCommand(@NonNull CommandSender sender, @NonNull Command command } // Getting the mapping from the slot - InfuseEffect effect = plugin.getDataManager().getEffect(player.getUniqueId(), slot); + InfuseEffect effect = plugin.getDataManager().getEffect(player, slot); // Handling an invalid or empty mapping if (effect == null) { @@ -55,7 +55,7 @@ public boolean onCommand(@NonNull CommandSender sender, @NonNull Command command } // Removing the effect from the player - plugin.getDataManager().removeEffect(player.getUniqueId(), slot); + plugin.getDataManager().removeEffect(player, slot); Message msg = new Message(MessageType.DRAIN_SUCCESS); msg.applyPlaceholder("effect_name", effect.getName()); player.sendMessage(msg.toComponent()); diff --git a/src/main/java/com/catadmirer/infuseSMP/commands/InfuseCommand.java b/src/main/java/com/catadmirer/infuseSMP/commands/InfuseCommand.java index 1fc6572e..c3887aec 100644 --- a/src/main/java/com/catadmirer/infuseSMP/commands/InfuseCommand.java +++ b/src/main/java/com/catadmirer/infuseSMP/commands/InfuseCommand.java @@ -135,7 +135,7 @@ public boolean onCommand(@NonNull CommandSender sender, @NonNull Command command } // Setting the effect - plugin.getDataManager().setEffect(new EffectSlot(target, effect, args[3])); + plugin.getDataManager().setEffect(target, args[3], effect); msg = new Message(MessageType.INFUSE_SETEFFECT_SUCCESS); msg.applyPlaceholder("slot", slot); msg.applyPlaceholder("player_name", target.getName()); @@ -161,8 +161,8 @@ public boolean onCommand(@NonNull CommandSender sender, @NonNull Command command } // Removing the effects from the player - plugin.getDataManager().removeEffect(target.getUniqueId(), "1"); - plugin.getDataManager().removeEffect(target.getUniqueId(), "2"); + plugin.getDataManager().removeEffect(target, "1"); + plugin.getDataManager().removeEffect(target, "2"); msg = new Message(MessageType.INFUSE_CLEAREFFECTS_SUCCESS); msg.applyPlaceholder("player_name", target.getName()); player.sendMessage(msg.toComponent()); @@ -205,7 +205,7 @@ public boolean onCommand(@NonNull CommandSender sender, @NonNull Command command } // Setting the control mode for the user. - plugin.getDataManager().setControlMode(player.getUniqueId(), choice); + plugin.getDataManager().setControlMode(player, choice); // Assigning the permission for offhand use if the user chose offhand mode boolean offhandEnabled = choice.equalsIgnoreCase("offhand"); diff --git a/src/main/java/com/catadmirer/infuseSMP/commands/Recipes.java b/src/main/java/com/catadmirer/infuseSMP/commands/Recipes.java index ba638a06..5c3b0b7a 100644 --- a/src/main/java/com/catadmirer/infuseSMP/commands/Recipes.java +++ b/src/main/java/com/catadmirer/infuseSMP/commands/Recipes.java @@ -20,6 +20,7 @@ import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import org.jspecify.annotations.NullMarked; public class Recipes implements CommandExecutor, Listener { private static Infuse plugin; @@ -28,6 +29,7 @@ public Recipes(Infuse plugin) { Recipes.plugin = plugin; } + @NullMarked @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { if (sender instanceof Player player) { diff --git a/src/main/java/com/catadmirer/infuseSMP/commands/SwapEffects.java b/src/main/java/com/catadmirer/infuseSMP/commands/SwapEffects.java index e092a272..87405236 100644 --- a/src/main/java/com/catadmirer/infuseSMP/commands/SwapEffects.java +++ b/src/main/java/com/catadmirer/infuseSMP/commands/SwapEffects.java @@ -8,7 +8,9 @@ import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.jspecify.annotations.NullMarked; +@NullMarked public class SwapEffects implements CommandExecutor { private final Infuse plugin; @@ -24,12 +26,12 @@ public boolean onCommand(CommandSender sender, Command command, String label, St } // Getting the equipped effects - InfuseEffect effect1 = plugin.getDataManager().getEffect(player.getUniqueId(), "1"); - InfuseEffect effect2 = plugin.getDataManager().getEffect(player.getUniqueId(), "2"); + InfuseEffect effect1 = plugin.getDataManager().getEffect(player, "1"); + InfuseEffect effect2 = plugin.getDataManager().getEffect(player, "2"); // Swapping the effects - plugin.getDataManager().setEffect(player.getUniqueId(), "1", effect2); - plugin.getDataManager().setEffect(player.getUniqueId(), "2", effect1); + plugin.getDataManager().setEffect(player, "1", effect2); + plugin.getDataManager().setEffect(player, "2", effect1); player.sendMessage(new Message(MessageType.SWAP_SUCCESS).toComponent()); return true; } diff --git a/src/main/java/com/catadmirer/infuseSMP/commands/TrustCommand.java b/src/main/java/com/catadmirer/infuseSMP/commands/TrustCommand.java index f1db0503..637f10eb 100644 --- a/src/main/java/com/catadmirer/infuseSMP/commands/TrustCommand.java +++ b/src/main/java/com/catadmirer/infuseSMP/commands/TrustCommand.java @@ -2,13 +2,15 @@ import com.catadmirer.infuseSMP.Message; import com.catadmirer.infuseSMP.Message.MessageType; -import com.catadmirer.infuseSMP.managers.DataManager; +import com.catadmirer.infuseSMP.playerdata.DataManager; import org.bukkit.Bukkit; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.jspecify.annotations.NullMarked; +@NullMarked public class TrustCommand implements CommandExecutor { private final DataManager dataManager; @@ -39,7 +41,7 @@ public boolean onCommand(CommandSender sender, Command command, String label, St return true; } - // Preventing the caster from trusting/untrusting themself. + // Preventing the caster from trusting/untrusting themselves. if (caster.getUniqueId().equals(target.getUniqueId())) { caster.sendMessage(new Message(MessageType.TRUST_SELF).toComponent()); return true; diff --git a/src/main/java/com/catadmirer/infuseSMP/effects/Emerald.java b/src/main/java/com/catadmirer/infuseSMP/effects/Emerald.java index 87c1d548..d7c53230 100644 --- a/src/main/java/com/catadmirer/infuseSMP/effects/Emerald.java +++ b/src/main/java/com/catadmirer/infuseSMP/effects/Emerald.java @@ -225,6 +225,7 @@ public void emeraldExpMultiplier(PlayerPickupExperienceEvent event) { orb.setExperience(newAmount); } + @SuppressWarnings("unchecked") @EventHandler public void emeraldEnchantBonus(PrepareItemEnchantEvent event) { ItemStack item = event.getItem(); @@ -278,15 +279,15 @@ public void emeraldEnchantBonus(PrepareItemEnchantEvent event) { if (!list.isEmpty()) { EnchantmentInstance enchantmentinstance = (EnchantmentInstance) list.get(random.nextInt(list.size())); - Holder enchantment = null; + Holder enchantment; int level; Class clazz = EnchantmentInstance.class; if (!clazz.isRecord()) { // Handling pre-1.21.5 - enchantment = (Holder) clazz.getField("enchantment").get(enchantmentinstance); - level = (int) clazz.getField("level").get(enchantmentinstance); + enchantment = (Holder) clazz.getDeclaredField("enchantment").get(enchantmentinstance); + level = (int) clazz.getDeclaredField("level").get(enchantmentinstance); } else { RecordComponent[] components = clazz.getRecordComponents(); enchantment = (Holder) components[0].getAccessor().invoke(enchantmentinstance); diff --git a/src/main/java/com/catadmirer/infuseSMP/effects/Ender.java b/src/main/java/com/catadmirer/infuseSMP/effects/Ender.java index 25a674c0..b964876b 100644 --- a/src/main/java/com/catadmirer/infuseSMP/effects/Ender.java +++ b/src/main/java/com/catadmirer/infuseSMP/effects/Ender.java @@ -181,6 +181,11 @@ public void onEntityDamage(EntityDamageEvent event) { if (cursedUUID == damagedUUID) continue; Player player = Bukkit.getPlayer(cursedUUID); + if (player == null) { + cursedPlayers.remove(cursedUUID); + continue; + } + player.damage(event.getDamage(), fakeSource); } } diff --git a/src/main/java/com/catadmirer/infuseSMP/effects/Feather.java b/src/main/java/com/catadmirer/infuseSMP/effects/Feather.java index 3a1caab9..51396a3a 100644 --- a/src/main/java/com/catadmirer/infuseSMP/effects/Feather.java +++ b/src/main/java/com/catadmirer/infuseSMP/effects/Feather.java @@ -73,9 +73,7 @@ public void activateSpark(Player owner) { CooldownManager.setTimes(playerUUID, "feather", duration, cooldown); - owner.getScheduler().runDelayed(plugin, t -> { - CooldownManager.setDuration(playerUUID, "feathermace", 5L); - }, null, 10); + owner.getScheduler().runDelayed(plugin, _ -> CooldownManager.setDuration(playerUUID, "feathermace", 5L), null, 10); } @Override @@ -121,9 +119,7 @@ public void FeatherLand(PlayerMoveEvent event) { Vector knockback = new Vector(0, 1, 0); target.setVelocity(target.getVelocity().add(knockback)); Location anchor = target.getLocation(); - Bukkit.getRegionScheduler().run(plugin, anchor, (task) -> { - target.addPotionEffect(new PotionEffect(PotionEffectType.SLOW_FALLING, 80, 0, false, false, false)); - }); + Bukkit.getRegionScheduler().run(plugin, anchor, _ -> target.addPotionEffect(new PotionEffect(PotionEffectType.SLOW_FALLING, 80, 0, false, false, false))); } world.spawnParticle(Particle.CLOUD, loc, 50, 0, 0, 0, 2); @@ -176,9 +172,7 @@ public void onPlayerRightClickWindcharge(PlayerInteractEvent event) { if (!event.getAction().isRightClick()) return; Location anchor = player.getLocation(); - Bukkit.getRegionScheduler().runDelayed(plugin, anchor, (task) -> { - player.setCooldown(Material.WIND_CHARGE, 5); - }, 1L); + Bukkit.getRegionScheduler().runDelayed(plugin, anchor, _ -> player.setCooldown(Material.WIND_CHARGE, 5), 1L); } @EventHandler diff --git a/src/main/java/com/catadmirer/infuseSMP/effects/Frost.java b/src/main/java/com/catadmirer/infuseSMP/effects/Frost.java index ab07ec92..9bb5a3d0 100644 --- a/src/main/java/com/catadmirer/infuseSMP/effects/Frost.java +++ b/src/main/java/com/catadmirer/infuseSMP/effects/Frost.java @@ -259,6 +259,7 @@ public void onPlayerAttack(EntityDamageByEntityEvent event) { if (!(event.getDamager() instanceof Player attacker)) return; if (!attacker.hasPotionEffect(PotionEffectType.UNLUCK)) return; PotionEffect effect = attacker.getPotionEffect(PotionEffectType.UNLUCK); + if (effect == null) return; if (effect.getAmplifier() >= 0 && frozenAttackers.contains(attacker.getUniqueId()) && event.getEntity() instanceof Player target) { target.setFreezeTicks(200); } diff --git a/src/main/java/com/catadmirer/infuseSMP/effects/Heart.java b/src/main/java/com/catadmirer/infuseSMP/effects/Heart.java index 7871c9df..fd72df5b 100644 --- a/src/main/java/com/catadmirer/infuseSMP/effects/Heart.java +++ b/src/main/java/com/catadmirer/infuseSMP/effects/Heart.java @@ -105,7 +105,7 @@ public Message getLore() { private void showAndUpdateHealthAboveEntity(Entity player) { Location ploc = player.getLocation().add(0, 2.5, 0); - TextDisplay as = (TextDisplay) ploc.getWorld().spawn(ploc, TextDisplay.class); + TextDisplay as = ploc.getWorld().spawn(ploc, TextDisplay.class); as.setGravity(false); as.setCustomNameVisible(true); diff --git a/src/main/java/com/catadmirer/infuseSMP/effects/InfuseEffect.java b/src/main/java/com/catadmirer/infuseSMP/effects/InfuseEffect.java index 0e2b9c5c..ae977fd2 100644 --- a/src/main/java/com/catadmirer/infuseSMP/effects/InfuseEffect.java +++ b/src/main/java/com/catadmirer/infuseSMP/effects/InfuseEffect.java @@ -159,7 +159,7 @@ public ItemStack createItem() { * * @param item The item to check. * - * @return Whether or not the item was created by this effect. + * @return Whether the item was created by this effect. */ public boolean itemMatches(@Nullable ItemStack item) { if (item == null) return false; @@ -187,7 +187,7 @@ public int serialize() { /** * Deserializes an InfuseEffect from an int - * + *

* The first two digits of an infuse effect are the effect id. IDs 0-12 are taken by the base Effects. * If the number is >= 100, then the effect will be converted to its augmented form. * diff --git a/src/main/java/com/catadmirer/infuseSMP/effects/Regen.java b/src/main/java/com/catadmirer/infuseSMP/effects/Regen.java index 0aa92b5c..e0ade51f 100644 --- a/src/main/java/com/catadmirer/infuseSMP/effects/Regen.java +++ b/src/main/java/com/catadmirer/infuseSMP/effects/Regen.java @@ -132,9 +132,7 @@ public void regenCanAlwaysEat(PlayerInteractEvent event) { meta.setFood(foodComp); }); } else { - event.getItem().editMeta(meta -> { - meta.setFood(null); - }); + event.getItem().editMeta(meta -> meta.setFood(null)); } } diff --git a/src/main/java/com/catadmirer/infuseSMP/effects/Strength.java b/src/main/java/com/catadmirer/infuseSMP/effects/Strength.java index 746e0812..41037eb7 100644 --- a/src/main/java/com/catadmirer/infuseSMP/effects/Strength.java +++ b/src/main/java/com/catadmirer/infuseSMP/effects/Strength.java @@ -169,8 +169,6 @@ public void strengthLengthenShieldCooldown(EntityDamageByEntityEvent event) { player.getWorld().playSound(player.getLocation(), Sound.ITEM_SHIELD_BREAK, 1, 1); // TODO: Test if this can be removed - Bukkit.getScheduler().runTaskLater(plugin, () -> { - player.setCooldown(Material.SHIELD, 200); - }, 20L); + Bukkit.getScheduler().runTaskLater(plugin, () -> player.setCooldown(Material.SHIELD, 200), 20L); } } \ No newline at end of file diff --git a/src/main/java/com/catadmirer/infuseSMP/effects/Thunder.java b/src/main/java/com/catadmirer/infuseSMP/effects/Thunder.java index fb3ac846..60a39019 100644 --- a/src/main/java/com/catadmirer/infuseSMP/effects/Thunder.java +++ b/src/main/java/com/catadmirer/infuseSMP/effects/Thunder.java @@ -146,7 +146,7 @@ public static void strikeLighting(LivingEntity target, LivingEntity attacker) { * This is a recursive function that runs up to 10 times to strike nearby entities with lightning. * The function should be called with a list containing only the attacking entity. * - * @param targets The list of targets that have been hit by the lightning bolt, with the exception of the first entry which is the attacker. + * @param targets The list of targets that have been hit by the lightning bolt, except for the first entry which is the attacker. * * @throws InvalidParameterException If the targets parameter is null or empty. */ @@ -176,8 +176,8 @@ private void chainLightning(List targets) { } } - //// Listeners //// - //// These are only registered once, so they need to be able to handle being used for every player, no matter what effects they actually have + // Listeners // + // These are only registered once, so they need to be able to handle being used for every player, no matter what effects they actually have /** * Tracking the number of hits a player has. @@ -253,7 +253,7 @@ public void onPlayerHit(EntityDamageByEntityEvent event) { decayQueue.remove(); decayTask.run(); } - }, hitCounterDecaySeconds * 20); + }, hitCounterDecaySeconds * 20L); } @EventHandler diff --git a/src/main/java/com/catadmirer/infuseSMP/managers/ActionBarUpdater.java b/src/main/java/com/catadmirer/infuseSMP/managers/ActionBarUpdater.java index 6d1346a2..a37d89e1 100644 --- a/src/main/java/com/catadmirer/infuseSMP/managers/ActionBarUpdater.java +++ b/src/main/java/com/catadmirer/infuseSMP/managers/ActionBarUpdater.java @@ -36,7 +36,7 @@ public void run() { String rightPad = ""; // Loading info for the first effect - effect = plugin.getDataManager().getEffect(uuid, "1"); + effect = plugin.getDataManager().getEffect(player, "1"); if (effect != null) { leftEmoji = effect.getIcon() + "\ue904"; @@ -55,7 +55,7 @@ public void run() { } // Loading info for the second effect - effect = plugin.getDataManager().getEffect(uuid, "2"); + effect = plugin.getDataManager().getEffect(player, "2"); if (effect != null) { rightEmoji = effect.getIcon() + "\ue904"; @@ -92,9 +92,9 @@ public void run2() { String placeholder = plugin.getMainConfig().emptyEffectIcon() ? "\uE901" : ""; - String lSide = ""; + String lSide; // Loading info for the first effect - effect = plugin.getDataManager().getEffect(uuid, "1"); + effect = plugin.getDataManager().getEffect(player, "1"); if (effect == null) { lSide = " " + placeholder + "\ue904"; } else { @@ -113,7 +113,7 @@ public void run2() { // Loading info for the second effect String rSide; - effect = plugin.getDataManager().getEffect(uuid, "2"); + effect = plugin.getDataManager().getEffect(player, "2"); if (effect == null) { rSide = "" + placeholder + "\ue904 "; } else { diff --git a/src/main/java/com/catadmirer/infuseSMP/managers/ApophisManager.java b/src/main/java/com/catadmirer/infuseSMP/managers/ApophisManager.java index d2a208db..464b1904 100644 --- a/src/main/java/com/catadmirer/infuseSMP/managers/ApophisManager.java +++ b/src/main/java/com/catadmirer/infuseSMP/managers/ApophisManager.java @@ -17,8 +17,9 @@ import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; -public class ApophisManager { +public class ApophisManager implements Listener { private static final MiniMessage mm = MiniMessage.miniMessage(); private final Infuse plugin; @@ -46,7 +47,7 @@ public void initDisguise(Player target) { FileWriter writer = new FileWriter(disguiseFile); Optional textures = target.getPlayerProfile().getProperties().stream().filter(property -> "textures".equals(property.getName())).findFirst(); - // Writing the urls to disk + // Writing the URLs to disk writer.write(mm.serialize(target.displayName())); writer.write("\n"); if (textures.isEmpty()) { @@ -87,7 +88,7 @@ public void unequipApophis(EffectUnequipEvent event) { Player target = event.getPlayer(); if (!target.isOnline()) { - Infuse.LOGGER.warn("Could not remove {0}'s disguise as they are not online.", target.getName()); + Infuse.LOGGER.warn("Could not remove {}'s disguise as they are not online.", target.getName()); return; } @@ -124,13 +125,13 @@ public void unequipApophis(EffectUnequipEvent event) { profile.setProperty(new ProfileProperty("textures", value, signature)); target.setPlayerProfile(profile); - } catch (FileNotFoundException err) {} + } catch (FileNotFoundException err) { + Infuse.LOGGER.error("Could not find {}.", disguiseFile.getPath(), err); + } // Deleting the disguise file if (disguiseFile.exists()) { disguiseFile.delete(); } - - return; } } \ No newline at end of file diff --git a/src/main/java/com/catadmirer/infuseSMP/managers/CooldownManager.java b/src/main/java/com/catadmirer/infuseSMP/managers/CooldownManager.java index 11c7ba84..3ab978c9 100644 --- a/src/main/java/com/catadmirer/infuseSMP/managers/CooldownManager.java +++ b/src/main/java/com/catadmirer/infuseSMP/managers/CooldownManager.java @@ -7,7 +7,6 @@ public class CooldownManager { private static final Map> cooldowns = new ConcurrentHashMap<>(); private static final Map> durations = new ConcurrentHashMap<>(); - public static final Map displayNames = new ConcurrentHashMap<>(); public static void setTimes(UUID playerUUID, String key, long durationSeconds, long cooldownSeconds) { setCooldown(playerUUID, key, cooldownSeconds + durationSeconds); @@ -75,7 +74,7 @@ public static void clearSpecificCooldown(UUID playerUUID, String key) { } } - public static void cleanupAllExpiredCooldowns() { + public static void cleanupExpiredCooldowns() { long currentTime = System.currentTimeMillis(); for (UUID playerUUID : cooldowns.keySet()) { diff --git a/src/main/java/com/catadmirer/infuseSMP/managers/DataManager.java b/src/main/java/com/catadmirer/infuseSMP/managers/DataManager.java deleted file mode 100644 index 72363dac..00000000 --- a/src/main/java/com/catadmirer/infuseSMP/managers/DataManager.java +++ /dev/null @@ -1,244 +0,0 @@ -package com.catadmirer.infuseSMP.managers; - -import com.catadmirer.infuseSMP.Infuse; -import com.catadmirer.infuseSMP.effects.InfuseEffect; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; -import org.bukkit.configuration.InvalidConfigurationException; -import org.bukkit.configuration.file.YamlConfiguration; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Scanner; -import java.util.UUID; - -public class DataManager { - private final Infuse plugin; - private final File dataFile; - private final YamlConfiguration config; - - public DataManager(Infuse plugin) { - this.plugin = plugin; - this.dataFile = new File(plugin.getDataFolder(), "data/playerdata.yml"); - this.config = YamlConfiguration.loadConfiguration(dataFile); - } - - /** - * Reloads the configuration. - * - * @return Whether the configuration was loaded successfully. - */ - public boolean load() { - if (!plugin.isEnabled()) { - Infuse.LOGGER.error("Infuse not loaded, cannot load {}.", dataFile.getName()); - return false; - } - - // Creating the file if it doesn't exist. - // If the function returns false, the load function fails too. - if (!createFile(false)) { - return false; - } - - // Loading the config - try { - config.load(dataFile); - Infuse.LOGGER.info("Successfully loaded {}", dataFile.getName()); - return true; - } catch (InvalidConfigurationException err) { - Infuse.LOGGER.warn("{} contains an invalid YAML configuration. Verify the contents of the file.", dataFile.getName()); - } catch (IOException err) { - Infuse.LOGGER.error("Could not find {}. Check that it exists.", dataFile.getName()); - } - - return false; - } - - /** - * Writes the config to the file. - * - * @return Whether or not the config was successfully written. - */ - public boolean save() { - // Getting a plugin instance to use - if (!plugin.isEnabled()) { - Infuse.LOGGER.error("Infuse not loaded, cannot save the {}.", dataFile.getName()); - return false; - } - - // Creating the file if it doesn't exist. - // If the function returns false, the load function fails too. - if (!createFile(false)) { - return false; - } - - // Saving the config - try { - config.save(dataFile); - Infuse.LOGGER.info("Saved {}", dataFile.getName()); - return true; - } catch (IOException e) { - Infuse.LOGGER.warn("Could not save {}. Make sure the user has write permissions.", dataFile.getName()); - } - - return false; - } - - /** - * Creating the config file. If it doesn't exist, it loads the default config. If the file does - * exist, it will only replace it if the parameter is true. - * - * @param replace Whether or not to replace the config file with the default configs. - * @return Whether or not the file was created successfully. - */ - public boolean createFile(boolean replace) { - // Getting a plugin instance to use - if (!plugin.isEnabled()) { - Infuse.LOGGER.error("Infuse not loaded, cannot create default {}.", dataFile.getName()); - return false; - } - - // Creating the file if it doesn't exist. - if (!dataFile.exists()) { - try { - dataFile.getParentFile().mkdirs(); - dataFile.createNewFile(); - } catch (IOException e) { - Infuse.LOGGER.error("Could not create {}. Make sure the user has the right permissions.", dataFile.getName()); - return false; - } - } - - return true; - } - - public int getExistingCount(InfuseEffect effect) { - return config.getInt("existing-effects." + effect.getKey(), 0); - } - - public void setExistingCount(InfuseEffect effect, int crafted) { - config.set("existing-effects." + effect.getKey(), crafted); - - save(); - } - - public List getTrusted(OfflinePlayer truster) { - return new ArrayList<>(config.getStringList(truster.getUniqueId() + ".trust").stream().map(UUID::fromString).map(Bukkit::getOfflinePlayer).toList()); - } - - public void setTrusted(OfflinePlayer truster, List trusted) { - config.set(truster.getUniqueId() + ".trust", trusted.stream().map(OfflinePlayer::getUniqueId).map(UUID::toString).toList()); - save(); - } - - public void addTrust(OfflinePlayer caster, OfflinePlayer toTrust) { - List trustedPlayers = getTrusted(caster); - trustedPlayers.add(toTrust); - - setTrusted(caster, trustedPlayers); - } - - public void removeTrust(OfflinePlayer caster, OfflinePlayer trusted) { - List trustedSet = getTrusted(caster); - trustedSet.remove(trusted); - - setTrusted(caster, trustedSet); - } - - public boolean isTrusted(OfflinePlayer caster, OfflinePlayer trusted) { - if (caster == null || trusted == null) return false; - if (caster.getUniqueId().equals(trusted.getUniqueId())) return true; - - return getTrusted(caster).contains(trusted); - } - - public void setEffect(UUID owner, String slot, InfuseEffect effect) { - String key = owner.toString() + "." + slot; - if (effect == null) { - config.set(key, null); - } else { - config.set(key, effect.getKey()); - } - save(); - } - - @Nullable - public InfuseEffect getEffect(UUID playerUUID, String slot) { - String effectKey = config.getString(playerUUID.toString() + "." + slot, null); - InfuseEffect effect = InfuseEffect.fromString(effectKey); - if (effectKey != null && effect == null) { - Infuse.LOGGER.warn("No valid ability found for the equipped effect."); - } - - return effect; - } - - public boolean hasEffect(OfflinePlayer player, InfuseEffect effect) { - return hasEffect(player, effect, false); - } - - public boolean hasEffect(OfflinePlayer player, InfuseEffect effect, boolean differentiateAugmented) { - return hasEffect(player, effect, differentiateAugmented, "1") || hasEffect(player, effect, differentiateAugmented, "2"); - } - - public boolean hasEffect(OfflinePlayer player, InfuseEffect effect, String slot) { - return hasEffect(player, effect, false, slot); - } - - public boolean hasEffect(OfflinePlayer player, InfuseEffect effect, boolean differentiateAugmented, String slot) { - InfuseEffect equippedEffect = getEffect(player.getUniqueId(), slot); - - if (equippedEffect == null) return false; - - if (differentiateAugmented) { - return effect.equals(equippedEffect); - } - - return effect.getId() == equippedEffect.getId(); - } - - public void removeEffect(UUID playerUUID, String slot) { - config.set(playerUUID.toString() + "." + slot, null); - save(); - } - - public void setControlMode(UUID playerUUID, String defaultMode) { - config.set(playerUUID.toString() + ".controls", defaultMode); - save(); - } - - public String getControlMode(UUID playerUUID) { - return config.getString(playerUUID.toString() + ".controls", "offhand"); - } - - public void applyUpdates() { - try { - Scanner scanner = new Scanner(dataFile); - StringBuffer inputBuffer = new StringBuffer(); - String line; - - while (scanner.hasNextLine()) { - line = scanner.nextLine(); - - // Replacing old configs - if (line.startsWith("effects-crafted")) { - line = line.replace("effects-crafted", "existing-effects"); - } - inputBuffer.append(line); - inputBuffer.append('\n'); - } - scanner.close(); - - // Emptying the string buffer back into the file - FileOutputStream fileOut = new FileOutputStream(dataFile); - fileOut.write(inputBuffer.toString().getBytes()); - fileOut.close(); - } catch (IOException err) { - - } - } -} \ No newline at end of file diff --git a/src/main/java/com/catadmirer/infuseSMP/managers/EffectCraftManager.java b/src/main/java/com/catadmirer/infuseSMP/managers/EffectCraftManager.java index beba5310..b641d537 100644 --- a/src/main/java/com/catadmirer/infuseSMP/managers/EffectCraftManager.java +++ b/src/main/java/com/catadmirer/infuseSMP/managers/EffectCraftManager.java @@ -71,8 +71,7 @@ private void sendToDiscord(String webhookUrl, String message) { .header("Content-Type", "application/json") .POST(BodyPublishers.ofString(payload)).build(); - HttpClient client = HttpClient.newHttpClient(); - try { + try (HttpClient client = HttpClient.newHttpClient()) { HttpResponse response = client.send(request, BodyHandlers.discarding()); // Checking the response status code @@ -80,7 +79,7 @@ private void sendToDiscord(String webhookUrl, String message) { if (status == 200) { Infuse.LOGGER.info("Message sent to Discord!"); } else { - Infuse.LOGGER.info("Error sending message to Discord: " + status); + Infuse.LOGGER.error("Error sending message to Discord: {}", status); } } catch (IOException err) { Infuse.LOGGER.error("Could not send webhook message to discord.", err); @@ -108,6 +107,10 @@ public void onCraft(CraftItemEvent event) { // Making sure the brewing stand is still placed Location brewerLocation = event.getInventory().getLocation(); + if (brewerLocation == null) { + return; + } + if (brewerLocation.getBlock().getType() != Material.BREWING_STAND) { event.setCancelled(true); return; @@ -158,9 +161,7 @@ public void onCraft(CraftItemEvent event) { } // Removing the ingredients - event.getInventory().forEach(item -> { - item.subtract(1); - }); + event.getInventory().forEach(item -> item.subtract(1)); // Closing the inventory player.closeInventory(); @@ -383,11 +384,7 @@ public void onBrewingStandBreak(BlockBreakEvent event) { @EventHandler public void onBrewingStandExplode(EntityExplodeEvent event) { List blocks = event.blockList(); - for (Block block : blocks) { - if (block.getLocation().equals(brewerLocation)) { - blocks.remove(block); - } - } + blocks.removeIf(block -> block.getLocation().equals(brewerLocation)); } @EventHandler(priority = EventPriority.LOW) diff --git a/src/main/java/com/catadmirer/infuseSMP/managers/ParticleManager.java b/src/main/java/com/catadmirer/infuseSMP/managers/ParticleManager.java index 568ac895..1a2d515b 100644 --- a/src/main/java/com/catadmirer/infuseSMP/managers/ParticleManager.java +++ b/src/main/java/com/catadmirer/infuseSMP/managers/ParticleManager.java @@ -17,7 +17,7 @@ public ParticleManager(Infuse plugin) { } public void spawnEffectParticles(Player player, String slot) { - InfuseEffect effect = plugin.getDataManager().getEffect(player.getUniqueId(), slot); + InfuseEffect effect = plugin.getDataManager().getEffect(player, slot); if (effect == null) return; // Handling special particles for ender effect diff --git a/src/main/java/com/catadmirer/infuseSMP/managers/RecipeManager.java b/src/main/java/com/catadmirer/infuseSMP/managers/RecipeManager.java index 0ce950db..17cd6a32 100644 --- a/src/main/java/com/catadmirer/infuseSMP/managers/RecipeManager.java +++ b/src/main/java/com/catadmirer/infuseSMP/managers/RecipeManager.java @@ -17,13 +17,12 @@ public class RecipeManager { private final Infuse plugin; - private final File recipesFile; private final FileConfiguration recipesConfig; public RecipeManager(Infuse plugin) { this.plugin = plugin; - recipesFile = new File(plugin.getDataFolder(), "recipes.yml"); + File recipesFile = new File(plugin.getDataFolder(), "recipes.yml"); if (!recipesFile.exists()) { plugin.saveResource("recipes.yml", false); } @@ -33,7 +32,7 @@ public RecipeManager(Infuse plugin) { /** * Manager functionality for when the plugin is reloaded. - * + *

* In this case, it unregisters all the recipes then adds them back. */ public void reload() { @@ -67,6 +66,8 @@ public ShapedRecipe getRecipe(InfuseEffect mapping) { effectRecipe.shape(recipesConfig.getStringList(baseKey + ".shape").toArray(String[]::new)); ConfigurationSection ingredientsConfig = recipesConfig.getConfigurationSection(baseKey + ".ingredients"); + if (ingredientsConfig == null) return effectRecipe; + for (String key : ingredientsConfig.getKeys(false)) { char ingredientLabel = key.charAt(0); String materialName = ingredientsConfig.getString(key); diff --git a/src/main/java/com/catadmirer/infuseSMP/placeholders/InfusePlaceholders.java b/src/main/java/com/catadmirer/infuseSMP/placeholders/InfusePlaceholders.java index 8b67c5f7..a525cc01 100644 --- a/src/main/java/com/catadmirer/infuseSMP/placeholders/InfusePlaceholders.java +++ b/src/main/java/com/catadmirer/infuseSMP/placeholders/InfusePlaceholders.java @@ -9,24 +9,28 @@ import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.bukkit.OfflinePlayer; import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NonNull; public class InfusePlaceholders extends PlaceholderExpansion { - private Infuse plugin; + private final Infuse plugin; public InfusePlaceholders(Infuse plugin) { this.plugin = plugin; } + @NonNull @Override public String getAuthor() { return "catadmirer"; } + @NonNull @Override public String getIdentifier() { return "infuse"; } + @NonNull @Override public String getVersion() { return plugin.getPluginMeta().getVersion(); @@ -34,32 +38,23 @@ public String getVersion() { @Override public String onRequest(OfflinePlayer player, @NotNull String params) { - UUID uuid = player.getUniqueId(); - - switch (params.toLowerCase()) { - case "first_effect": - return getEffectIcon(uuid, "1"); - case "second_effect": - return getEffectIcon(uuid, "2"); - case "first_time": - return getTime(uuid, "1"); - case "second_time": - return getTime(uuid, "2"); - case "first_effect_raw": - return getEffectRaw(uuid, "1"); - case "second_effect_raw": - return getEffectRaw(uuid, "2"); - case "first_effect_name": - return getEffectName(uuid, "1"); - case "second_effect_name": - return getEffectName(uuid, "2"); - } - - return null; + return switch (params.toLowerCase()) { + case "first_effect" -> getEffectIcon(player, "1"); + case "second_effect" -> getEffectIcon(player, "2"); + case "first_time" -> getTime(player, "1"); + case "second_time" -> getTime(player, "2"); + case "first_effect_raw" -> getEffectRaw(player, "1"); + case "second_effect_raw" -> getEffectRaw(player, "2"); + case "first_effect_name" -> getEffectName(player, "1"); + case "second_effect_name" -> getEffectName(player, "2"); + default -> null; + }; } - public String getEffectIcon(UUID uuid, String slot) { - InfuseEffect effect = plugin.getDataManager().getEffect(uuid, slot); + public String getEffectIcon(OfflinePlayer player, String slot) { + UUID uuid = player.getUniqueId(); + + InfuseEffect effect = plugin.getDataManager().getEffect(player, slot); if (effect == null) { return plugin.getMainConfig().emptyEffectIcon() ? "\uE901" : ""; @@ -68,8 +63,9 @@ public String getEffectIcon(UUID uuid, String slot) { return "" + (CooldownManager.isEffectActive(uuid, effect.getKey()) ? effect.getActiveIcon() : effect.getIcon()); } - public String getTime(UUID uuid, String slot) { - InfuseEffect effect = plugin.getDataManager().getEffect(uuid, slot); + public String getTime(OfflinePlayer player, String slot) { + UUID uuid = player.getUniqueId(); + InfuseEffect effect = plugin.getDataManager().getEffect(player, slot); if (effect == null) return ""; String key = effect.getKey(); if (CooldownManager.isEffectActive(uuid, key)) { @@ -83,15 +79,15 @@ public String getTime(UUID uuid, String slot) { } } - public String getEffectRaw(UUID uuid, String slot) { - InfuseEffect effect = plugin.getDataManager().getEffect(uuid, slot); + public String getEffectRaw(OfflinePlayer player, String slot) { + InfuseEffect effect = plugin.getDataManager().getEffect(player, slot); if (effect== null) return ""; return PlainTextComponentSerializer.plainText().serialize(effect.getName().toComponent()); } - public String getEffectName(UUID uuid, String slot) { - InfuseEffect effect = plugin.getDataManager().getEffect(uuid, slot); + public String getEffectName(OfflinePlayer player, String slot) { + InfuseEffect effect = plugin.getDataManager().getEffect(player, slot); if (effect == null) return ""; return effect.getName().toString(); diff --git a/src/main/java/com/catadmirer/infuseSMP/playerdata/AsyncDataManager.java b/src/main/java/com/catadmirer/infuseSMP/playerdata/AsyncDataManager.java new file mode 100644 index 00000000..2f265e05 --- /dev/null +++ b/src/main/java/com/catadmirer/infuseSMP/playerdata/AsyncDataManager.java @@ -0,0 +1,107 @@ +package com.catadmirer.infuseSMP.playerdata; + +import com.catadmirer.infuseSMP.Infuse; +import com.catadmirer.infuseSMP.effects.InfuseEffect; +import org.bukkit.OfflinePlayer; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Abstract class that helps with handling data asynchronously. + */ +@NullMarked +public abstract class AsyncDataManager implements DataManager { + protected ExecutorService executorService; + + public AsyncDataManager() { + executorService = Executors.newThreadPerTaskExecutor(Executors.defaultThreadFactory()); + } + + @Override + public void setExistingCount(InfuseEffect effect, int count) { + // Attempts to update the database asynchronously, but falls back to synchronous execution if there is an error or the ExecutorService is closed. + if (!executorService.isShutdown()) { + executorService.execute(() -> reallySetExistingCount(effect, count)); + return; + } + + Infuse.LOGGER.warn("Could not write data to the database asynchronously"); + reallySetExistingCount(effect, count); + } + + protected abstract void reallySetExistingCount(InfuseEffect effect, int count); + + @Override + public void setTrusted(OfflinePlayer player, Set trusted) { + // Attempts to update the database asynchronously, but falls back to synchronous execution if there is an error or the ExecutorService is closed. + if (!executorService.isShutdown()) { + executorService.execute(() -> reallySetTrusted(player, trusted)); + return; + } + + Infuse.LOGGER.warn("Could not write data to the database asynchronously"); + reallySetTrusted(player, trusted); + } + + protected abstract void reallySetTrusted(OfflinePlayer player, Set trusted); + + @Override + public void addTrust(OfflinePlayer player, OfflinePlayer trusted) { + // Attempts to update the database asynchronously, but falls back to synchronous execution if there is an error or the ExecutorService is closed. + if (!executorService.isShutdown()) { + executorService.execute(() -> reallyAddTrust(player, trusted)); + return; + } + + Infuse.LOGGER.warn("Could not write data to the database asynchronously"); + reallyAddTrust(player, trusted); + } + + protected abstract void reallyAddTrust(OfflinePlayer player, OfflinePlayer trusted); + + @Override + public void removeTrust(OfflinePlayer player, OfflinePlayer untrusted) { + // Attempts to update the database asynchronously, but falls back to synchronous execution if there is an error or the ExecutorService is closed. + if (!executorService.isShutdown()) { + executorService.execute(() -> reallyRemoveTrust(player, untrusted)); + return; + } + + Infuse.LOGGER.warn("Could not write data to the database asynchronously"); + reallyRemoveTrust(player, untrusted); + } + + protected abstract void reallyRemoveTrust(OfflinePlayer player, OfflinePlayer untrusted); + + @Override + public void setEffect(OfflinePlayer player, String slot, @Nullable InfuseEffect effect) { + // Attempts to update the database asynchronously, but falls back to synchronous execution if there is an error or the ExecutorService is closed. + if (!executorService.isShutdown()) { + executorService.execute(() -> reallySetEffect(player, slot, effect)); + return; + } + + Infuse.LOGGER.warn("Could not write data to the database asynchronously"); + reallySetEffect(player, slot, effect); + } + + protected abstract void reallySetEffect(OfflinePlayer player, String slot, @Nullable InfuseEffect effect); + + @Override + public void setControlMode(OfflinePlayer player, String controlMode) { + // Attempts to update the database asynchronously, but falls back to synchronous execution if there is an error or the ExecutorService is closed. + if (!executorService.isShutdown()) { + executorService.execute(() -> reallySetControlMode(player, controlMode)); + return; + } + + Infuse.LOGGER.warn("Could not write data to the database asynchronously"); + reallySetControlMode(player, controlMode); + } + + protected abstract void reallySetControlMode(OfflinePlayer player, String controlMode); +} diff --git a/src/main/java/com/catadmirer/infuseSMP/playerdata/DataCache.java b/src/main/java/com/catadmirer/infuseSMP/playerdata/DataCache.java new file mode 100644 index 00000000..2989516c --- /dev/null +++ b/src/main/java/com/catadmirer/infuseSMP/playerdata/DataCache.java @@ -0,0 +1,101 @@ +package com.catadmirer.infuseSMP.playerdata; + +import com.catadmirer.infuseSMP.Infuse; +import com.catadmirer.infuseSMP.effects.InfuseEffect; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * This class is a non-persistent implementation of a {@link DataManager}.
+ * It may not reflect the current state of the persistent data.
+ * It should only be used by internal persistent implementations to prevent constant read/write operations. + */ +// TODO: Add a DataCacheBuilder +@ApiStatus.Internal +@NullMarked +public class DataCache implements DataManager { + public final Map> allTrusts = new HashMap<>(); + public final Map leftEffects = new HashMap<>(); + public final Map rightEffects = new HashMap<>(); + public final Map controlModes = new HashMap<>(); + public final Map craftedCounts = new HashMap<>(); + + @Override + public void load() { + Infuse.LOGGER.error("Cannot call 'DataManager#load' on a CachedData object.", new IllegalAccessException()); + } + + @Override + public int getExistingCount(InfuseEffect effect) { + return craftedCounts.getOrDefault(effect.serialize(), 0); + } + + @Override + public void setExistingCount(InfuseEffect effect, int count) { + craftedCounts.put(effect.serialize(), count); + } + + @Override + public Set getTrusted(OfflinePlayer player) { + return allTrusts.getOrDefault(player.getUniqueId(), Set.of()).stream().map(Bukkit::getOfflinePlayer).collect(Collectors.toSet()); + } + + @Override + public void setTrusted(OfflinePlayer player, Set trusted) { + allTrusts.put(player.getUniqueId(), trusted.stream().map(OfflinePlayer::getUniqueId).collect(Collectors.toSet())); + } + + @Override + public void setEffect(OfflinePlayer player, String slot, @Nullable InfuseEffect effect) { + Integer val = effect == null ? null : effect.serialize(); + if (slot.equals("1")) { + leftEffects.put(player.getUniqueId(), val); + } else if (slot.equals("2")) { + rightEffects.put(player.getUniqueId(), val); + } else { + Infuse.LOGGER.warn("Slot '{}' is not a valid slot. Please use \"1\" or \"2\"", slot); + } + } + + @Nullable + @Override + public InfuseEffect getEffect(OfflinePlayer player, String slot) { + if (slot.equals("1")) { + Integer serialized = leftEffects.get(player.getUniqueId()); + if (serialized == null) return null; + return InfuseEffect.deserialize(serialized); + } else if (slot.equals("2")) { + Integer serialized = rightEffects.get(player.getUniqueId()); + if (serialized == null) return null; + return InfuseEffect.deserialize(serialized); + } else { + Infuse.LOGGER.warn("Slot '{}' is not a valid slot. Please use \"1\" or \"2\"", slot); + } + + return null; + } + + @Override + public void setControlMode(OfflinePlayer player, String controlMode) { + controlModes.put(player.getUniqueId(), controlMode.equals("offhand")); + } + + @Override + public String getControlMode(OfflinePlayer player) { + return controlModes.getOrDefault(player.getUniqueId(), false) ? "offhand" : "command"; + } + + @Override + public void applyUpdates() { + + } +} diff --git a/src/main/java/com/catadmirer/infuseSMP/playerdata/DataManager.java b/src/main/java/com/catadmirer/infuseSMP/playerdata/DataManager.java new file mode 100644 index 00000000..a8393bc5 --- /dev/null +++ b/src/main/java/com/catadmirer/infuseSMP/playerdata/DataManager.java @@ -0,0 +1,168 @@ +package com.catadmirer.infuseSMP.playerdata; + +import com.catadmirer.infuseSMP.effects.InfuseEffect; +import org.bukkit.OfflinePlayer; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.Set; + +@NullMarked +public interface DataManager { + /** + * Reloads the player data. + */ + void load(); + + /** + * Gets the number of effects that exist. + * + * @param effect The effect to count + */ + int getExistingCount(InfuseEffect effect); + + /** + * Sets the number of effects that exists. + * + * @param effect The effect to set the count of + * @param count The number of this effect that exists + */ + void setExistingCount(InfuseEffect effect, int count); + + /** + * Gets a set of the players that the player trusts. + * + * @return The set of players trusted by the player. + */ + Set getTrusted(OfflinePlayer player); + + /** + * Sets the players that the truster trusts. + * + * @param player The player to modify + * @param trusted The set of players the truster should now trust + */ + void setTrusted(OfflinePlayer player, Set trusted); + + /** + * Adds a player to the list of trusted people. + * + * @param player The person whose trusted list to modify. + * @param trusted The person the truster now trusts. + */ + default void addTrust(OfflinePlayer player, OfflinePlayer trusted) { + // TODO: Override in SQL-based data managers + Set allTrusted = getTrusted(player); + allTrusted.add(trusted); + setTrusted(player, allTrusted); + } + + /** + * Removes a player from another player's list of trusted people. + * + * @param player The player whose trusted list to modify. + * @param untrusted The person to remove from the truster's trust. + */ + default void removeTrust(OfflinePlayer player, OfflinePlayer untrusted) { + // TODO: Override in SQL-based data managers + Set trusted = getTrusted(player); + trusted.remove(untrusted); + setTrusted(player, trusted); + } + + /** + * Checks if a player is trusted by another player. + * + * @param player The player whose trusted list to check. + * @param trusted The player to check if truster trusts. + * + * @return True if the truster trusts the toCheck player, false otherwise + */ + default boolean isTrusted(OfflinePlayer player, OfflinePlayer trusted) { + // TODO: Override in SQL-based data managers + return getTrusted(player).contains(trusted); + } + + /** + * Sets the infuse effect in a specific slot for a player. + * + * @param slot The slot to equip the effect in. + * @param effect The {@link InfuseEffect} for the infuse effect. + */ + void setEffect(OfflinePlayer player, String slot, @Nullable InfuseEffect effect); + + /** + * Gets the infuse effect a player has in a specific slot. + * + * @return null if there is not an effect equipped there or if the InfuseEffect could not be deserialized. Otherwise, it returns the deserialized InfuseEffect. + */ + @Nullable InfuseEffect getEffect(OfflinePlayer player, String slot); + + /** + * Checks if the player has the infuse effect. + * It checks both slots and doesn't differentiate between regular and augmented effects. + * + * @return True if the player has the effect equipped, false otherwise. + */ + default boolean hasEffect(OfflinePlayer player, InfuseEffect effect) { + return hasEffect(player, effect, false); + } + + /** + * Checks if the player has the infuse effect. + * It checks both slots and doesn't differentiate between regular and augmented effects. + * + * @return True if the player has the effect equipped, false otherwise. + */ + default boolean hasEffect(OfflinePlayer player, InfuseEffect effect, boolean differentiateAugmented) { + return hasEffect(player, effect, differentiateAugmented, "1") || hasEffect(player, effect, differentiateAugmented, "2"); + } + + /** + * Checks if the player has the infuse effect. + * It checks both slots and doesn't differentiate between regular and augmented effects. + * + * @return True if the player has the effect equipped, false otherwise. + */ + default boolean hasEffect(OfflinePlayer player, InfuseEffect effect, String slot) { + return hasEffect(player, effect, false, slot); + } + + /** + * Checks if the player has the infuse effect equipped in a specific slot. + * + * @param differentiateAugmented Whether the search should differentiate between regular and augmented effects. + * + * @return True if the player has the effect equipped, false otherwise. + */ + default boolean hasEffect(OfflinePlayer player, InfuseEffect effect, boolean differentiateAugmented, String slot) { + InfuseEffect equipped = getEffect(player, slot); + if (equipped == null) return false; + + if (differentiateAugmented) { + return effect.equals(equipped); + } + + return effect.getId() == equipped.getId(); + } + + /** Removes an infuse effect from a specific slot for a player. */ + default void removeEffect(OfflinePlayer player, String slot) { + setEffect(player, slot, null); + } + + /** Sets the control mode for a player. */ + void setControlMode(OfflinePlayer player, String controlMode); + + /** + * Gets the control mode of a player. + * + * @return Either "command" or "offhand". Defaults to "command" + */ + String getControlMode(OfflinePlayer player); + + /** + * Modifies the config to make any necessary changes to make old versions compatible with this new one. + */ + void applyUpdates(); +} \ No newline at end of file diff --git a/src/main/java/com/catadmirer/infuseSMP/playerdata/H2DataManager.java b/src/main/java/com/catadmirer/infuseSMP/playerdata/H2DataManager.java new file mode 100644 index 00000000..d4f3f1ed --- /dev/null +++ b/src/main/java/com/catadmirer/infuseSMP/playerdata/H2DataManager.java @@ -0,0 +1,367 @@ +package com.catadmirer.infuseSMP.playerdata; + +import com.catadmirer.infuseSMP.Infuse; +import com.catadmirer.infuseSMP.effects.InfuseEffect; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.h2.jdbcx.JdbcDataSource; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import javax.sql.DataSource; + +@NullMarked +public class H2DataManager extends AsyncDataManager { + private DataCache cache; + private final DataSource dataSource; + + public H2DataManager(Infuse plugin) { + try { + Class.forName("org.h2.Driver"); + } catch (ClassNotFoundException err) { + Infuse.LOGGER.error("Could not load the H2 driver", err); + } + + // Creating an empty cache + cache = new DataCache(); + + // Creating the JDBC DataSource + JdbcDataSource dataSource = new JdbcDataSource(); + dataSource.setUrl("jdbc:h2:./" + plugin.getDataFolder().getPath() + "/data/playerdata"); + dataSource.setUser("infuse"); + dataSource.setPassword(""); + dataSource.setDescription("Infuse PlayerData Storage"); + + this.dataSource = dataSource; + } + + @Override + public void load() { + final String createPlayerDataTable = "CREATE TABLE IF NOT EXISTS player_data(player UUID PRIMARY KEY NOT NULL, slot_1 INTEGER, slot_2 INTEGER, offhand_control BOOLEAN NOT NULL);"; + final String createTrustTable = "CREATE TABLE IF NOT EXISTS trusts(truster UUID NOT NULL, trusted UUID NOT NULL);"; + final String createCraftedTable = "CREATE TABLE IF NOT EXISTS crafted_effects(effect INTEGER PRIMARY KEY NOT NULL, crafted INTEGER NOT NULL);"; + + final String getAllTrusts = "SELECT * FROM trusts;"; + final String getAllPlayerData = "SELECT * FROM player_data;"; + final String getAllCrafted = "SELECT * FROM crafted_effects;"; + + try (Connection conn = dataSource.getConnection()) { + // Creating the tables if they don't exist + try (Statement stmt = conn.createStatement()) { + stmt.execute(createPlayerDataTable); + stmt.execute(createTrustTable); + stmt.execute(createCraftedTable); + } + + // Commiting any changes + conn.commit(); + + // Clearing any cached data + cache = new DataCache(); + + // Loading data into the cache + try (Statement stmt = conn.createStatement()) { + // Mirroring trusts + ResultSet results = stmt.executeQuery(getAllTrusts); + while (results.next()) { + UUID player = results.getObject(1, UUID.class); + UUID trusted = results.getObject(2, UUID.class); + + Set playerTrusts = cache.allTrusts.computeIfAbsent(player, _ -> new HashSet<>()); + playerTrusts.add(trusted); + cache.allTrusts.put(player, playerTrusts); + } + + results.close(); + + // Mirroring player data + results = stmt.executeQuery(getAllPlayerData); + while (results.next()) { + UUID player = results.getObject(1, UUID.class); + + int lEffect = results.getInt(2); + if (!results.wasNull()) { + cache.leftEffects.put(player, lEffect); + } + + int rEffect = results.getInt(3); + if (!results.wasNull()) { + cache.rightEffects.put(player, rEffect); + } + + boolean offhandControl = results.getBoolean(4); + cache.controlModes.put(player, offhandControl); + } + + results.close(); + + // Mirroring crafted effect counts + results = stmt.executeQuery(getAllCrafted); + while (results.next()) { + int effectId = results.getInt(1); + int crafted = results.getInt(2); + + cache.craftedCounts.put(effectId, crafted); + } + + results.close(); + } + + Infuse.LOGGER.info("Successfully loaded H2 database!"); + } catch (SQLException err) { + Infuse.LOGGER.error("Could not open connection to H2 database", err); + } + } + + @Override + public int getExistingCount(InfuseEffect effect) { + return cache.getExistingCount(effect); + } + + @Override + protected void reallySetExistingCount(InfuseEffect effect, int count) { + // Updating the cache + cache.setExistingCount(effect, count); + + // Updating the database + String sql = "INSERT OR REPLACE INTO crafted_effects(effect, crafted) VALUES (?, ?);"; + + try (Connection conn = dataSource.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setInt(1, effect.serialize()); + stmt.setInt(2, count); + + stmt.executeUpdate(); + } catch (SQLException e) { + Infuse.LOGGER.error("Database error!", e); + } + } + + /** + * Creates a new player object in the database. + * Both effects default to null, and the offhand_control defaults to false. + * If a row for the player already exists, nothing happens. + * + * @param player The player to create an entry for. + */ + private void createNewPlayer(OfflinePlayer player) { + // All calls are already async. This does not need to be run through the service. + String newPlayer = "INSERT OR IGNORE INTO player_data (player, offhand_control) VALUES (?, FALSE);"; + try (Connection conn = dataSource.getConnection(); + PreparedStatement stmt = conn.prepareStatement(newPlayer)) { + stmt.setObject(1, player.getUniqueId()); + stmt.executeUpdate(); + } catch (SQLException e) { + Infuse.LOGGER.error("Database error!", e); + } + } + + @Override + public Set getTrusted(OfflinePlayer player) { + return cache.getTrusted(player); + } + + @Override + protected void reallySetTrusted(OfflinePlayer player, Set allTrusted) { + // Updating the cache + cache.setTrusted(player, allTrusted); + + // Updating the database + Set trustedCopy = new HashSet<>(allTrusted); + + String findUnused = "SELECT * FROM trusts WHERE truster = ?;"; + String deleteExtra = "DELETE FROM trusts WHERE truster = ? AND trusted = ?;"; + + String insertTrusted = "INSERT INTO trusts (truster, trusted) VALUES (?, ?);"; + + try (Connection conn = dataSource.getConnection()) { + // Removing players who are no longer trusted and filtering out players who are already trusted from the copy of the set + try (PreparedStatement stmt = conn.prepareStatement(findUnused); + PreparedStatement delStmt = conn.prepareStatement(deleteExtra)) { + stmt.setObject(1, player.getUniqueId()); + stmt.execute(); + ResultSet results = stmt.getResultSet(); + + delStmt.setObject(1, player.getUniqueId()); + + // Looping through the trusted players + while (results.next()) { + OfflinePlayer trusted = Bukkit.getOfflinePlayer(results.getObject(1, UUID.class)); + + // Skipping already trusted players + if (trustedCopy.contains(trusted)) { + trustedCopy.remove(trusted); + continue; + } + + // Removing players who are no longer trusted + delStmt.setObject(2, trusted.getUniqueId()); + delStmt.addBatch(); + } + + // Executing the delete statement + delStmt.executeBatch(); + } catch (SQLException e) { + Infuse.LOGGER.error("Database error!", e); + } + + // Adding the rest of the players to trust + try (PreparedStatement stmt = conn.prepareStatement(insertTrusted)) { + stmt.setObject(1, player.getUniqueId()); + + for (OfflinePlayer trusted : trustedCopy) { + stmt.setObject(2, trusted.getUniqueId()); + stmt.addBatch(); + } + + stmt.executeBatch(); + } catch (SQLException err) { + Infuse.LOGGER.error("Failed to insert players back into the database", err); + } + } catch (SQLException err) { + Infuse.LOGGER.info("Failed to connect to database.", err); + } + + throw new UnsupportedOperationException("Unimplemented method 'setTrusted'"); + } + + @Override + protected void reallyAddTrust(OfflinePlayer player, OfflinePlayer toTrust) { + // Updating the cache + cache.addTrust(player, toTrust); + + // Updating the database + String insertElem = """ + INSERT INTO trusts (truster, trusted) + SELECT ?, ? + WHERE NOT EXISTS ( + SELECT * FROM trusts WHERE truster = ? AND trusted = ? + );"""; + + UUID trusterUUID = player.getUniqueId(); + UUID toTrustUUID = toTrust.getUniqueId(); + + try (Connection conn = dataSource.getConnection()) { + try (PreparedStatement stmt = conn.prepareStatement(insertElem)) { + stmt.setObject(1, trusterUUID); + stmt.setObject(2, toTrustUUID); + stmt.setObject(3, trusterUUID); + stmt.setObject(4, toTrustUUID); + stmt.execute(); + } catch (SQLException err) { + Infuse.LOGGER.error("Failed to insert data into database", err); + } + } catch (SQLException err) { + Infuse.LOGGER.info("Failed to connect to database.", err); + } + } + + @Override + protected void reallyRemoveTrust(OfflinePlayer player, OfflinePlayer untrusted) { + // Updating the cache + cache.removeTrust(player, untrusted); + + // Updating the database + String deleteElem = "DELETE FROM trusts WHERE truster = ? AND trusted = ?"; + + UUID trusterUUID = player.getUniqueId(); + UUID trustedUUID = untrusted.getUniqueId(); + + try (Connection conn = dataSource.getConnection()) { + try (PreparedStatement stmt = conn.prepareStatement(deleteElem)) { + stmt.setObject(1, trusterUUID); + stmt.setObject(2, trustedUUID); + stmt.execute(); + } catch (SQLException err) { + Infuse.LOGGER.error("Failed to remove data from the database", err); + } + } catch (SQLException err) { + Infuse.LOGGER.info("Failed to connect to database.", err); + } + } + + @Override + public boolean isTrusted(OfflinePlayer player, OfflinePlayer trusted) { + return cache.isTrusted(player, trusted); + } + + @Override + protected void reallySetEffect(OfflinePlayer player, String slot, @Nullable InfuseEffect effect) { + // Updating the cache + cache.setEffect(player, slot, effect); + + // Updating the database + createNewPlayer(player); + + // Making sure slot is "1" or "2" + if (!slot.equals("1") && !slot.equals("2")) { + Infuse.LOGGER.warn("Slot '{}' is not a valid slot. Please use \"1\" or \"2\"", slot); + return; + } + + // Constructing sql based on specified slot + final String setEffectSQL = "UPDATE player_data SET slot_1 = ? WHERE player = ?;"; + + try (Connection conn = dataSource.getConnection()) { + PreparedStatement stmt = conn.prepareStatement(setEffectSQL); + stmt.setObject(1, effect == null ? null : effect.serialize()); + stmt.setObject(2, player.getUniqueId()); + + stmt.executeUpdate(); + } catch (SQLException err) { + Infuse.LOGGER.error("Could not open connection to H2 database", err); + } + } + + @Nullable + @Override + public InfuseEffect getEffect(OfflinePlayer player, String slot) { + return cache.getEffect(player, slot); + } + + @Override + protected void reallySetControlMode(OfflinePlayer player, String controlMode) { + // Updating the cache + cache.setControlMode(player, controlMode); + + // Updating the database + createNewPlayer(player); + + boolean offhandControls; + if (controlMode.equals("offhand")) { + offhandControls = true; + } else if (controlMode.equals("command")) { + offhandControls = false; + } else { + Infuse.LOGGER.error("Invalid control mode \"{}\". Please use \"offhand\" or \"command\"", controlMode); + return; + } + + String sql = "UPDATE player_data SET offhand_control = ? WHERE player = ?;"; + try (Connection conn = dataSource.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setObject(1, offhandControls); + stmt.setObject(2, player.getUniqueId()); + + stmt.executeUpdate(); + } catch (SQLException e) { + Infuse.LOGGER.error("Database error!", e); + } + } + + @Override + public String getControlMode(OfflinePlayer player) { + return cache.getControlMode(player); + } + + @Override + public void applyUpdates() {} +} diff --git a/src/main/java/com/catadmirer/infuseSMP/playerdata/YamlDataManager.java b/src/main/java/com/catadmirer/infuseSMP/playerdata/YamlDataManager.java new file mode 100644 index 00000000..e09c4b33 --- /dev/null +++ b/src/main/java/com/catadmirer/infuseSMP/playerdata/YamlDataManager.java @@ -0,0 +1,154 @@ +package com.catadmirer.infuseSMP.playerdata; + +import com.catadmirer.infuseSMP.Infuse; +import com.catadmirer.infuseSMP.effects.InfuseEffect; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Scanner; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +@NullMarked +public class YamlDataManager implements DataManager { + private final Infuse plugin; + private final File dataFile; + private final YamlConfiguration config; + + public YamlDataManager(Infuse plugin) { + this.plugin = plugin; + this.dataFile = new File(plugin.getDataFolder(), "data/playerdata.yml"); + this.config = YamlConfiguration.loadConfiguration(dataFile); + } + + @Override + public void load() { + // Creating the file if it doesn't exist. + createFile(); + + // Loading the config + try { + config.load(dataFile); + Infuse.LOGGER.info("Successfully loaded YAML data!"); + } catch (InvalidConfigurationException err) { + Infuse.LOGGER.warn("{} contains an invalid YAML configuration. Verify the contents of the file.", dataFile.getName()); + } catch (IOException err) { + Infuse.LOGGER.error("Could not find {}. Check that it exists.", dataFile.getName()); + } + + } + + private void save() { + // Creating the file if it doesn't exist. + createFile(); + + // Saving the config + try { + config.save(dataFile); + Infuse.LOGGER.info("Saved {}", dataFile.getName()); + } catch (IOException e) { + Infuse.LOGGER.warn("Could not save {}. Make sure the user has write permissions.", dataFile.getName()); + } + + } + + /** Creates the config file. If it doesn't exist, it loads the default config. */ + public void createFile() { + plugin.saveResource("config.yml", false); + } + + @Override + public int getExistingCount(InfuseEffect effect) { + return config.getInt("effects-crafted." + effect.getKey(), 0); + } + + @Override + public void setExistingCount(InfuseEffect effect, int count) { + config.set("effects-crafted." + effect.getKey(), count); + } + + @Override + public Set getTrusted(OfflinePlayer player) { + return config.getStringList(player.getUniqueId() + ".trust").stream().map(UUID::fromString).map(Bukkit::getOfflinePlayer).collect(Collectors.toSet()); + } + + @Override + public void setTrusted(OfflinePlayer player, Set allTrusted) { + config.set(player.getUniqueId() + ".trust", allTrusted.stream().map(OfflinePlayer::getUniqueId).toList()); + + save(); + } + + @Override + public void setEffect(OfflinePlayer player, String slot, @Nullable InfuseEffect effect) { + // Making sure slot is "1" or "2" + if (!slot.equals("1") && !slot.equals("2")) { + Infuse.LOGGER.warn("Slot '{}' is not a valid slot. Please use \"1\" or \"2\"", slot); + return; + } + + if (effect == null) { + config.set(player.getUniqueId() + "." + slot, null); + } else { + config.set(player.getUniqueId() + "." + slot, effect.toString()); + } + save(); + } + + @Nullable + @Override + public InfuseEffect getEffect(OfflinePlayer player, String slot) { + String effectKey = config.getString(player.getUniqueId() + "." + slot, null); + InfuseEffect effect = InfuseEffect.fromString(effectKey); + if (effectKey != null && effect == null) { + Infuse.LOGGER.warn("No valid ability found for the equipped effect."); + } + + return effect; + } + + @Override + public void setControlMode(OfflinePlayer player, String defaultMode) { + config.set(player.getUniqueId() + ".controls", defaultMode); + save(); + } + + @Override + public String getControlMode(OfflinePlayer player) { + return config.getString(player.getUniqueId() + ".controls", "command"); + } + + @Override + public void applyUpdates() { + try { + Scanner scanner = new Scanner(dataFile); + StringBuilder inputBuffer = new StringBuilder(); + String line; + + while (scanner.hasNextLine()) { + line = scanner.nextLine(); + + // Replacing old configs + if (line.startsWith("effects-crafted")) { + line = line.replace("effects-crafted", "existing-effects"); + } + inputBuffer.append(line); + inputBuffer.append('\n'); + } + scanner.close(); + + // Emptying the string buffer back into the file + FileOutputStream fileOut = new FileOutputStream(dataFile); + fileOut.write(inputBuffer.toString().getBytes()); + fileOut.close(); + } catch (IOException ignored) {} + } +} \ No newline at end of file diff --git a/src/main/java/com/catadmirer/infuseSMP/util/InventoryUtils.java b/src/main/java/com/catadmirer/infuseSMP/util/InventoryUtils.java index 74cdb533..d3073f12 100644 --- a/src/main/java/com/catadmirer/infuseSMP/util/InventoryUtils.java +++ b/src/main/java/com/catadmirer/infuseSMP/util/InventoryUtils.java @@ -69,9 +69,7 @@ public static void fillRemainingSlots(Inventory inventory) { public static void lockInventory(Inventory inventory) { for (ItemStack item : inventory.getContents()) { if (item == null) continue; - item.editMeta(meta -> { - meta.setMaxStackSize(1); - }); + item.editMeta(meta -> meta.setMaxStackSize(1)); } } } \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index e3492e76..bafd6475 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -27,6 +27,15 @@ player_head_drops: true enable_discord_broadcasts: false +# Sets where the data will be stored. +# IMPORTANT: Data is not transferred between nodes. To move data to different storage types, export it to YAML then import it. +# (Exported data is the same format as the YAML storage mode, so no exporting is needed if you already use the YAML config.) +# This config IS NOT UPDATED when you use `/infuse reload`. You need to RESTART THE SERVER to change this config +# Supported values: +# - "H2" +# - "YAML" +storage_mode: YAML + discord_webhook_url: "" brewing_gui: true diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 94084262..e4224005 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -4,6 +4,9 @@ main: com.catadmirer.infuseSMP.Infuse api-version: '1.21.4' authors: [CatAdmirer, TurboJax] softdepend: [PlaceholderAPI] +libraries: +- com.h2database:h2:$h2Version + commands: infuse: description: Base command for Infuse