From 5c59ea020379c1cfa749f90e9a68a8c9c8cfd889 Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Sun, 20 Aug 2023 22:06:38 +0100 Subject: [PATCH 01/43] Fix pregen storage things --- .../kotlin/mine/storage/PregenStorage.kt | 29 +++++++++++++++---- src/main/resources/config.yml | 1 - 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/me/untouchedodin0/kotlin/mine/storage/PregenStorage.kt b/src/main/kotlin/me/untouchedodin0/kotlin/mine/storage/PregenStorage.kt index d07a7f7e..bb62aaab 100644 --- a/src/main/kotlin/me/untouchedodin0/kotlin/mine/storage/PregenStorage.kt +++ b/src/main/kotlin/me/untouchedodin0/kotlin/mine/storage/PregenStorage.kt @@ -22,6 +22,7 @@ package me.untouchedodin0.kotlin.mine.storage import me.untouchedodin0.kotlin.mine.pregen.PregenMine +import me.untouchedodin0.privatemines.PrivateMines import org.bukkit.Location /** @@ -31,6 +32,8 @@ import org.bukkit.Location */ class PregenStorage { + private var privateMines = PrivateMines.getPrivateMines() + // Initialize an empty MutableMap to store the pregenerated mines private var pregenMines: MutableMap = HashMap() @@ -46,17 +49,31 @@ class PregenStorage { * Gets and removes the oldest pregenerated mine from the storage. * @return The oldest pregenerated mine. */ - fun getAndRemove(): PregenMine { - // Get the oldest entry in the map (which is the first entry) - val oldestEntry = pregenMines.entries.first() - // Use a let block to avoid nullable warnings and return the oldest pregenerated mine - oldestEntry.let { - return oldestEntry.key.also { + fun getAndRemove(): PregenMine? { + return try { + // Get the oldest entry in the map (which is the first entry) + val oldestEntry = pregenMines.entries.first() + // Use a let block to avoid nullable warnings and return the oldest pregenerated mine + oldestEntry.key.also { pregenMines.remove(oldestEntry.key) } + } catch (e: NoSuchElementException) { + privateMines.logger.info("No more pregenerated mines available.") + null // Return null to indicate no more pregenerated mines } } +// fun getAndRemove(): PregenMine { +// // Get the oldest entry in the map (which is the first entry) +// val oldestEntry = pregenMines.entries.first() +// // Use a let block to avoid nullable warnings and return the oldest pregenerated mine +// oldestEntry.let { +// return oldestEntry.key.also { +// pregenMines.remove(oldestEntry.key) +// } +// } +// } + /** * Checks if all pregenerated mines in the storage have been redeemed. * @return True if there are no more pregenerated mines in the storage, false otherwise. diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 3ca958aa..331b3d10 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -20,7 +20,6 @@ enableMenu: true enableTax: true sendTaxMessages: true defaultClosed: true -storageType: YAML # Supports YAML, SQLite (Important: DO NOT CHANGE ONCE YOU START USING ONE) resetCooldown: 15 formatResetMessage: true From 268f4a3e81c817cf49c3df82bd2093a989dd051e Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Tue, 22 Aug 2023 21:33:30 +0100 Subject: [PATCH 02/43] Fix the repeating task spam --- src/main/java/me/untouchedodin0/privatemines/mine/Mine.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java b/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java index 6e2e4d58..ee73b05f 100644 --- a/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java +++ b/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java @@ -519,7 +519,7 @@ public void startTasks() { MineType mineType = mineData.getMineType(); if (task == null) { - this.task = Task.syncRepeating(this::handleReset, 0L, mineType.getResetTime() * 20L); + this.task = Task.syncRepeating(this::handleReset, 0L, mineType.getResetTime() * 60 * 20L); } } From 3e7b4676a08c7f3b90db8025583114981837113a Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Sat, 26 Aug 2023 22:12:13 +0100 Subject: [PATCH 03/43] Small fix --- .../privatemines/mine/Mine.java | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java b/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java index ee73b05f..651cf4d0 100644 --- a/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java +++ b/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java @@ -42,6 +42,7 @@ import java.io.File; import java.time.Duration; import java.time.Instant; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.UUID; @@ -533,32 +534,38 @@ public void stopTasks() { privateMines.getLogger().info("Stopped tasks for mine " + mineData.getMineOwner()); } - public void ban(Player player) { - if (mineData.getBannedPlayers().contains(player.getUniqueId())) { - return; - } - Player owner = Bukkit.getPlayer(mineData.getMineOwner()); - if (player.equals(owner)) { - return; + UUID uuid = player.getUniqueId(); + UUID mineOwner = mineData.getMineOwner(); + List bannedPlayers = mineData.getBannedPlayers(); + if (bannedPlayers.contains(uuid) || uuid.equals(mineOwner)) { + return; // Player is already banned or is the mine owner, nothing to do. } - audienceUtils.sendMessage(player, Objects.requireNonNull(owner), - MessagesConfig.successfullyBannedPlayer); - audienceUtils.sendMessage(player, Objects.requireNonNull(owner), MessagesConfig.bannedFromMine); + Player owner = Bukkit.getPlayer(mineOwner); + if (owner == null) { + return; // The mine owner is not online, so we can't proceed. + } + audienceUtils.sendMessage(player, owner, MessagesConfig.successfullyBannedPlayer); + audienceUtils.sendMessage(player, owner, MessagesConfig.bannedFromMine); - mineData.getBannedPlayers().add(player.getUniqueId()); + bannedPlayers.add(uuid); setMineData(mineData); SQLUtils.update(this); } public void unban(Player player) { + UUID playerUniqueId = player.getUniqueId(); Player owner = Bukkit.getPlayer(mineData.getMineOwner()); - audienceUtils.sendMessage(player, Objects.requireNonNull(owner), MessagesConfig.unbannedPlayer); - audienceUtils.sendMessage(player, Objects.requireNonNull(owner), - MessagesConfig.unbannedFromMine); - mineData.getBannedPlayers().remove(player.getUniqueId()); + if (owner == null) { + return; // The mine owner is not online, so we can't proceed. + } + + audienceUtils.sendMessage(player, owner, MessagesConfig.unbannedPlayer); + audienceUtils.sendMessage(player, owner, MessagesConfig.unbannedFromMine); + + mineData.getBannedPlayers().remove(playerUniqueId); setMineData(mineData); SQLUtils.update(this); } From 18749305b2fb17f678a750ab38b73ed2c5714367 Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Thu, 31 Aug 2023 13:34:43 +0100 Subject: [PATCH 04/43] New placeholders listening to feedback. --- .../commands/PrivateMinesCommand.java | 1 + .../placeholderapi/PrivateMinesExpansion.java | 34 ++++++++++++++----- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java b/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java index a5fe5db0..fa9f4d49 100644 --- a/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java +++ b/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java @@ -330,6 +330,7 @@ public void teleport(Player player) { @CommandCompletion("@players") @CommandPermission("privatemines.go") @Syntax("") + @CommandAlias("visit") public void go(Player player, OfflinePlayer target) { if (target.getPlayer() != null) { Player targetPlayer = target.getPlayer(); diff --git a/src/main/java/me/untouchedodin0/privatemines/utils/placeholderapi/PrivateMinesExpansion.java b/src/main/java/me/untouchedodin0/privatemines/utils/placeholderapi/PrivateMinesExpansion.java index 045ef4da..2f0b2a70 100644 --- a/src/main/java/me/untouchedodin0/privatemines/utils/placeholderapi/PrivateMinesExpansion.java +++ b/src/main/java/me/untouchedodin0/privatemines/utils/placeholderapi/PrivateMinesExpansion.java @@ -64,12 +64,14 @@ public String onRequest(OfflinePlayer offlinePlayer, @NotNull String params) { Player player = offlinePlayer.getPlayer(); MineStorage mineStorage = privateMines.getMineStorage(); Mine mine; + Mine closest; if (player != null) { - mine = mineStorage.get(Objects.requireNonNull(player.getPlayer()).getUniqueId()); - Location location = player.getPlayer().getLocation(); + mine = mineStorage.get(Objects.requireNonNull(player.getPlayer()).getUniqueId()); + closest = mineStorage.getClosest(location); + switch (params.toLowerCase()) { case "size": if (mine != null) { @@ -81,26 +83,40 @@ public String onRequest(OfflinePlayer offlinePlayer, @NotNull String params) { return Integer.toString(distance); } case "owner": - Mine closest = mineStorage.getClosest(location); if (closest != null) { MineData mineData = closest.getMineData(); return String.valueOf(mineData.getMineOwner()); } break; case "location": - Mine mine1 = mineStorage.get(player); - if (mine1 != null) { - return String.valueOf(mine1.getLocation()); + if (mine != null) { + return String.valueOf(mine.getLocation()); } case "spawn": - Mine mine2 = mineStorage.get(player); - if (mine2 != null) { - return LocationUtils.toString(mine2.getSpawnLocation()); + if (mine != null) { + return LocationUtils.toString(mine.getSpawnLocation()); } case "inqueue": return String.valueOf(queueUtils.isInQueue(player.getUniqueId())); case "hasmine": return String.valueOf(mineStorage.hasMine(player)); + case "tax": + if (mine != null) { + MineData mineData = mine.getMineData(); + double tax = mineData.getTax(); + return String.valueOf(tax); + } + case "current_mine_tax": + if (closest != null) { + MineData mineData = closest.getMineData(); + double tax = mineData.getTax(); + return String.valueOf(tax); + } + case "is_public": + if (mine != null) { + MineData mineData = mine.getMineData(); + return String.valueOf(mineData.isOpen()); + } } } From ca1a6aef5b649de6118bd48f4deed5b02be5f94c Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Sun, 10 Dec 2023 21:53:27 +0000 Subject: [PATCH 05/43] Major optimization fix for the percentage checker. Fix for pregen factory not using the correct border distance Fix the Materials not loading correctly from the sqllite database Fix flags not being set --- build.gradle | 3 +- .../privatemines/PrivateMines.java | 33 ++++---- .../commands/PrivateMinesCommand.java | 13 +++- .../privatemines/factory/PregenFactory.java | 4 +- .../listener/BlockPlaceListener.java | 78 +++++++++++++++++++ .../privatemines/mine/Mine.java | 34 +++++++- .../utils/world/MineWorldManager.java | 4 + 7 files changed, 146 insertions(+), 23 deletions(-) create mode 100644 src/main/java/me/untouchedodin0/privatemines/listener/BlockPlaceListener.java diff --git a/build.gradle b/build.gradle index 9141378b..7283d09c 100644 --- a/build.gradle +++ b/build.gradle @@ -53,7 +53,8 @@ dependencies { compileOnly 'com.mojang:authlib:1.11' compileOnly 'me.clip:placeholderapi:2.11.3' compileOnly 'com.github.oraxen:oraxen:1.157.2' - compileOnly 'com.github.LoneDev6:API-ItemsAdder:3.4.1-r4' + compileOnly 'com.github.LoneDev6:API-ItemsAdder:3.6.1' +// compileOnly 'com.github.LoneDev6:API-ItemsAdder:3.4.1-r4' compileOnly 'com.google.code.gson:gson:2.10' compileOnly 'com.fasterxml.jackson.core:jackson-databind:2.15.0' // i think this is the big thing? diff --git a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java index 7b681140..ea37c41c 100644 --- a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java +++ b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java @@ -339,20 +339,23 @@ public void loadSQLMines() { String spawn = result.getString(8); double tax = result.get(9); int isOpen = result.get(10); -// String resultsMaterial = result.getString(13); -// resultsMaterial = resultsMaterial.substring(1); // remove starting '{' -// -// Map materials = new HashMap<>(); -// -// String[] pairs = resultsMaterial.split("\\s*,\\s*"); -// -// for (String string : pairs) { -// String[] parts = string.split("="); -// String matString = parts[0]; -// double percent = Double.parseDouble(parts[1].substring(0, parts[1].length() - 1)); -// Material material = Material.valueOf(matString); -// materials.put(material, percent); -// } + String resultsMaterial = result.getString(13); + Map materials = new HashMap<>(); + + if (!resultsMaterial.isEmpty()) { + resultsMaterial = resultsMaterial.substring(1); // remove starting '{' + + + String[] pairs = resultsMaterial.split("\\s*,\\s*"); + + for (String string : pairs) { + String[] parts = string.split("="); + String matString = parts[0]; + double percent = Double.parseDouble(parts[1].substring(0, parts[1].length() - 1)); + Material material = Material.valueOf(matString); + materials.put(material, percent); + } + } Mine mine = new Mine(this); UUID uuid = UUID.fromString(owner); @@ -367,7 +370,7 @@ public void loadSQLMines() { MineData mineData = new MineData(uuid, minMining, maxMining, fullMin, fullMax, location, spawnLocation, type, open, tax); -// mineData.setMaterials(materials); - This breaks it for some reason + mineData.setMaterials(materials); //- This breaks it for some reason mine.setMineData(mineData); mineStorage.addMine(uuid, mine); diff --git a/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java b/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java index fa9f4d49..ca5a89d1 100644 --- a/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java +++ b/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java @@ -47,6 +47,7 @@ import me.untouchedodin0.kotlin.mine.storage.PregenStorage; import me.untouchedodin0.kotlin.mine.type.MineType; import me.untouchedodin0.kotlin.utils.AudienceUtils; +import me.untouchedodin0.kotlin.utils.FlagUtils; import me.untouchedodin0.kotlin.utils.Range; import me.untouchedodin0.privatemines.PrivateMines; import me.untouchedodin0.privatemines.config.Config; @@ -209,7 +210,8 @@ public void forceUpgrade(CommandSender sender, OfflinePlayer target) { if (mine != null) { mine.upgrade(); mine.reset(); - audienceUtils.sendMessage(Objects.requireNonNull(target.getPlayer()), MessagesConfig.mineUpgraded); + audienceUtils.sendMessage(Objects.requireNonNull(target.getPlayer()), + MessagesConfig.mineUpgraded); List players = new ArrayList<>(); @@ -283,6 +285,12 @@ public void reset(Player player) { int timeLeft = cooldownManager.getCooldown(player.getUniqueId()); Mine mine = mineStorage.get(player); + // debug + if (mine != null) { + mine.getPercentage(); + } + + if (!Config.enableResetCooldown) { if (mine != null) { mine.handleReset(); @@ -505,7 +513,6 @@ public void claim(Player player) { PregenStorage pregenStorage = privateMines.getPregenStorage(); if (pregenStorage.isAllRedeemed()) { -// player.sendMessage(ChatColor.RED + "All the mines have been claimed..."); audienceUtils.sendMessage(player, MessagesConfig.allMinesClaimed); } else { PregenMine pregenMine = pregenStorage.getAndRemove(); @@ -520,6 +527,7 @@ public void claim(Player player) { BlockVector3 miningRegionMax = BukkitAdapter.asBlockVector(Objects.requireNonNull(corner2)); BlockVector3 fullRegionMin = BukkitAdapter.asBlockVector(Objects.requireNonNull(minimum)); BlockVector3 fullRegionMax = BukkitAdapter.asBlockVector(Objects.requireNonNull(maximum)); + FlagUtils flagUtils = new FlagUtils(); RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer(); RegionManager regionManager = container.get( @@ -545,6 +553,7 @@ public void claim(Player player) { mineStorage.addMine(player.getUniqueId(), mine); Task.syncDelayed(() -> spawn.getBlock().setType(Material.AIR, false)); + Task.syncDelayed(() -> flagUtils.setFlags(mine)); pregenMine.teleport(player); mine.handleReset(); } diff --git a/src/main/java/me/untouchedodin0/privatemines/factory/PregenFactory.java b/src/main/java/me/untouchedodin0/privatemines/factory/PregenFactory.java index 9ea4a186..915d19b5 100644 --- a/src/main/java/me/untouchedodin0/privatemines/factory/PregenFactory.java +++ b/src/main/java/me/untouchedodin0/privatemines/factory/PregenFactory.java @@ -28,6 +28,7 @@ import me.untouchedodin0.privatemines.mine.MineTypeManager; import me.untouchedodin0.privatemines.storage.SchematicStorage; import me.untouchedodin0.privatemines.storage.sql.SQLUtils; +import me.untouchedodin0.privatemines.utils.world.MineWorldManager; import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.Material; @@ -39,6 +40,7 @@ public class PregenFactory { public static PrivateMines privateMines = PrivateMines.getPrivateMines(); public static MineTypeManager mineTypeManager = privateMines.getMineTypeManager(); + public static MineWorldManager mineWorldManager = privateMines.getMineWorldManager(); private static Clipboard clipboard; private static ClipboardHolder clipboardHolder; @@ -86,7 +88,7 @@ public void run() { } } - location.add(0, 0, 100); + location.add(0, 0, mineWorldManager.getBorderDistance()); Chunk chunk = location.getChunk(); Task.syncDelayed(() -> chunk.load(true)); diff --git a/src/main/java/me/untouchedodin0/privatemines/listener/BlockPlaceListener.java b/src/main/java/me/untouchedodin0/privatemines/listener/BlockPlaceListener.java new file mode 100644 index 00000000..89934aec --- /dev/null +++ b/src/main/java/me/untouchedodin0/privatemines/listener/BlockPlaceListener.java @@ -0,0 +1,78 @@ +package me.untouchedodin0.privatemines.listener; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockPlaceEvent; + +public class BlockPlaceListener implements Listener { + + private final Player player; + private final Map requiredAmounts = new HashMap<>(); + private final Map placedAmounts = new HashMap<>(); + private final List corners = new ArrayList<>(); + private boolean allBlocksPlaced = false; + + public BlockPlaceListener(Player player) { + this.player = player; + + requiredAmounts.put(Material.COAL_BLOCK, 2); + requiredAmounts.put(Material.COBBLESTONE, 1); + } + + @EventHandler + public void onBlockPlace(BlockPlaceEvent event) { + Player player = event.getPlayer(); + Block placedBlock = event.getBlockPlaced(); + + if (placedBlock.getType() == Material.COAL_BLOCK + || placedBlock.getType() == Material.COBBLESTONE) { + + // Check if the block type is already in the map + int currentAmount = placedAmounts.getOrDefault(placedBlock.getType(), 0); + + if (currentAmount < requiredAmounts.get(placedBlock.getType())) { + // The player has placed the expected block, and they haven't placed the maximum allowed amount yet + player.sendMessage("You've placed a " + placedBlock.getType().name() + " block."); + + // Increase the count for the placed block type + placedAmounts.put(placedBlock.getType(), currentAmount + 1); + + // Check if the required amounts are met + if (Objects.equals(placedAmounts.get(Material.COAL_BLOCK), requiredAmounts.get(Material.COAL_BLOCK)) + && Objects.equals(placedAmounts.get(Material.COBBLESTONE), requiredAmounts.get(Material.COBBLESTONE))) { + + // Store the corners if not already stored + if (corners.size() < 2) { + corners.add(placedBlock.getLocation()); + } + + // Set the flag to true when all blocks are placed + allBlocksPlaced = true; + } + } else { + // The player has placed the maximum allowed blocks, cancel the event + player.sendMessage("You've placed the maximum allowed " + placedBlock.getType().name() + " blocks."); + event.setCancelled(true); + } + + // Check the flag and inform the player + if (allBlocksPlaced) { + player.sendMessage(ChatColor.RED + "You've placed all the required blocks for all materials!"); + player.sendMessage("Corner 1: " + corners.get(0).toString()); + player.sendMessage("Corner 2: " + corners.get(1).toString()); + Bukkit.broadcastMessage(ChatColor.RED + "You've placed all the required blocks for all materials!"); + } + } + } +} diff --git a/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java b/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java index 651cf4d0..df8f369c 100644 --- a/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java +++ b/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java @@ -22,8 +22,10 @@ package me.untouchedodin0.privatemines.mine; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.function.mask.BlockTypeMask; import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.function.pattern.RandomPattern; import com.sk89q.worldedit.math.BlockVector3; @@ -438,6 +440,7 @@ public double getPercentage() { mineData.getMinimumMining().getBlockY(), mineData.getMinimumMining().getBlockZ()), BlockVector3.at(mineData.getMaximumMining().getBlockX(), mineData.getMaximumMining().getBlockY(), mineData.getMaximumMining().getBlockZ())); + World world = getSpawnLocation().getWorld(); if (Config.addWallGap) { for (int i = 0; i < Config.wallsGap; i++) { @@ -447,16 +450,39 @@ public double getPercentage() { long total = region.getVolume(); + long worldEditBefore = System.currentTimeMillis(); + // Calculate the percetage of the region called "region" to then compare with how many blocks have been mined. airBlocks = 0; + + try (EditSession editSession = WorldEdit.getInstance().newEditSession(BukkitAdapter.adapt(world))) { + BlockTypeMask mask = new BlockTypeMask(editSession, BukkitAdapter.asBlockType(Material.AIR)); + + int airBlocks = editSession.countBlocks(region, mask); + Bukkit.broadcastMessage("Air blocks: " + airBlocks); + } catch (MaxChangedBlocksException e) { + e.printStackTrace(); // Handle the exception appropriately + } + + long worldEditAfter = System.currentTimeMillis(); + long worldEditDone = worldEditAfter - worldEditBefore; + + long forLoopBefore = System.currentTimeMillis(); + for (BlockVector3 vector : region) { - Block block = Objects.requireNonNull(Bukkit.getWorld( - Objects.requireNonNull(Objects.requireNonNull(getSpawnLocation()).getWorld()).getName())) - .getBlockAt(vector.getBlockX(), vector.getBlockY(), vector.getBlockZ()); + Block block = Objects.requireNonNull(Objects.requireNonNull(Bukkit.getWorld( + Objects.requireNonNull(Objects.requireNonNull(world).getName()))) + .getBlockAt(vector.getBlockX(), vector.getBlockY(), vector.getBlockZ())); if (block.getType().equals(Material.AIR)) { this.airBlocks++; } } + long forLoopAfter = System.currentTimeMillis(); + long forLoopDone = forLoopAfter - forLoopBefore; + + System.out.println("worldedit time " + worldEditDone); + System.out.println("for loop time " + forLoopDone); + return (float) airBlocks * 100L / total; } @@ -506,7 +532,7 @@ public void handleReset() { double percentage = getPercentage(); double resetPercentage = mineType.getResetPercentage(); if (percentage > resetPercentage) { - handleReset(); + //handleReset(); airBlocks = 0; } }, 0, 80); diff --git a/src/main/java/me/untouchedodin0/privatemines/utils/world/MineWorldManager.java b/src/main/java/me/untouchedodin0/privatemines/utils/world/MineWorldManager.java index 20a63f80..cdda139f 100644 --- a/src/main/java/me/untouchedodin0/privatemines/utils/world/MineWorldManager.java +++ b/src/main/java/me/untouchedodin0/privatemines/utils/world/MineWorldManager.java @@ -102,4 +102,8 @@ public World getMinesWorld() { public Location getDefaultLocation() { return defaultLocation; } + + public int getBorderDistance() { + return borderDistance; + } } From d5641955191bca6fe3dfd658792607a86bc8777d Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Fri, 15 Dec 2023 16:41:58 +0000 Subject: [PATCH 06/43] Faster percentage checks Fix commands? --- build.gradle | 1 - .../privatemines/PrivateMines.java | 17 +++----- .../commands/PrivateMinesCommand.java | 6 --- .../listener/PlayerJoinListener.java | 12 +++--- .../privatemines/mine/Mine.java | 43 +++++-------------- 5 files changed, 21 insertions(+), 58 deletions(-) diff --git a/build.gradle b/build.gradle index 7283d09c..1b148029 100644 --- a/build.gradle +++ b/build.gradle @@ -54,7 +54,6 @@ dependencies { compileOnly 'me.clip:placeholderapi:2.11.3' compileOnly 'com.github.oraxen:oraxen:1.157.2' compileOnly 'com.github.LoneDev6:API-ItemsAdder:3.6.1' -// compileOnly 'com.github.LoneDev6:API-ItemsAdder:3.4.1-r4' compileOnly 'com.google.code.gson:gson:2.10' compileOnly 'com.fasterxml.jackson.core:jackson-databind:2.15.0' // i think this is the big thing? diff --git a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java index ea37c41c..174614b3 100644 --- a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java +++ b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java @@ -21,7 +21,7 @@ package me.untouchedodin0.privatemines; -import co.aikar.commands.PaperCommandManager; +import co.aikar.commands.BukkitCommandManager; import java.io.File; import java.io.IOException; import java.nio.file.FileSystems; @@ -34,9 +34,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.UUID; -import java.util.concurrent.CompletableFuture; import java.util.stream.Stream; import me.untouchedodin0.kotlin.mine.data.MineData; import me.untouchedodin0.kotlin.mine.storage.MineStorage; @@ -63,7 +61,6 @@ import me.untouchedodin0.privatemines.storage.sql.SQLite; import me.untouchedodin0.privatemines.utils.QueueUtils; import me.untouchedodin0.privatemines.utils.UpdateChecker; -import me.untouchedodin0.privatemines.utils.addon.Addon; import me.untouchedodin0.privatemines.utils.addon.AddonManager; import me.untouchedodin0.privatemines.utils.placeholderapi.PrivateMinesExpansion; import me.untouchedodin0.privatemines.utils.world.MineWorldManager; @@ -77,7 +74,6 @@ import org.bukkit.entity.Player; import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.java.JavaPlugin; -import org.jetbrains.annotations.Nullable; import redempt.redlib.config.ConfigManager; import redempt.redlib.misc.LocationUtils; import redempt.redlib.misc.Task; @@ -118,7 +114,6 @@ public void onEnable() { getLogger().info("Loading Private Mines v" + getDescription().getVersion()); saveDefaultConfig(); saveResource("menus.yml", false); -// saveResource("messages_en.yml", false); saveLocales(); privateMines = this; @@ -206,12 +201,11 @@ public void onEnable() { schematicStorage.addSchematic(schematicFile, mineBlocks); }); - PaperCommandManager paperCommandManager = new PaperCommandManager(this); - paperCommandManager.enableUnstableAPI("help"); + BukkitCommandManager bukkitCommandManager = new BukkitCommandManager(this); - paperCommandManager.registerCommand(new PrivateMinesCommand()); - paperCommandManager.registerCommand(new PublicMinesCommand()); - paperCommandManager.registerCommand(new AddonsCommand()); + bukkitCommandManager.registerCommand(new PrivateMinesCommand()); + bukkitCommandManager.registerCommand(new PublicMinesCommand()); + bukkitCommandManager.registerCommand(new AddonsCommand()); File dataFolder = new File(privateMines.getDataFolder(), "privatemines.db"); if (!dataFolder.exists()) { @@ -345,7 +339,6 @@ public void loadSQLMines() { if (!resultsMaterial.isEmpty()) { resultsMaterial = resultsMaterial.substring(1); // remove starting '{' - String[] pairs = resultsMaterial.split("\\s*,\\s*"); for (String string : pairs) { diff --git a/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java b/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java index ca5a89d1..0b5d8832 100644 --- a/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java +++ b/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java @@ -285,12 +285,6 @@ public void reset(Player player) { int timeLeft = cooldownManager.getCooldown(player.getUniqueId()); Mine mine = mineStorage.get(player); - // debug - if (mine != null) { - mine.getPercentage(); - } - - if (!Config.enableResetCooldown) { if (mine != null) { mine.handleReset(); diff --git a/src/main/java/me/untouchedodin0/privatemines/listener/PlayerJoinListener.java b/src/main/java/me/untouchedodin0/privatemines/listener/PlayerJoinListener.java index e19a7e8c..04bedc97 100644 --- a/src/main/java/me/untouchedodin0/privatemines/listener/PlayerJoinListener.java +++ b/src/main/java/me/untouchedodin0/privatemines/listener/PlayerJoinListener.java @@ -43,12 +43,12 @@ public void onJoin(PlayerJoinEvent playerJoinEvent) { if (Config.giveMineOnFirstJoin) { Player player = playerJoinEvent.getPlayer(); - if (mineStorage.hasMine(player)) { - Mine mine = mineStorage.get(player); - if (mine != null) { - mine.startTasks(); - } - } +// if (mineStorage.hasMine(player)) { +// Mine mine = mineStorage.get(player); +// if (mine != null) { +// mine.startTasks(); +// } +// } QueueUtils queueUtils = privateMines.getQueueUtils(); if (queueUtils.isInQueue(player.getUniqueId())) { diff --git a/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java b/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java index df8f369c..a740bd1a 100644 --- a/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java +++ b/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java @@ -31,6 +31,7 @@ import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.block.BlockTypes; import com.sk89q.worldguard.WorldGuard; import com.sk89q.worldguard.protection.managers.RegionManager; @@ -83,7 +84,6 @@ public class Mine { private boolean canExpand = true; private Task task; private Task percentageTask = null; - private int airBlocks; AudienceUtils audienceUtils = new AudienceUtils(); public Mine(PrivateMines privateMines) { @@ -448,42 +448,20 @@ public double getPercentage() { } } - long total = region.getVolume(); + double percentage = 0.0; - long worldEditBefore = System.currentTimeMillis(); - - // Calculate the percetage of the region called "region" to then compare with how many blocks have been mined. - airBlocks = 0; - - try (EditSession editSession = WorldEdit.getInstance().newEditSession(BukkitAdapter.adapt(world))) { - BlockTypeMask mask = new BlockTypeMask(editSession, BukkitAdapter.asBlockType(Material.AIR)); + try (EditSession editSession = WorldEdit.getInstance() + .newEditSession(BukkitAdapter.adapt(world))) { + BlockType air = BukkitAdapter.asBlockType(Material.AIR); + BlockTypeMask mask = new BlockTypeMask(editSession, air); int airBlocks = editSession.countBlocks(region, mask); - Bukkit.broadcastMessage("Air blocks: " + airBlocks); + int totalBlocks = region.size(); + percentage = ((double) airBlocks / totalBlocks) * 100; } catch (MaxChangedBlocksException e) { e.printStackTrace(); // Handle the exception appropriately } - - long worldEditAfter = System.currentTimeMillis(); - long worldEditDone = worldEditAfter - worldEditBefore; - - long forLoopBefore = System.currentTimeMillis(); - - for (BlockVector3 vector : region) { - Block block = Objects.requireNonNull(Objects.requireNonNull(Bukkit.getWorld( - Objects.requireNonNull(Objects.requireNonNull(world).getName()))) - .getBlockAt(vector.getBlockX(), vector.getBlockY(), vector.getBlockZ())); - if (block.getType().equals(Material.AIR)) { - this.airBlocks++; - } - } - long forLoopAfter = System.currentTimeMillis(); - long forLoopDone = forLoopAfter - forLoopBefore; - - System.out.println("worldedit time " + worldEditDone); - System.out.println("for loop time " + forLoopDone); - - return (float) airBlocks * 100L / total; + return percentage; } public void handleReset() { @@ -532,8 +510,7 @@ public void handleReset() { double percentage = getPercentage(); double resetPercentage = mineType.getResetPercentage(); if (percentage > resetPercentage) { - //handleReset(); - airBlocks = 0; + handleReset(); } }, 0, 80); } From aa3800882fbce3b31970f3f996bc476a840b477d Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Sun, 17 Dec 2023 22:06:37 +0000 Subject: [PATCH 07/43] Logger fixes --- .../me/untouchedodin0/privatemines/PrivateMines.java | 12 ------------ .../privatemines/iterator/SchematicIterator.java | 6 +++--- .../me/untouchedodin0/privatemines/mine/Mine.java | 7 +++---- 3 files changed, 6 insertions(+), 19 deletions(-) diff --git a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java index 174614b3..fe66fbe9 100644 --- a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java +++ b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java @@ -77,7 +77,6 @@ import redempt.redlib.config.ConfigManager; import redempt.redlib.misc.LocationUtils; import redempt.redlib.misc.Task; -import redempt.redlib.sql.SQLCache; import redempt.redlib.sql.SQLHelper; import redempt.redlib.sql.SQLHelper.Results; @@ -100,7 +99,6 @@ public class PrivateMines extends JavaPlugin { private static Economy econ = null; private SQLite sqlite; private SQLHelper sqlHelper; - private Map caches; private BukkitAudiences adventure; private AddonManager addonManager; @@ -251,16 +249,6 @@ max_full VARCHAR(20) """); sqlHelper.setAutoCommit(true); - this.caches = new HashMap<>(); - - String databaseName = "privatemines"; - List cacheNames = List.of("owner", "mineType", "mineLocation", "corner1", "corner2"); - - cacheNames.forEach(string -> { - SQLCache sqlCache = sqlHelper.createCache(databaseName, string); - caches.put(string, sqlCache); - }); - Task.asyncDelayed(this::loadSQLMines); Task.asyncDelayed(SQLUtils::loadPregens); Task.asyncDelayed(this::loadAddons); diff --git a/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIterator.java b/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIterator.java index 91d1f3fb..cde9bddd 100644 --- a/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIterator.java +++ b/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIterator.java @@ -30,6 +30,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.util.logging.Level; import me.untouchedodin0.privatemines.PrivateMines; import me.untouchedodin0.privatemines.config.Config; import me.untouchedodin0.privatemines.storage.SchematicStorage; @@ -71,7 +72,6 @@ public MineBlocks findRelativePoints(File file) { BlockType npcType = BlockType.REGISTRY.get(npcMaterial.getKey().getKey()); BlockType quarryType = BlockType.REGISTRY.get(quarryMaterial.getKey().getKey()); - clipboard.getRegion().forEach(blockVector3 -> { BlockType blockType = clipboard.getBlock(blockVector3).getBlockType(); int x = blockVector3.getX(); @@ -99,7 +99,6 @@ public MineBlocks findRelativePoints(File file) { } }); - if (spawn == null) { privateMines.getLogger().info( String.format("Failed to find a spawn block in the mine\nhave you placed a %s block?", @@ -133,7 +132,8 @@ public MineBlocks findRelativePoints(File file) { corner1 = null; corner2 = null; } catch (IOException e) { - e.printStackTrace(); + privateMines.getLogger().log(Level.WARNING, + "An error occurred whilst iterating the mine blocks in the schematic", e); } } return mineBlocks; diff --git a/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java b/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java index a740bd1a..daa53b8b 100644 --- a/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java +++ b/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java @@ -50,6 +50,7 @@ import java.util.Objects; import java.util.UUID; import java.util.concurrent.TimeUnit; +import java.util.logging.Level; import me.untouchedodin0.kotlin.mine.data.MineData; import me.untouchedodin0.kotlin.mine.type.MineType; import me.untouchedodin0.kotlin.utils.AudienceUtils; @@ -459,7 +460,7 @@ public double getPercentage() { int totalBlocks = region.size(); percentage = ((double) airBlocks / totalBlocks) * 100; } catch (MaxChangedBlocksException e) { - e.printStackTrace(); // Handle the exception appropriately + privateMines.getLogger().log(Level.WARNING, "An error occurred during the edit session", e); } return percentage; } @@ -756,9 +757,7 @@ public void upgrade() { mineData.setMaximumFullRegion(maximum); setMineData(mineData); handleReset(); - Task.asyncDelayed(() -> { - SQLUtils.update(this); - }); + Task.asyncDelayed(() -> SQLUtils.update(this)); Task.syncDelayed(() -> spawn.getBlock().setType(Material.AIR, false)); if (Bukkit.getPluginManager().isPluginEnabled("XPrison")) { From 85426f6197ac4780c63613b7af3dcc6b3e22508f Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Mon, 18 Dec 2023 14:53:24 +0000 Subject: [PATCH 08/43] Fixes --- build.gradle | 6 +++--- .../me/untouchedodin0/privatemines/factory/MineFactory.java | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 1b148029..230fc66a 100644 --- a/build.gradle +++ b/build.gradle @@ -60,15 +60,15 @@ dependencies { compileOnly files('libs/XPrison-1.12.11-RELEASE.jar') compileOnly files('libs/AutoSellAPI-1.2.0.jar') - implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + bukkitLibrary 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + implementation 'com.github.Redempt:RedLib:6.5.8' - implementation 'org.bstats:bstats-bukkit:3.0.0' + implementation 'org.bstats:bstats-bukkit:3.0.2' implementation 'net.kyori:adventure-text-minimessage:4.14.0' implementation 'net.kyori:adventure-platform-bukkit:4.3.0' implementation 'co.aikar:acf-paper:0.5.1-SNAPSHOT' implementation 'org.apache.httpcomponents:httpclient:4.5.14' implementation "dev.triumphteam:triumph-gui:3.1.2" // Replace version here - implementation 'io.papermc:paperlib:1.0.7' implementation 'com.convallyria.languagy:api:3.0.2' implementation files('libs/PrivateMinesAPI-1.3-SNAPSHOT.jar') diff --git a/src/main/java/me/untouchedodin0/privatemines/factory/MineFactory.java b/src/main/java/me/untouchedodin0/privatemines/factory/MineFactory.java index 9455f653..0a111d12 100644 --- a/src/main/java/me/untouchedodin0/privatemines/factory/MineFactory.java +++ b/src/main/java/me/untouchedodin0/privatemines/factory/MineFactory.java @@ -109,9 +109,10 @@ public void create(Player player, Location location, MineType mineType) { Mine mine = new Mine(privateMines); MineData mineData = new MineData(uuid, corner2, corner1, minimum, maximum, location, spawn, mineType, false, 5.0); - if (!Config.defaultClosed) { - mineData.setOpen(true); + if (Config.defaultClosed) { + mineData.setOpen(false); } + mine.setMineData(mineData); SQLUtils.insert(mine); mineStorage.addMine(uuid, mine); From 48885d06680288b74ddf1f228f05f0c81ba53020 Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Sat, 23 Dec 2023 19:40:35 +0000 Subject: [PATCH 09/43] Fixes --- build.gradle | 7 ++- .../privatemines/factory/MineFactory.java | 11 ++-- .../privatemines/mine/Mine.java | 50 +++++++++++++++---- .../kotlin/mine/data/MineData.kt | 4 -- 4 files changed, 48 insertions(+), 24 deletions(-) diff --git a/build.gradle b/build.gradle index 230fc66a..d458f92e 100644 --- a/build.gradle +++ b/build.gradle @@ -18,6 +18,10 @@ if (devBuild) { version = version + "-DEV" } +ext { + spigotVersion = '1.13.2-R0.1-SNAPSHOT' +} + repositories { mavenLocal() mavenCentral() @@ -37,7 +41,7 @@ repositories { maven { url = 'https://repo.dmulloy2.net/repository/public/' } maven { url = 'https://repo.extendedclip.com/content/repositories/placeholderapi/' } maven { url = 'https://repo.mineinabyss.com/releases' } - maven { url = "https://repo.mattstudios.me/artifactory/public/" } +// maven { url = "https://repo.mattstudios.me/artifactory/public/" } maven { url = 'https://repo.convallyria.com/releases' } } @@ -72,6 +76,7 @@ dependencies { implementation 'com.convallyria.languagy:api:3.0.2' implementation files('libs/PrivateMinesAPI-1.3-SNAPSHOT.jar') + implementation files('libs/Ocelot-API-1.0.0.jar') } shadowJar { diff --git a/src/main/java/me/untouchedodin0/privatemines/factory/MineFactory.java b/src/main/java/me/untouchedodin0/privatemines/factory/MineFactory.java index 0a111d12..4e8e27fd 100644 --- a/src/main/java/me/untouchedodin0/privatemines/factory/MineFactory.java +++ b/src/main/java/me/untouchedodin0/privatemines/factory/MineFactory.java @@ -108,10 +108,7 @@ public void create(Player player, Location location, MineType mineType) { Mine mine = new Mine(privateMines); MineData mineData = new MineData(uuid, corner2, corner1, minimum, maximum, location, spawn, - mineType, false, 5.0); - if (Config.defaultClosed) { - mineData.setOpen(false); - } + mineType, Config.defaultClosed, 5.0); mine.setMineData(mineData); SQLUtils.insert(mine); @@ -186,10 +183,8 @@ public Mine createMine(Player player, Location location, MineType mineType) { } MineData mineData = new MineData(uuid, corner2, corner1, minimum, maximum, location, spawn, - mineType, false, 5.0); - if (!Config.defaultClosed) { - mineData.setOpen(true); - } + mineType, !Config.defaultClosed, 5.0); + mine.setMineData(mineData); SQLUtils.insert(mine); mineStorage.addMine(uuid, mine); diff --git a/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java b/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java index daa53b8b..aab8e5fb 100644 --- a/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java +++ b/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java @@ -45,12 +45,15 @@ import java.io.File; import java.time.Duration; import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.logging.Level; +import me.byteful.lib.ocelot.OcelotAPI; import me.untouchedodin0.kotlin.mine.data.MineData; import me.untouchedodin0.kotlin.mine.type.MineType; import me.untouchedodin0.kotlin.utils.AudienceUtils; @@ -215,17 +218,42 @@ public void reset() { World world = location.getWorld(); Region region = new CuboidRegion(BukkitAdapter.adapt(world), corner1, corner2); - try (EditSession editSession = WorldEdit.getInstance().newEditSessionBuilder() - .world(BukkitAdapter.adapt(world)).fastMode(true).build()) { - if (Config.addWallGap) { - editSession.setBlocks(region, BukkitAdapter.adapt(Material.AIR.createBlockData())); - for (int i = 0; i < Config.wallsGap; i++) { - region.contract(ExpansionUtils.contractVectors(1)); - } - } - editSession.setBlocks(region, randomPattern); - editSession.flushQueue(); - } + Location min = mineData.getMinimumMining(); + Location max = mineData.getMaximumMining(); + + redempt.redlib.region.CuboidRegion cuboidRegion = new redempt.redlib.region.CuboidRegion(min, max); + cuboidRegion.expand(1, 0, 1, 0, 1, 0); + + Bukkit.broadcastMessage(cuboidRegion.toString()); + +// OcelotAPI.updateBlock(min.getBlock(), Material.EMERALD_BLOCK); + long currenttime = System.currentTimeMillis(); + + Collection blocks = new ArrayList<>(); + + //OcelotAPI.updateBlock(block, Material.EMERALD_BLOCK); + cuboidRegion.forEachBlock(blocks::add); + + //OcelotAPI.updateBlocks(blocks, Material.EMERALD_BLOCK); + OcelotAPI.updateBlocks(blocks, Material.ACACIA_LOG, true); + long afterTime = System.currentTimeMillis(); + long timeTaken = afterTime - currenttime; + Bukkit.broadcastMessage(String.format("Time taken to fill %f blocks %d", cuboidRegion.getVolume(), timeTaken)); + +// OcelotAPI.updateBlocks(cuboidRegion.stream().toList(), Material.EMERALD_BLOCK); + + +// try (EditSession editSession = WorldEdit.getInstance().newEditSessionBuilder() +// .world(BukkitAdapter.adapt(world)).fastMode(true).build()) { +// if (Config.addWallGap) { +// editSession.setBlocks(region, BukkitAdapter.adapt(Material.AIR.createBlockData())); +// for (int i = 0; i < Config.wallsGap; i++) { +// region.contract(ExpansionUtils.contractVectors(1)); +// } +// } +// editSession.setBlocks(region, randomPattern); +// editSession.flushQueue(); +// } } public void resetOraxen() { diff --git a/src/main/kotlin/me/untouchedodin0/kotlin/mine/data/MineData.kt b/src/main/kotlin/me/untouchedodin0/kotlin/mine/data/MineData.kt index 0f6b94c7..c6b6c58f 100644 --- a/src/main/kotlin/me/untouchedodin0/kotlin/mine/data/MineData.kt +++ b/src/main/kotlin/me/untouchedodin0/kotlin/mine/data/MineData.kt @@ -39,10 +39,6 @@ data class MineData( var isOpen: Boolean = false, var tax: Double = 5.0, ) { -// fun setMineType(mineType: MineType) { -// this.mineType = mineType; -// } - private var shop: Shop? = null var bannedPlayers: MutableList = mutableListOf() From 9b81d28ad053d9af82c690a71395458dffb6ce26 Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Tue, 16 Jan 2024 22:17:00 +0000 Subject: [PATCH 10/43] More events --- .../events/PrivateMineBanEvent.java | 71 ++++++++++++++++ .../events/PrivateMineTeleportEvent.java | 65 +++++++++++++++ .../events/PrivateMineUnbanEvent.java | 70 ++++++++++++++++ .../privatemines/mine/Mine.java | 80 +++++++++---------- 4 files changed, 242 insertions(+), 44 deletions(-) create mode 100644 src/main/java/me/untouchedodin0/privatemines/events/PrivateMineBanEvent.java create mode 100644 src/main/java/me/untouchedodin0/privatemines/events/PrivateMineTeleportEvent.java create mode 100644 src/main/java/me/untouchedodin0/privatemines/events/PrivateMineUnbanEvent.java diff --git a/src/main/java/me/untouchedodin0/privatemines/events/PrivateMineBanEvent.java b/src/main/java/me/untouchedodin0/privatemines/events/PrivateMineBanEvent.java new file mode 100644 index 00000000..ff436ef0 --- /dev/null +++ b/src/main/java/me/untouchedodin0/privatemines/events/PrivateMineBanEvent.java @@ -0,0 +1,71 @@ +/** + * MIT License + *

+ * Copyright (c) 2021 - 2023 Kyle Hicks + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package me.untouchedodin0.privatemines.events; + +import java.util.UUID; +import me.untouchedodin0.privatemines.mine.Mine; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +public class PrivateMineBanEvent extends Event { + + public static final HandlerList handlers = new HandlerList(); + + public UUID mineOwner; + public UUID bannedPlayer; + public Mine mine; + public boolean cancelled; + + public PrivateMineBanEvent(UUID mineOwner, UUID bannedPlayer, Mine mine) { + this.mineOwner = mineOwner; + this.bannedPlayer = bannedPlayer; + this.mine = mine; + } + + public UUID getMineOwner() { + return mineOwner; + } + + public UUID getBannedPlayer() { + return bannedPlayer; + } + + public Mine getMine() { + return mine; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } +} diff --git a/src/main/java/me/untouchedodin0/privatemines/events/PrivateMineTeleportEvent.java b/src/main/java/me/untouchedodin0/privatemines/events/PrivateMineTeleportEvent.java new file mode 100644 index 00000000..8a459f3a --- /dev/null +++ b/src/main/java/me/untouchedodin0/privatemines/events/PrivateMineTeleportEvent.java @@ -0,0 +1,65 @@ +/** + * MIT License + *

+ * Copyright (c) 2021 - 2023 Kyle Hicks + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package me.untouchedodin0.privatemines.events; + +import java.util.UUID; +import me.untouchedodin0.privatemines.mine.Mine; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +public class PrivateMineTeleportEvent extends Event { + + public static final HandlerList handlers = new HandlerList(); + + public Player player; + public Mine mine; + public boolean cancelled; + + public PrivateMineTeleportEvent(Player player, Mine mine) { + this.player = player; + this.mine = mine; + } + + public Player getPlayer() { + return player; + } + + public Mine getMine() { + return mine; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } +} diff --git a/src/main/java/me/untouchedodin0/privatemines/events/PrivateMineUnbanEvent.java b/src/main/java/me/untouchedodin0/privatemines/events/PrivateMineUnbanEvent.java new file mode 100644 index 00000000..0613dc64 --- /dev/null +++ b/src/main/java/me/untouchedodin0/privatemines/events/PrivateMineUnbanEvent.java @@ -0,0 +1,70 @@ +/** + * MIT License + *

+ * Copyright (c) 2021 - 2023 Kyle Hicks + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package me.untouchedodin0.privatemines.events; + +import java.util.UUID; +import me.untouchedodin0.privatemines.mine.Mine; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +public class PrivateMineUnbanEvent extends Event { + + public static final HandlerList handlers = new HandlerList(); + + public UUID mineOwner; + public UUID bannedPlayer; + public Mine mine; + public boolean cancelled; + + public PrivateMineUnbanEvent(UUID mineOwner, UUID bannedPlayer, Mine mine) { + this.mineOwner = mineOwner; + this.bannedPlayer = bannedPlayer; + this.mine = mine; + } + + public UUID getMineOwner() { + return mineOwner; + } + + public UUID getBannedPlayer() { + return bannedPlayer; + } + + public Mine getMine() { + return mine; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return handlers; + } +} diff --git a/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java b/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java index aab8e5fb..6856122d 100644 --- a/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java +++ b/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java @@ -45,15 +45,12 @@ import java.io.File; import java.time.Duration; import java.time.Instant; -import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.logging.Level; -import me.byteful.lib.ocelot.OcelotAPI; import me.untouchedodin0.kotlin.mine.data.MineData; import me.untouchedodin0.kotlin.mine.type.MineType; import me.untouchedodin0.kotlin.utils.AudienceUtils; @@ -61,9 +58,11 @@ import me.untouchedodin0.privatemines.PrivateMines; import me.untouchedodin0.privatemines.config.Config; import me.untouchedodin0.privatemines.config.MessagesConfig; +import me.untouchedodin0.privatemines.events.PrivateMineBanEvent; import me.untouchedodin0.privatemines.events.PrivateMineDeleteEvent; import me.untouchedodin0.privatemines.events.PrivateMineExpandEvent; import me.untouchedodin0.privatemines.events.PrivateMineResetEvent; +import me.untouchedodin0.privatemines.events.PrivateMineTeleportEvent; import me.untouchedodin0.privatemines.events.PrivateMineUpgradeEvent; import me.untouchedodin0.privatemines.storage.sql.SQLUtils; import me.untouchedodin0.privatemines.utils.ExpansionUtils; @@ -115,10 +114,15 @@ public void setMineData(MineData mineData) { } public void teleport(Player player) { - if (getSpawnLocation().getBlock().getType().isBlock()) { - getSpawnLocation().getBlock().setType(Material.AIR, false); - player.teleport(getSpawnLocation()); - audienceUtils.sendMessage(player, MessagesConfig.teleportedToOwnMine); + PrivateMineTeleportEvent privateMineTeleportEvent = new PrivateMineTeleportEvent(player, this); + Task.syncDelayed(() -> Bukkit.getPluginManager().callEvent(privateMineTeleportEvent)); + + if (!privateMineTeleportEvent.isCancelled()) { + if (getSpawnLocation().getBlock().getType().isBlock()) { + getSpawnLocation().getBlock().setType(Material.AIR, false); + player.teleport(getSpawnLocation()); + audienceUtils.sendMessage(player, MessagesConfig.teleportedToOwnMine); + } } } @@ -218,42 +222,17 @@ public void reset() { World world = location.getWorld(); Region region = new CuboidRegion(BukkitAdapter.adapt(world), corner1, corner2); - Location min = mineData.getMinimumMining(); - Location max = mineData.getMaximumMining(); - - redempt.redlib.region.CuboidRegion cuboidRegion = new redempt.redlib.region.CuboidRegion(min, max); - cuboidRegion.expand(1, 0, 1, 0, 1, 0); - - Bukkit.broadcastMessage(cuboidRegion.toString()); - -// OcelotAPI.updateBlock(min.getBlock(), Material.EMERALD_BLOCK); - long currenttime = System.currentTimeMillis(); - - Collection blocks = new ArrayList<>(); - - //OcelotAPI.updateBlock(block, Material.EMERALD_BLOCK); - cuboidRegion.forEachBlock(blocks::add); - - //OcelotAPI.updateBlocks(blocks, Material.EMERALD_BLOCK); - OcelotAPI.updateBlocks(blocks, Material.ACACIA_LOG, true); - long afterTime = System.currentTimeMillis(); - long timeTaken = afterTime - currenttime; - Bukkit.broadcastMessage(String.format("Time taken to fill %f blocks %d", cuboidRegion.getVolume(), timeTaken)); - -// OcelotAPI.updateBlocks(cuboidRegion.stream().toList(), Material.EMERALD_BLOCK); - - -// try (EditSession editSession = WorldEdit.getInstance().newEditSessionBuilder() -// .world(BukkitAdapter.adapt(world)).fastMode(true).build()) { -// if (Config.addWallGap) { -// editSession.setBlocks(region, BukkitAdapter.adapt(Material.AIR.createBlockData())); -// for (int i = 0; i < Config.wallsGap; i++) { -// region.contract(ExpansionUtils.contractVectors(1)); -// } -// } -// editSession.setBlocks(region, randomPattern); -// editSession.flushQueue(); -// } + try (EditSession editSession = WorldEdit.getInstance().newEditSessionBuilder() + .world(BukkitAdapter.adapt(world)).fastMode(true).build()) { + if (Config.addWallGap) { + editSession.setBlocks(region, BukkitAdapter.adapt(Material.AIR.createBlockData())); + for (int i = 0; i < Config.wallsGap; i++) { + region.contract(ExpansionUtils.contractVectors(1)); + } + } + editSession.setBlocks(region, randomPattern); + editSession.flushQueue(); + } } public void resetOraxen() { @@ -569,6 +548,12 @@ public void stopTasks() { public void ban(Player player) { UUID uuid = player.getUniqueId(); UUID mineOwner = mineData.getMineOwner(); + + PrivateMineBanEvent privateMineBanEvent = new PrivateMineBanEvent(mineOwner, uuid, this); + Task.asyncDelayed(() -> Bukkit.getPluginManager().callEvent(privateMineBanEvent)); + + if (privateMineBanEvent.isCancelled()) return; + List bannedPlayers = mineData.getBannedPlayers(); if (bannedPlayers.contains(uuid) || uuid.equals(mineOwner)) { return; // Player is already banned or is the mine owner, nothing to do. @@ -587,13 +572,20 @@ public void ban(Player player) { } public void unban(Player player) { - UUID playerUniqueId = player.getUniqueId(); Player owner = Bukkit.getPlayer(mineData.getMineOwner()); if (owner == null) { return; // The mine owner is not online, so we can't proceed. } + UUID playerUniqueId = player.getUniqueId(); + UUID ownerUUID = owner.getUniqueId(); + + PrivateMineBanEvent privateMineBanEvent = new PrivateMineBanEvent(ownerUUID, playerUniqueId, this); + Task.asyncDelayed(() -> Bukkit.getPluginManager().callEvent(privateMineBanEvent)); + + if (privateMineBanEvent.isCancelled()) return; + audienceUtils.sendMessage(player, owner, MessagesConfig.unbannedPlayer); audienceUtils.sendMessage(player, owner, MessagesConfig.unbannedFromMine); From 094a1402696911e4f30b9a218db24f1580f890aa Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Fri, 19 Jan 2024 22:25:06 +0000 Subject: [PATCH 11/43] Recode of set blocks command --- .../commands/PrivateMinesCommand.java | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java b/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java index 0b5d8832..cd576475 100644 --- a/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java +++ b/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java @@ -26,7 +26,6 @@ import co.aikar.commands.annotation.CommandCompletion; import co.aikar.commands.annotation.CommandPermission; import co.aikar.commands.annotation.Default; -import co.aikar.commands.annotation.Split; import co.aikar.commands.annotation.Subcommand; import co.aikar.commands.annotation.Syntax; import com.sk89q.worldedit.bukkit.BukkitAdapter; @@ -462,18 +461,34 @@ public void tax(Player player, double tax) { @Subcommand("setblocks") @CommandCompletion("@players") @CommandPermission("privatemines.setblocks") - @Syntax(" e.g. DIRT, STONE") - public void setBlocks(CommandSender sender, OfflinePlayer target, - @Split(",") String[] materials) { + @Syntax(" STONE, DIRT") + public void setBlocks(CommandSender sender, OfflinePlayer target, String materials) { Map map = new HashMap<>(); - for (String s : materials) { - if (Material.getMaterial(s.toUpperCase()) == null) { - sender.sendMessage(ChatColor.RED + "Failed to find Material: " + s); - return; + // Split the materials string into an array + String[] materialArray = materials.split(","); + + Bukkit.broadcastMessage("map keys " + map.keySet()); + + for (String string : materialArray) { + // Valid format, proceed with getting the material and percent values. + String[] parts = string.split(";"); + + Bukkit.broadcastMessage("parts size " + parts.length); + + if (parts.length == 1) { + Material material = Material.getMaterial(parts[0].toUpperCase()); + map.put(material, 1.0); + } else if (parts.length == 2) { + Material material = Material.getMaterial(parts[0].toUpperCase()); + + Bukkit.broadcastMessage("parts[1] = " + parts[1]); + double percentage = Double.parseDouble(parts[1]); + map.put(material, percentage); } - Material material = Material.valueOf(s.toUpperCase()); - map.put(material, 1.0); + + Bukkit.broadcastMessage("map keys " + map.keySet()); + } if (target != null) { From 086af1e13f2298c94c49add7eaee7490c549dd3b Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Sat, 20 Jan 2024 19:44:09 +0000 Subject: [PATCH 12/43] Basic NMS stuff. --- .../java/me/untouchedodin0/ver/Compat.java | 8 +++++++ V1_19_R2/src/main/java/NMS_v1_19_R2.java | 9 +++++++ build.gradle | 3 +++ settings.gradle | 8 +++++++ .../privatemines/PrivateMines.java | 6 +++++ .../privatemines/utils/Utils.java | 6 +++++ .../utils/worldedit/PasteHelper.java | 24 ++++++++++++++++--- 7 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 Compat/src/main/java/me/untouchedodin0/ver/Compat.java create mode 100644 V1_19_R2/src/main/java/NMS_v1_19_R2.java diff --git a/Compat/src/main/java/me/untouchedodin0/ver/Compat.java b/Compat/src/main/java/me/untouchedodin0/ver/Compat.java new file mode 100644 index 00000000..8ebe1b3b --- /dev/null +++ b/Compat/src/main/java/me/untouchedodin0/ver/Compat.java @@ -0,0 +1,8 @@ +package me.untouchedodin0.ver; + +import org.bukkit.Location; + +public interface Compat { + + String test(); +} diff --git a/V1_19_R2/src/main/java/NMS_v1_19_R2.java b/V1_19_R2/src/main/java/NMS_v1_19_R2.java new file mode 100644 index 00000000..52c66b33 --- /dev/null +++ b/V1_19_R2/src/main/java/NMS_v1_19_R2.java @@ -0,0 +1,9 @@ +import me.untouchedodin0.ver.Compat; + +public class NMS_v1_19_R2 implements Compat { + + @Override + public String test() { + return "Location Test"; + } +} diff --git a/build.gradle b/build.gradle index d458f92e..516ac190 100644 --- a/build.gradle +++ b/build.gradle @@ -75,6 +75,9 @@ dependencies { implementation "dev.triumphteam:triumph-gui:3.1.2" // Replace version here implementation 'com.convallyria.languagy:api:3.0.2' + implementation project(":Compat") + implementation project(":V1_19_2") + implementation files('libs/PrivateMinesAPI-1.3-SNAPSHOT.jar') implementation files('libs/Ocelot-API-1.0.0.jar') } diff --git a/settings.gradle b/settings.gradle index f0a99f7b..1c12f953 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,9 @@ rootProject.name = 'PrivateMines' +include 'nms' +include 'nms:v1_20_R1' +findProject(':nms:v1_20_R1')?.name = 'v1_20_R1' +include 'abstraction' +include 'Compat' +include 'V1_19_2' +include 'V1_19_R1' + diff --git a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java index fe66fbe9..3c351d27 100644 --- a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java +++ b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java @@ -61,9 +61,11 @@ import me.untouchedodin0.privatemines.storage.sql.SQLite; import me.untouchedodin0.privatemines.utils.QueueUtils; import me.untouchedodin0.privatemines.utils.UpdateChecker; +import me.untouchedodin0.privatemines.utils.Utils; import me.untouchedodin0.privatemines.utils.addon.AddonManager; import me.untouchedodin0.privatemines.utils.placeholderapi.PrivateMinesExpansion; import me.untouchedodin0.privatemines.utils.world.MineWorldManager; +import me.untouchedodin0.ver.Compat; import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.milkbowl.vault.economy.Economy; import org.bstats.bukkit.Metrics; @@ -101,6 +103,7 @@ public class PrivateMines extends JavaPlugin { private SQLHelper sqlHelper; private BukkitAudiences adventure; private AddonManager addonManager; + private Compat compat; public static PrivateMines getPrivateMines() { return privateMines; @@ -124,12 +127,15 @@ public void onEnable() { this.queueUtils = new QueueUtils(); this.addonManager = new AddonManager(); + getLogger().info("Detected NMS version: " + Utils.getNMSVersion()); + if (Config.enableTax) { registerSellListener(); } registerListeners(); setupSchematicUtils(); + if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) { boolean registered = new PrivateMinesExpansion().register(); if (registered) { diff --git a/src/main/java/me/untouchedodin0/privatemines/utils/Utils.java b/src/main/java/me/untouchedodin0/privatemines/utils/Utils.java index 42811046..61726ada 100644 --- a/src/main/java/me/untouchedodin0/privatemines/utils/Utils.java +++ b/src/main/java/me/untouchedodin0/privatemines/utils/Utils.java @@ -35,6 +35,7 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; +import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.Material; @@ -96,4 +97,9 @@ public static String mapToString(Map map) { } return ""; } + + public static String getNMSVersion() { + final String version = Bukkit.getServer().getClass().getPackage().getName(); + return version.substring(version.lastIndexOf('.') + 1); + } } diff --git a/src/main/java/me/untouchedodin0/privatemines/utils/worldedit/PasteHelper.java b/src/main/java/me/untouchedodin0/privatemines/utils/worldedit/PasteHelper.java index 20ae4416..c285902b 100644 --- a/src/main/java/me/untouchedodin0/privatemines/utils/worldedit/PasteHelper.java +++ b/src/main/java/me/untouchedodin0/privatemines/utils/worldedit/PasteHelper.java @@ -1,5 +1,6 @@ package me.untouchedodin0.privatemines.utils.worldedit; +import com.fastasyncworldedit.core.util.MaskTraverser; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.bukkit.BukkitAdapter; @@ -7,8 +8,11 @@ import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat; import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats; import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.operation.ForwardExtentCopy; import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.function.operation.Operations; +import com.sk89q.worldedit.function.visitor.RegionVisitor; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.Vector3; import com.sk89q.worldedit.regions.Region; @@ -21,6 +25,7 @@ import java.io.IOException; import me.untouchedodin0.privatemines.utils.worldedit.objects.PastedMine; import org.bukkit.Location; +import org.bukkit.Material; public class PasteHelper { @@ -80,7 +85,8 @@ public PastedMine paste(File file, Location location) { throw new RuntimeException(e); } - Region region = clipboard.getRegion(); + +/* Region region = clipboard.getRegion(); BlockVector3 clipboardOffset = clipboard.getRegion().getMinimumPoint() .subtract(clipboard.getOrigin()); @@ -90,9 +96,20 @@ public PastedMine paste(File file, Location location) { .apply(region.getMaximumPoint().subtract(region.getMinimumPoint()).toVector3())); RegionSelector regionSelector = new CuboidRegionSelector(world, realTo.toBlockPoint(), max.toBlockPoint()); - newRegion = regionSelector.getRegion(); + newRegion = regionSelector.getRegion();*/ try (EditSession editSession = WorldEdit.getInstance().newEditSession(world)) { + + Region region = clipboard.getRegion(); + BlockVector3 min = region.getMinimumPoint(); + BlockVector3 max = region.getMaximumPoint(); + Mask mask = BukkitAdapter.adapt(Material.AIR.createBlockData()).toMask(); + + new MaskTraverser(mask).setNewExtent(editSession); + + ForwardExtentCopy forwardExtentCopy = new ForwardExtentCopy(editSession, region, editSession, to); + + Operation operation = new ClipboardHolder(clipboard).createPaste(editSession).to(to) .ignoreAirBlocks(true) // configure here @@ -104,11 +121,12 @@ public PastedMine paste(File file, Location location) { Location fullMin = BukkitAdapter.adapt(BukkitAdapter.adapt(world), newRegion.getMinimumPoint()); Location fullMax = BukkitAdapter.adapt(BukkitAdapter.adapt(world), newRegion.getMaximumPoint()); - setSpawn(spawn); +/* setSpawn(spawn); setCorner1(upperRails); setCorner2(lowerRails); setMinimum(fullMin); setMaximum(fullMax); + */ return pastedMine; } } From e1624f7d20bc5c03b9eebc88d81941db2d7e13f9 Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Sun, 21 Jan 2024 21:55:19 +0000 Subject: [PATCH 13/43] Remove NMS Stuff Improve gradle script for compile speed. Added sendpacket debug command --- .../java/me/untouchedodin0/ver/Compat.java | 8 --- V1_19_R2/src/main/java/NMS_v1_19_R2.java | 9 --- build.gradle | 23 ++++---- .../privatemines/PrivateMines.java | 8 ++- .../commands/PrivateMinesCommand.java | 59 +++++++++++++++---- .../utils/worldedit/PasteHelper.java | 10 +--- .../kotlin/utils/AudienceUtils.kt | 1 - 7 files changed, 66 insertions(+), 52 deletions(-) delete mode 100644 Compat/src/main/java/me/untouchedodin0/ver/Compat.java delete mode 100644 V1_19_R2/src/main/java/NMS_v1_19_R2.java diff --git a/Compat/src/main/java/me/untouchedodin0/ver/Compat.java b/Compat/src/main/java/me/untouchedodin0/ver/Compat.java deleted file mode 100644 index 8ebe1b3b..00000000 --- a/Compat/src/main/java/me/untouchedodin0/ver/Compat.java +++ /dev/null @@ -1,8 +0,0 @@ -package me.untouchedodin0.ver; - -import org.bukkit.Location; - -public interface Compat { - - String test(); -} diff --git a/V1_19_R2/src/main/java/NMS_v1_19_R2.java b/V1_19_R2/src/main/java/NMS_v1_19_R2.java deleted file mode 100644 index 52c66b33..00000000 --- a/V1_19_R2/src/main/java/NMS_v1_19_R2.java +++ /dev/null @@ -1,9 +0,0 @@ -import me.untouchedodin0.ver.Compat; - -public class NMS_v1_19_R2 implements Compat { - - @Override - public String test() { - return "Location Test"; - } -} diff --git a/build.gradle b/build.gradle index 516ac190..8ccdeca8 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ group 'me.untouchedodin0.privatemines' version = pluginVersion -def devBuild = false +def devBuild = true if (devBuild) { version = version + "-DEV" @@ -41,7 +41,6 @@ repositories { maven { url = 'https://repo.dmulloy2.net/repository/public/' } maven { url = 'https://repo.extendedclip.com/content/repositories/placeholderapi/' } maven { url = 'https://repo.mineinabyss.com/releases' } -// maven { url = "https://repo.mattstudios.me/artifactory/public/" } maven { url = 'https://repo.convallyria.com/releases' } } @@ -60,6 +59,7 @@ dependencies { compileOnly 'com.github.LoneDev6:API-ItemsAdder:3.6.1' compileOnly 'com.google.code.gson:gson:2.10' compileOnly 'com.fasterxml.jackson.core:jackson-databind:2.15.0' // i think this is the big thing? + compileOnly 'com.comphenix.protocol:ProtocolLib:5.1.0' compileOnly files('libs/XPrison-1.12.11-RELEASE.jar') compileOnly files('libs/AutoSellAPI-1.2.0.jar') @@ -75,9 +75,6 @@ dependencies { implementation "dev.triumphteam:triumph-gui:3.1.2" // Replace version here implementation 'com.convallyria.languagy:api:3.0.2' - implementation project(":Compat") - implementation project(":V1_19_2") - implementation files('libs/PrivateMinesAPI-1.3-SNAPSHOT.jar') implementation files('libs/Ocelot-API-1.0.0.jar') } @@ -114,14 +111,14 @@ test { useJUnitPlatform() } -compileKotlin { - kotlinOptions { - jvmTarget = "17" +kotlin { + jvmToolchain { + languageVersion = JavaLanguageVersion.of(17) } } -compileTestKotlin { - kotlinOptions { - jvmTarget = "17" - } -} \ No newline at end of file +//compileKotlin { +// kotlinOptions { +// jvmTarget = "17" +// } +//} \ No newline at end of file diff --git a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java index 3c351d27..829f8f63 100644 --- a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java +++ b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java @@ -22,6 +22,8 @@ package me.untouchedodin0.privatemines; import co.aikar.commands.BukkitCommandManager; +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.ProtocolManager; import java.io.File; import java.io.IOException; import java.nio.file.FileSystems; @@ -65,7 +67,6 @@ import me.untouchedodin0.privatemines.utils.addon.AddonManager; import me.untouchedodin0.privatemines.utils.placeholderapi.PrivateMinesExpansion; import me.untouchedodin0.privatemines.utils.world.MineWorldManager; -import me.untouchedodin0.ver.Compat; import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.milkbowl.vault.economy.Economy; import org.bstats.bukkit.Metrics; @@ -97,13 +98,13 @@ public class PrivateMines extends JavaPlugin { private PregenStorage pregenStorage; private MineWorldManager mineWorldManager; private MineTypeManager mineTypeManager; + private ProtocolManager protocolManager; private QueueUtils queueUtils; private static Economy econ = null; private SQLite sqlite; private SQLHelper sqlHelper; private BukkitAudiences adventure; private AddonManager addonManager; - private Compat compat; public static PrivateMines getPrivateMines() { return privateMines; @@ -126,8 +127,9 @@ public void onEnable() { this.mineTypeManager = new MineTypeManager(this); this.queueUtils = new QueueUtils(); this.addonManager = new AddonManager(); + this.protocolManager = ProtocolLibrary.getProtocolManager(); - getLogger().info("Detected NMS version: " + Utils.getNMSVersion()); + getLogger().info("protocol manager " + protocolManager); if (Config.enableTax) { registerSellListener(); diff --git a/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java b/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java index cd576475..74bc8f44 100644 --- a/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java +++ b/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java @@ -28,6 +28,11 @@ import co.aikar.commands.annotation.Default; import co.aikar.commands.annotation.Subcommand; import co.aikar.commands.annotation.Syntax; +import com.comphenix.protocol.PacketType.Play.Server; +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.wrappers.BlockPosition; +import com.comphenix.protocol.wrappers.WrappedBlockData; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldguard.WorldGuard; @@ -71,6 +76,7 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.Vector; import redempt.redlib.misc.LocationUtils; import redempt.redlib.misc.Task; import redempt.redlib.region.CuboidRegion; @@ -274,7 +280,6 @@ public void forceReset(CommandSender sender, OfflinePlayer target) { } } - @Subcommand("reset") @CommandPermission("privatemines.reset") public void reset(Player player) { @@ -468,27 +473,17 @@ public void setBlocks(CommandSender sender, OfflinePlayer target, String materia // Split the materials string into an array String[] materialArray = materials.split(","); - Bukkit.broadcastMessage("map keys " + map.keySet()); - for (String string : materialArray) { // Valid format, proceed with getting the material and percent values. String[] parts = string.split(";"); - - Bukkit.broadcastMessage("parts size " + parts.length); - if (parts.length == 1) { Material material = Material.getMaterial(parts[0].toUpperCase()); map.put(material, 1.0); } else if (parts.length == 2) { Material material = Material.getMaterial(parts[0].toUpperCase()); - - Bukkit.broadcastMessage("parts[1] = " + parts[1]); double percentage = Double.parseDouble(parts[1]); map.put(material, percentage); } - - Bukkit.broadcastMessage("map keys " + map.keySet()); - } if (target != null) { @@ -568,4 +563,46 @@ public void claim(Player player) { } } } + + @Subcommand("sendpacket") + public void sendPacket(Player player) { + Map> changes = new HashMap<>(); + + // Converts player location to chunk coordinates + int x = player.getLocation().getBlockX() >> 4; + int y = player.getLocation().getBlockY() >> 4; + int z = player.getLocation().getBlockZ() >> 4; + + // Creates a new BlockPosition at chunk coordinate + BlockPosition blockPosition = new BlockPosition(x, y, z); + + // Create a map to hold block changes within the chunk + Map blockChanges = new HashMap<>(); + + // Add a sample block change to the map + Vector sampleBlockLocation = new Vector(x * 16, y * 16, z * 16); // Adjust this based on your actual block location + WrappedBlockData sampleBlockData = WrappedBlockData.createData(Material.ACACIA_LOG); + blockChanges.put(sampleBlockLocation, sampleBlockData); + + changes.put(blockPosition, blockChanges); + + // Create a packet for MULTI_BLOCK_CHANGE + PacketContainer packet = new PacketContainer(Server.MULTI_BLOCK_CHANGE); + packet.getSectionPositions().write(0, blockPosition); + + WrappedBlockData[] data = new WrappedBlockData[1]; + data[0] = WrappedBlockData.createData(Material.ACACIA_LOG); + + for (Map.Entry blockEntry : changes.get(blockPosition).entrySet()) { + short[] shortLocation = new short[]{(short) ((blockEntry.getKey().getBlockX() & 0x0F) << 12 + | (blockEntry.getKey().getBlockZ() & 0x0F) << 8 | (blockEntry.getKey().getBlockY() + & 0xFF))}; + + + packet.getShortArrays().writeSafely(0, shortLocation); + packet.getBlockDataArrays().writeSafely(0, data); + } + + ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet); + } } diff --git a/src/main/java/me/untouchedodin0/privatemines/utils/worldedit/PasteHelper.java b/src/main/java/me/untouchedodin0/privatemines/utils/worldedit/PasteHelper.java index c285902b..b06cc498 100644 --- a/src/main/java/me/untouchedodin0/privatemines/utils/worldedit/PasteHelper.java +++ b/src/main/java/me/untouchedodin0/privatemines/utils/worldedit/PasteHelper.java @@ -86,7 +86,7 @@ public PastedMine paste(File file, Location location) { } -/* Region region = clipboard.getRegion(); + Region region = clipboard.getRegion(); BlockVector3 clipboardOffset = clipboard.getRegion().getMinimumPoint() .subtract(clipboard.getOrigin()); @@ -96,13 +96,10 @@ public PastedMine paste(File file, Location location) { .apply(region.getMaximumPoint().subtract(region.getMinimumPoint()).toVector3())); RegionSelector regionSelector = new CuboidRegionSelector(world, realTo.toBlockPoint(), max.toBlockPoint()); - newRegion = regionSelector.getRegion();*/ + newRegion = regionSelector.getRegion(); try (EditSession editSession = WorldEdit.getInstance().newEditSession(world)) { - Region region = clipboard.getRegion(); - BlockVector3 min = region.getMinimumPoint(); - BlockVector3 max = region.getMaximumPoint(); Mask mask = BukkitAdapter.adapt(Material.AIR.createBlockData()).toMask(); new MaskTraverser(mask).setNewExtent(editSession); @@ -121,12 +118,11 @@ public PastedMine paste(File file, Location location) { Location fullMin = BukkitAdapter.adapt(BukkitAdapter.adapt(world), newRegion.getMinimumPoint()); Location fullMax = BukkitAdapter.adapt(BukkitAdapter.adapt(world), newRegion.getMaximumPoint()); -/* setSpawn(spawn); + setSpawn(spawn); setCorner1(upperRails); setCorner2(lowerRails); setMinimum(fullMin); setMaximum(fullMax); - */ return pastedMine; } } diff --git a/src/main/kotlin/me/untouchedodin0/kotlin/utils/AudienceUtils.kt b/src/main/kotlin/me/untouchedodin0/kotlin/utils/AudienceUtils.kt index 1573b5c3..732bc89d 100644 --- a/src/main/kotlin/me/untouchedodin0/kotlin/utils/AudienceUtils.kt +++ b/src/main/kotlin/me/untouchedodin0/kotlin/utils/AudienceUtils.kt @@ -51,7 +51,6 @@ class AudienceUtils { fun sendMessage(player: Player, textComponent: TextComponent) { if (privateMines.adventure != null) { - val miniMessage = MiniMessage.miniMessage() val audiences = privateMines.adventure val audience = audiences.player(player) audience.sendMessage(textComponent) From cbde3c1a1f03ad718bcea0981265d7c0f17bae5b Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Sun, 21 Jan 2024 22:06:31 +0000 Subject: [PATCH 14/43] Tidy up --- build.gradle | 10 ++----- settings.gradle | 8 +----- .../privatemines/PrivateMines.java | 27 +++++-------------- 3 files changed, 9 insertions(+), 36 deletions(-) diff --git a/build.gradle b/build.gradle index 8ccdeca8..cb6a9523 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ plugins { id 'java' id 'java-library' id 'maven-publish' - id 'io.github.gradle-nexus.publish-plugin' version "1.1.0" + id 'io.github.gradle-nexus.publish-plugin' version '1.1.0' id 'com.github.johnrengelman.shadow' version '7.1.2' id 'org.jetbrains.kotlin.jvm' version '1.8.22' id 'net.minecrell.plugin-yml.bukkit' version '0.6.0' @@ -115,10 +115,4 @@ kotlin { jvmToolchain { languageVersion = JavaLanguageVersion.of(17) } -} - -//compileKotlin { -// kotlinOptions { -// jvmTarget = "17" -// } -//} \ No newline at end of file +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 1c12f953..88795759 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,9 +1,3 @@ rootProject.name = 'PrivateMines' -include 'nms' -include 'nms:v1_20_R1' -findProject(':nms:v1_20_R1')?.name = 'v1_20_R1' -include 'abstraction' -include 'Compat' -include 'V1_19_2' -include 'V1_19_R1' + diff --git a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java index 829f8f63..3f96cc3a 100644 --- a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java +++ b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java @@ -26,29 +26,16 @@ import com.comphenix.protocol.ProtocolManager; import java.io.File; import java.io.IOException; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.PathMatcher; -import java.time.Duration; -import java.time.Instant; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; +import java.nio.file.*; +import java.time.*; +import java.util.*; import java.util.stream.Stream; import me.untouchedodin0.kotlin.mine.data.MineData; import me.untouchedodin0.kotlin.mine.storage.MineStorage; import me.untouchedodin0.kotlin.mine.storage.PregenStorage; import me.untouchedodin0.kotlin.mine.type.MineType; -import me.untouchedodin0.privatemines.commands.AddonsCommand; -import me.untouchedodin0.privatemines.commands.PrivateMinesCommand; -import me.untouchedodin0.privatemines.commands.PublicMinesCommand; -import me.untouchedodin0.privatemines.config.Config; -import me.untouchedodin0.privatemines.config.MenuConfig; -import me.untouchedodin0.privatemines.config.MessagesConfig; -import me.untouchedodin0.privatemines.config.MineConfig; +import me.untouchedodin0.privatemines.commands.*; +import me.untouchedodin0.privatemines.config.*; import me.untouchedodin0.privatemines.factory.MineFactory; import me.untouchedodin0.privatemines.iterator.SchematicIterator; import me.untouchedodin0.privatemines.listener.MineResetListener; @@ -61,9 +48,7 @@ import me.untouchedodin0.privatemines.storage.StorageType; import me.untouchedodin0.privatemines.storage.sql.SQLUtils; import me.untouchedodin0.privatemines.storage.sql.SQLite; -import me.untouchedodin0.privatemines.utils.QueueUtils; -import me.untouchedodin0.privatemines.utils.UpdateChecker; -import me.untouchedodin0.privatemines.utils.Utils; +import me.untouchedodin0.privatemines.utils.*; import me.untouchedodin0.privatemines.utils.addon.AddonManager; import me.untouchedodin0.privatemines.utils.placeholderapi.PrivateMinesExpansion; import me.untouchedodin0.privatemines.utils.world.MineWorldManager; From 85d4b75dd3b75677d44983537689b0f1b8c588e1 Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Sun, 21 Jan 2024 22:09:24 +0000 Subject: [PATCH 15/43] Updated gradle from 7.6 -> 8.5 --- gradle/wrapper/gradle-wrapper.jar | Bin 61574 -> 43462 bytes gradle/wrapper/gradle-wrapper.properties | 3 ++- gradlew | 29 +++++++++++++---------- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cbfa754578e88a3dae77fce6e3dea56edbf..d64cd4917707c1f8861d8cb53dd15194d4248596 100644 GIT binary patch literal 43462 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 literal 61574 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 ScNgau(U?ep-K_E5zy1%ZQTdPn diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f398c33c..1af9e093 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68d..1aa94a42 100755 --- a/gradlew +++ b/gradlew @@ -83,10 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ From e154fc2ec41767fd938ae7f202799c4eef44cb09 Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Mon, 24 Jun 2024 22:08:39 +0100 Subject: [PATCH 16/43] Fixed pregen stuff --- build.gradle | 1 - gradle/wrapper/gradle-wrapper.properties | 2 +- .../privatemines/PrivateMines.java | 6 --- .../commands/PrivateMinesCommand.java | 49 +------------------ .../privatemines/factory/PregenFactory.java | 2 +- .../listener/PlayerJoinListener.java | 8 +-- .../privatemines/utils/QueueUtils.java | 13 +++-- .../kotlin/mine/pregen/PregenMine.kt | 2 + 8 files changed, 15 insertions(+), 68 deletions(-) diff --git a/build.gradle b/build.gradle index cb6a9523..8a6df827 100644 --- a/build.gradle +++ b/build.gradle @@ -59,7 +59,6 @@ dependencies { compileOnly 'com.github.LoneDev6:API-ItemsAdder:3.6.1' compileOnly 'com.google.code.gson:gson:2.10' compileOnly 'com.fasterxml.jackson.core:jackson-databind:2.15.0' // i think this is the big thing? - compileOnly 'com.comphenix.protocol:ProtocolLib:5.1.0' compileOnly files('libs/XPrison-1.12.11-RELEASE.jar') compileOnly files('libs/AutoSellAPI-1.2.0.jar') diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1af9e093..a4413138 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java index 3f96cc3a..fe7d33bd 100644 --- a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java +++ b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java @@ -22,8 +22,6 @@ package me.untouchedodin0.privatemines; import co.aikar.commands.BukkitCommandManager; -import com.comphenix.protocol.ProtocolLibrary; -import com.comphenix.protocol.ProtocolManager; import java.io.File; import java.io.IOException; import java.nio.file.*; @@ -83,7 +81,6 @@ public class PrivateMines extends JavaPlugin { private PregenStorage pregenStorage; private MineWorldManager mineWorldManager; private MineTypeManager mineTypeManager; - private ProtocolManager protocolManager; private QueueUtils queueUtils; private static Economy econ = null; private SQLite sqlite; @@ -112,9 +109,6 @@ public void onEnable() { this.mineTypeManager = new MineTypeManager(this); this.queueUtils = new QueueUtils(); this.addonManager = new AddonManager(); - this.protocolManager = ProtocolLibrary.getProtocolManager(); - - getLogger().info("protocol manager " + protocolManager); if (Config.enableTax) { registerSellListener(); diff --git a/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java b/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java index 74bc8f44..8d8724b7 100644 --- a/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java +++ b/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java @@ -28,11 +28,6 @@ import co.aikar.commands.annotation.Default; import co.aikar.commands.annotation.Subcommand; import co.aikar.commands.annotation.Syntax; -import com.comphenix.protocol.PacketType.Play.Server; -import com.comphenix.protocol.ProtocolLibrary; -import com.comphenix.protocol.events.PacketContainer; -import com.comphenix.protocol.wrappers.BlockPosition; -import com.comphenix.protocol.wrappers.WrappedBlockData; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldguard.WorldGuard; @@ -76,7 +71,6 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.util.Vector; import redempt.redlib.misc.LocationUtils; import redempt.redlib.misc.Task; import redempt.redlib.region.CuboidRegion; @@ -559,50 +553,9 @@ public void claim(Player player) { Task.syncDelayed(() -> spawn.getBlock().setType(Material.AIR, false)); Task.syncDelayed(() -> flagUtils.setFlags(mine)); pregenMine.teleport(player); + Task.syncDelayed(() -> Objects.requireNonNull(pregenMine.getSpawnLocation()).subtract(0, 0, 1).getBlock().setType(Material.AIR)); mine.handleReset(); } } } - - @Subcommand("sendpacket") - public void sendPacket(Player player) { - Map> changes = new HashMap<>(); - - // Converts player location to chunk coordinates - int x = player.getLocation().getBlockX() >> 4; - int y = player.getLocation().getBlockY() >> 4; - int z = player.getLocation().getBlockZ() >> 4; - - // Creates a new BlockPosition at chunk coordinate - BlockPosition blockPosition = new BlockPosition(x, y, z); - - // Create a map to hold block changes within the chunk - Map blockChanges = new HashMap<>(); - - // Add a sample block change to the map - Vector sampleBlockLocation = new Vector(x * 16, y * 16, z * 16); // Adjust this based on your actual block location - WrappedBlockData sampleBlockData = WrappedBlockData.createData(Material.ACACIA_LOG); - blockChanges.put(sampleBlockLocation, sampleBlockData); - - changes.put(blockPosition, blockChanges); - - // Create a packet for MULTI_BLOCK_CHANGE - PacketContainer packet = new PacketContainer(Server.MULTI_BLOCK_CHANGE); - packet.getSectionPositions().write(0, blockPosition); - - WrappedBlockData[] data = new WrappedBlockData[1]; - data[0] = WrappedBlockData.createData(Material.ACACIA_LOG); - - for (Map.Entry blockEntry : changes.get(blockPosition).entrySet()) { - short[] shortLocation = new short[]{(short) ((blockEntry.getKey().getBlockX() & 0x0F) << 12 - | (blockEntry.getKey().getBlockZ() & 0x0F) << 8 | (blockEntry.getKey().getBlockY() - & 0xFF))}; - - - packet.getShortArrays().writeSafely(0, shortLocation); - packet.getBlockDataArrays().writeSafely(0, data); - } - - ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet); - } } diff --git a/src/main/java/me/untouchedodin0/privatemines/factory/PregenFactory.java b/src/main/java/me/untouchedodin0/privatemines/factory/PregenFactory.java index 915d19b5..e5028282 100644 --- a/src/main/java/me/untouchedodin0/privatemines/factory/PregenFactory.java +++ b/src/main/java/me/untouchedodin0/privatemines/factory/PregenFactory.java @@ -50,7 +50,7 @@ public static void pregen(Player player, int amount) { File schematicFile = new File("plugins/PrivateMines/schematics/" + mineType.getFile()); PregenStorage pregenStorage = privateMines.getPregenStorage(); - location = player.getLocation(); + location = mineWorldManager.getNextFreeLocation(); //player.getLocation(); location.getBlock().setType(Material.GLOWSTONE); new BukkitRunnable() { diff --git a/src/main/java/me/untouchedodin0/privatemines/listener/PlayerJoinListener.java b/src/main/java/me/untouchedodin0/privatemines/listener/PlayerJoinListener.java index 04bedc97..57dca95f 100644 --- a/src/main/java/me/untouchedodin0/privatemines/listener/PlayerJoinListener.java +++ b/src/main/java/me/untouchedodin0/privatemines/listener/PlayerJoinListener.java @@ -26,6 +26,7 @@ import me.untouchedodin0.privatemines.config.Config; import me.untouchedodin0.privatemines.mine.Mine; import me.untouchedodin0.privatemines.utils.QueueUtils; +import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -43,12 +44,7 @@ public void onJoin(PlayerJoinEvent playerJoinEvent) { if (Config.giveMineOnFirstJoin) { Player player = playerJoinEvent.getPlayer(); -// if (mineStorage.hasMine(player)) { -// Mine mine = mineStorage.get(player); -// if (mine != null) { -// mine.startTasks(); -// } -// } + Bukkit.broadcastMessage("checking user : " + player); QueueUtils queueUtils = privateMines.getQueueUtils(); if (queueUtils.isInQueue(player.getUniqueId())) { diff --git a/src/main/java/me/untouchedodin0/privatemines/utils/QueueUtils.java b/src/main/java/me/untouchedodin0/privatemines/utils/QueueUtils.java index d2f31a80..f82bdeb7 100644 --- a/src/main/java/me/untouchedodin0/privatemines/utils/QueueUtils.java +++ b/src/main/java/me/untouchedodin0/privatemines/utils/QueueUtils.java @@ -22,6 +22,7 @@ import me.untouchedodin0.privatemines.mine.Mine; import me.untouchedodin0.privatemines.mine.MineTypeManager; import me.untouchedodin0.privatemines.storage.sql.SQLUtils; +import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.Material; @@ -87,10 +88,6 @@ public void claim(Player player) { } } int estimateSeconds = place.get() * 3; - -// player.sendTitle(ChatColor.GREEN + "You're at slot #" + slot.get(), -// ChatColor.YELLOW + String.format(" Estimated wait time: %d seconds!", -// estimateSeconds)); } } }, 0L, 60L); @@ -105,6 +102,12 @@ public void claim(Player player) { PregenMine pregenMine = pregenStorage.getAndRemove(); MineType mineType = mineTypeManager.getDefaultMineType(); + + if (pregenMine == null) { + privateMines.getLogger().info("No pregen mines are left."); + return; + } + Location location = pregenMine.getLocation(); Location spawn = pregenMine.getSpawnLocation(); Location corner1 = pregenMine.getLowerRails(); @@ -139,7 +142,7 @@ public void claim(Player player) { mineStorage.addMine(player.getUniqueId(), mine); - Task.syncDelayed(() -> spawn.getBlock().setType(Material.AIR, false)); + spawn.getBlock().setType(Material.AIR); pregenMine.teleport(player); mine.handleReset(); } diff --git a/src/main/kotlin/me/untouchedodin0/kotlin/mine/pregen/PregenMine.kt b/src/main/kotlin/me/untouchedodin0/kotlin/mine/pregen/PregenMine.kt index ba3d7100..f07231bc 100644 --- a/src/main/kotlin/me/untouchedodin0/kotlin/mine/pregen/PregenMine.kt +++ b/src/main/kotlin/me/untouchedodin0/kotlin/mine/pregen/PregenMine.kt @@ -24,6 +24,7 @@ package me.untouchedodin0.kotlin.mine.pregen import me.untouchedodin0.privatemines.PrivateMines import me.untouchedodin0.privatemines.utils.Utils import org.bukkit.Location +import org.bukkit.Material import org.bukkit.configuration.file.YamlConfiguration import org.bukkit.entity.Player import redempt.redlib.misc.LocationUtils @@ -42,6 +43,7 @@ class PregenMine { fun teleport(player: Player) { + spawnLocation?.block!!.type = Material.AIR spawnLocation?.let(player::teleport) } } \ No newline at end of file From 3d6925c561e74aab3f267db3d687c04eba4bcbe2 Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Tue, 25 Jun 2024 21:25:46 +0100 Subject: [PATCH 17/43] Tidy up PregenFactory#pregen Tidy up the PrivateMinesCommand#claim command --- .../commands/PrivateMinesCommand.java | 87 +++++++++-------- .../privatemines/factory/PregenFactory.java | 96 +------------------ 2 files changed, 49 insertions(+), 134 deletions(-) diff --git a/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java b/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java index 8d8724b7..823a1324 100644 --- a/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java +++ b/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java @@ -497,7 +497,7 @@ public void setBlocks(CommandSender sender, OfflinePlayer target, String materia @CommandPermission("privatemines.pregen") @Syntax("") public void pregen(Player player, int amount) { - PregenFactory.pregen(player, amount); + PregenFactory.pregen(amount); } @Subcommand("claim") @@ -515,46 +515,53 @@ public void claim(Player player) { } else { PregenMine pregenMine = pregenStorage.getAndRemove(); MineType mineType = mineTypeManager.getDefaultMineType(); - Location location = pregenMine.getLocation(); - Location spawn = pregenMine.getSpawnLocation(); - Location corner1 = pregenMine.getLowerRails(); - Location corner2 = pregenMine.getUpperRails(); - Location minimum = pregenMine.getFullMin(); - Location maximum = pregenMine.getFullMax(); - BlockVector3 miningRegionMin = BukkitAdapter.asBlockVector(Objects.requireNonNull(corner1)); - BlockVector3 miningRegionMax = BukkitAdapter.asBlockVector(Objects.requireNonNull(corner2)); - BlockVector3 fullRegionMin = BukkitAdapter.asBlockVector(Objects.requireNonNull(minimum)); - BlockVector3 fullRegionMax = BukkitAdapter.asBlockVector(Objects.requireNonNull(maximum)); - FlagUtils flagUtils = new FlagUtils(); - - RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer(); - RegionManager regionManager = container.get( - BukkitAdapter.adapt(Objects.requireNonNull(spawn).getWorld())); - - ProtectedCuboidRegion miningRegion = new ProtectedCuboidRegion(mineRegionName, - miningRegionMin, miningRegionMax); - ProtectedCuboidRegion fullRegion = new ProtectedCuboidRegion(fullRegionName, fullRegionMin, - fullRegionMax); - - if (regionManager != null) { - regionManager.addRegion(miningRegion); - regionManager.addRegion(fullRegion); - } - - Mine mine = new Mine(privateMines); - MineData mineData = new MineData(player.getUniqueId(), corner2, corner1, minimum, maximum, - Objects.requireNonNull(location), spawn, mineType, false, 5.0); - mine.setMineData(mineData); - SQLUtils.claim(location); - SQLUtils.insert(mine); - - mineStorage.addMine(player.getUniqueId(), mine); + Location location; + if (pregenMine != null) { + location = pregenMine.getLocation(); + Location spawn = pregenMine.getSpawnLocation(); + Location corner1 = pregenMine.getLowerRails(); + Location corner2 = pregenMine.getUpperRails(); + Location minimum = pregenMine.getFullMin(); + Location maximum = pregenMine.getFullMax(); + BlockVector3 miningRegionMin = BukkitAdapter.asBlockVector( + Objects.requireNonNull(corner1)); + BlockVector3 miningRegionMax = BukkitAdapter.asBlockVector( + Objects.requireNonNull(corner2)); + BlockVector3 fullRegionMin = BukkitAdapter.asBlockVector(Objects.requireNonNull(minimum)); + BlockVector3 fullRegionMax = BukkitAdapter.asBlockVector(Objects.requireNonNull(maximum)); + FlagUtils flagUtils = new FlagUtils(); + + RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer(); + RegionManager regionManager = container.get( + BukkitAdapter.adapt(Objects.requireNonNull(spawn).getWorld())); + + ProtectedCuboidRegion miningRegion = new ProtectedCuboidRegion(mineRegionName, + miningRegionMin, miningRegionMax); + ProtectedCuboidRegion fullRegion = new ProtectedCuboidRegion(fullRegionName, + fullRegionMin, fullRegionMax); + + if (regionManager != null) { + regionManager.addRegion(miningRegion); + regionManager.addRegion(fullRegion); + } - Task.syncDelayed(() -> spawn.getBlock().setType(Material.AIR, false)); - Task.syncDelayed(() -> flagUtils.setFlags(mine)); - pregenMine.teleport(player); - Task.syncDelayed(() -> Objects.requireNonNull(pregenMine.getSpawnLocation()).subtract(0, 0, 1).getBlock().setType(Material.AIR)); - mine.handleReset(); + Mine mine = new Mine(privateMines); + MineData mineData = new MineData(player.getUniqueId(), corner2, corner1, minimum, maximum, + Objects.requireNonNull(location), spawn, mineType, false, 5.0); + mine.setMineData(mineData); + SQLUtils.claim(location); + SQLUtils.insert(mine); + + mineStorage.addMine(player.getUniqueId(), mine); + + Task.syncDelayed(() -> spawn.getBlock().setType(Material.AIR, false)); + Task.syncDelayed(() -> flagUtils.setFlags(mine)); + pregenMine.teleport(player); + Task.syncDelayed( + () -> Objects.requireNonNull(pregenMine.getSpawnLocation()).subtract(0, 0, 1) + .getBlock().setType(Material.AIR)); + mine.handleReset(); + } } } } diff --git a/src/main/java/me/untouchedodin0/privatemines/factory/PregenFactory.java b/src/main/java/me/untouchedodin0/privatemines/factory/PregenFactory.java index e5028282..e375fcf3 100644 --- a/src/main/java/me/untouchedodin0/privatemines/factory/PregenFactory.java +++ b/src/main/java/me/untouchedodin0/privatemines/factory/PregenFactory.java @@ -44,13 +44,13 @@ public class PregenFactory { private static Clipboard clipboard; private static ClipboardHolder clipboardHolder; - public static void pregen(Player player, int amount) { + public static void pregen(int amount) { Location location; MineType mineType = mineTypeManager.getDefaultMineType(); File schematicFile = new File("plugins/PrivateMines/schematics/" + mineType.getFile()); PregenStorage pregenStorage = privateMines.getPregenStorage(); - location = mineWorldManager.getNextFreeLocation(); //player.getLocation(); + location = mineWorldManager.getNextFreeLocation(); location.getBlock().setType(Material.GLOWSTONE); new BukkitRunnable() { @@ -135,95 +135,3 @@ public void run() { }.runTaskTimerAsynchronously(privateMines, 20L, 20L); } } - -// BukkitRunnable bukkitRunnable = new BukkitRunnable() { -// int i = 0; -// -// @Override -// public void run() { -// i++; -// Bukkit.broadcastMessage("We're at #" + i); -// -// if (i == 5) { -// cancel(); -// } -// } -// }; - -// bukkitRunnable.run(); -// task = Task.asyncRepeating(() -> { -// PregenMine pregenMine = new PregenMine(); -// ClipboardFormat clipboardFormat = ClipboardFormats.findByFile(schematicFile); -// BlockVector3 vector = BlockVector3.at(location.getBlockX(), location.getBlockY(), -// location.getBlockZ()); -// SchematicStorage storage = privateMines.getSchematicStorage(); -// SchematicIterator.MineBlocks mineBlocks = storage.getMineBlocksMap().get(schematicFile); -// if (clipboardFormat != null) { -// try (ClipboardReader clipboardReader = clipboardFormat.getReader(new FileInputStream(schematicFile))) { -// World world = BukkitAdapter.adapt(location.getWorld()); -// try (EditSession editSession = WorldEdit.getInstance().newEditSessionBuilder() -// .world(world).fastMode(true).build()) { -// clipboard = clipboardReader.read(); -// clipboardHolder = new ClipboardHolder(clipboard); -// -// Operation operation = clipboardHolder.createPaste(editSession).to(vector).ignoreAirBlocks(true).build(); -// Operations.complete(operation); -// } catch (IOException e) { -// throw new RuntimeException(e); -// } -// } catch (IOException e) { -// throw new RuntimeException(e); -// } -// } -// -// location.add(0, 0, 100); -// Chunk chunk = location.getChunk(); -// Task.syncDelayed(() -> chunk.load(true)); -// -// BlockVector3 lrailsV = vector.subtract(mineBlocks.getSpawnLocation()) -// .add(mineBlocks.getCorner2().add(0, 0, 1)); -// BlockVector3 urailsV = vector.subtract(mineBlocks.getSpawnLocation()) -// .add(mineBlocks.getCorner1().add(0, 0, 1)); -// -// Region region = clipboard.getRegion(); -// Region newRegion; -// -// BlockVector3 clipboardOffset = clipboard.getRegion().getMinimumPoint() -// .subtract(clipboard.getOrigin()); -// Vector3 realTo = vector.toVector3() -// .add(clipboardHolder.getTransform().apply(clipboardOffset.toVector3())); -// Vector3 max = realTo.add(clipboardHolder.getTransform() -// .apply(region.getMaximumPoint().subtract(region.getMinimumPoint()).toVector3())); -// RegionSelector regionSelector = new CuboidRegionSelector(BukkitAdapter.adapt(location.getWorld()), -// realTo.toBlockPoint(), -// max.toBlockPoint()); -// newRegion = regionSelector.getRegion(); -// -// Location spongeL = new Location(location.getWorld(), vector.getBlockX(), -// vector.getBlockY(), vector.getBlockZ() + 1); -// Location lrailsL = new Location(location.getWorld(), lrailsV.getBlockX(), -// lrailsV.getBlockY(), lrailsV.getBlockZ()); -// Location urailsL = new Location(location.getWorld(), urailsV.getBlockX(), -// urailsV.getBlockY(), urailsV.getBlockZ()); -// Location fullMin = BukkitAdapter.adapt(Objects.requireNonNull(location.getWorld()), -// newRegion.getMinimumPoint()); -// Location fullMax = BukkitAdapter.adapt(location.getWorld(), -// newRegion.getMaximumPoint()); -// -// Bukkit.broadcastMessage("lraisv " + lrailsV); -// Bukkit.broadcastMessage("urailsV " + urailsV); -// Bukkit.broadcastMessage("spongeL " + LocationUtils.toString(spongeL)); -// Bukkit.broadcastMessage("lrailsL " + LocationUtils.toString(lrailsL)); -// Bukkit.broadcastMessage("urailsL " + LocationUtils.toString(urailsL)); -// -// pregenMine.setLocation(location); -// pregenMine.setSpawnLocation(spongeL); -// pregenMine.setLowerRails(lrailsL); -// pregenMine.setUpperRails(urailsL); -// pregenMine.setFullMin(fullMin); -// pregenMine.setFullMax(fullMax); -// -// SQLUtils.insertPregen(pregenMine); -// }, 20, 60); -// -// Task.syncDelayed(task::cancel, amount * 100L); From 5508c05ccb81bd5ddec45c112909847e195ad1c8 Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Tue, 25 Jun 2024 22:12:40 +0100 Subject: [PATCH 18/43] Add a progress message in PregenFactory --- .../privatemines/commands/PrivateMinesCommand.java | 2 +- .../privatemines/factory/PregenFactory.java | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java b/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java index 823a1324..ac220a75 100644 --- a/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java +++ b/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java @@ -497,7 +497,7 @@ public void setBlocks(CommandSender sender, OfflinePlayer target, String materia @CommandPermission("privatemines.pregen") @Syntax("") public void pregen(Player player, int amount) { - PregenFactory.pregen(amount); + PregenFactory.pregen(player, amount); } @Subcommand("claim") diff --git a/src/main/java/me/untouchedodin0/privatemines/factory/PregenFactory.java b/src/main/java/me/untouchedodin0/privatemines/factory/PregenFactory.java index e375fcf3..15ac9435 100644 --- a/src/main/java/me/untouchedodin0/privatemines/factory/PregenFactory.java +++ b/src/main/java/me/untouchedodin0/privatemines/factory/PregenFactory.java @@ -29,6 +29,7 @@ import me.untouchedodin0.privatemines.storage.SchematicStorage; import me.untouchedodin0.privatemines.storage.sql.SQLUtils; import me.untouchedodin0.privatemines.utils.world.MineWorldManager; +import org.bukkit.ChatColor; import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.Material; @@ -44,7 +45,7 @@ public class PregenFactory { private static Clipboard clipboard; private static ClipboardHolder clipboardHolder; - public static void pregen(int amount) { + public static void pregen(Player player, int amount) { Location location; MineType mineType = mineTypeManager.getDefaultMineType(); File schematicFile = new File("plugins/PrivateMines/schematics/" + mineType.getFile()); @@ -54,14 +55,14 @@ public static void pregen(int amount) { location.getBlock().setType(Material.GLOWSTONE); new BukkitRunnable() { - private int i = 0; + private int current = 0; @Override public void run() { - if (i == amount) { + if (current == amount) { cancel(); } else { - i++; + current++; PregenMine pregenMine = new PregenMine(); ClipboardFormat clipboardFormat = ClipboardFormats.findByFile(schematicFile); BlockVector3 vector = BlockVector3.at(location.getBlockX(), location.getBlockY(), @@ -130,6 +131,10 @@ public void run() { pregenStorage.addMine(pregenMine); Task.syncDelayed(() -> spongeL.getBlock().setType(Material.AIR, false)); + + double percentage = ((double) current / (double) amount) * 100; + String message = String.format("Pregeneration Progress: %d/%d Completed (%.2f%%)", current, amount, percentage); + player.sendMessage(ChatColor.GREEN + message); } } }.runTaskTimerAsynchronously(privateMines, 20L, 20L); From e2697061eb2671d7e6aa559a70637957881f99e2 Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Thu, 27 Jun 2024 18:09:46 +0100 Subject: [PATCH 19/43] Rewrote the Schematic Iteration system The old class of SchematicIteratorOriginal was causing some performance issues whereas the new one uses multiple threads to spread the load. --- .../privatemines/PrivateMines.java | 28 ++- .../privatemines/factory/PregenFactory.java | 4 +- .../privatemines/iterator/BlockVisitor.java | 69 +++++++ .../iterator/SchematicIterator.java | 145 +++++++-------- .../iterator/SchematicIteratorOriginal.java | 169 ++++++++++++++++++ .../storage/SchematicStorage.java | 2 +- .../utils/worldedit/objects/PastedMine.java | 2 +- 7 files changed, 344 insertions(+), 75 deletions(-) create mode 100644 src/main/java/me/untouchedodin0/privatemines/iterator/BlockVisitor.java create mode 100644 src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIteratorOriginal.java diff --git a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java index fe7d33bd..e45095a0 100644 --- a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java +++ b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java @@ -36,6 +36,8 @@ import me.untouchedodin0.privatemines.config.*; import me.untouchedodin0.privatemines.factory.MineFactory; import me.untouchedodin0.privatemines.iterator.SchematicIterator; +import me.untouchedodin0.privatemines.iterator.SchematicIteratorOriginal; +import me.untouchedodin0.privatemines.iterator.SchematicIteratorOriginal.MineBlocks; import me.untouchedodin0.privatemines.listener.MineResetListener; import me.untouchedodin0.privatemines.listener.PlayerJoinListener; import me.untouchedodin0.privatemines.listener.sell.AutoSellListener; @@ -75,6 +77,7 @@ public class PrivateMines extends JavaPlugin { private final Path schematicsDirectory = getDataFolder().toPath().resolve("schematics"); private final Path addonsDirectory = getDataFolder().toPath().resolve("addons"); private SchematicStorage schematicStorage; + private SchematicIteratorOriginal schematicIteratorOriginal; private SchematicIterator schematicIterator; private MineFactory mineFactory; private MineStorage mineStorage; @@ -182,7 +185,29 @@ public void onEnable() { return; } - SchematicIterator.MineBlocks mineBlocks = schematicIterator.findRelativePoints(schematicFile); + // Measure the time taken to execute the findRelativePoints method on the original + long startTime = System.nanoTime(); + SchematicIteratorOriginal.MineBlocks mineBlocks = schematicIteratorOriginal.findRelativePoints(schematicFile); + long endTime = System.nanoTime(); + + // Calculate the duration in milliseconds + long durationInMillis = (endTime - startTime) / 1_000_000; + + // Output the duration + System.out.println("Original Iterator Execution time: " + durationInMillis + " ms"); + + + // Measure the time taken to execute the findRelativePoints method on the rewrite + long startRewrite = System.nanoTime(); + MineBlocks mineBlocksRewrite = schematicIterator.findRelativePoints(schematicFile); + long endRewrite = System.nanoTime(); + + + // Calculate the duration in milliseconds + long durationInMillisRewrite = (endRewrite - startRewrite) / 1_000_000; + + // Output the duration + System.out.println("Rewrite Iterator Execution time: " + durationInMillisRewrite + " ms"); schematicStorage.addSchematic(schematicFile, mineBlocks); }); @@ -277,6 +302,7 @@ public void onDisable() { public void setupSchematicUtils() { this.schematicStorage = new SchematicStorage(); + this.schematicIteratorOriginal = new SchematicIteratorOriginal(getSchematicStorage()); this.schematicIterator = new SchematicIterator(getSchematicStorage()); } diff --git a/src/main/java/me/untouchedodin0/privatemines/factory/PregenFactory.java b/src/main/java/me/untouchedodin0/privatemines/factory/PregenFactory.java index 15ac9435..4870c402 100644 --- a/src/main/java/me/untouchedodin0/privatemines/factory/PregenFactory.java +++ b/src/main/java/me/untouchedodin0/privatemines/factory/PregenFactory.java @@ -24,7 +24,7 @@ import me.untouchedodin0.kotlin.mine.storage.PregenStorage; import me.untouchedodin0.kotlin.mine.type.MineType; import me.untouchedodin0.privatemines.PrivateMines; -import me.untouchedodin0.privatemines.iterator.SchematicIterator; +import me.untouchedodin0.privatemines.iterator.SchematicIteratorOriginal; import me.untouchedodin0.privatemines.mine.MineTypeManager; import me.untouchedodin0.privatemines.storage.SchematicStorage; import me.untouchedodin0.privatemines.storage.sql.SQLUtils; @@ -68,7 +68,7 @@ public void run() { BlockVector3 vector = BlockVector3.at(location.getBlockX(), location.getBlockY(), location.getBlockZ()); SchematicStorage storage = privateMines.getSchematicStorage(); - SchematicIterator.MineBlocks mineBlocks = storage.getMineBlocksMap().get(schematicFile); + SchematicIteratorOriginal.MineBlocks mineBlocks = storage.getMineBlocksMap().get(schematicFile); if (clipboardFormat != null) { try (ClipboardReader clipboardReader = clipboardFormat.getReader( new FileInputStream(schematicFile))) { diff --git a/src/main/java/me/untouchedodin0/privatemines/iterator/BlockVisitor.java b/src/main/java/me/untouchedodin0/privatemines/iterator/BlockVisitor.java new file mode 100644 index 00000000..084c6633 --- /dev/null +++ b/src/main/java/me/untouchedodin0/privatemines/iterator/BlockVisitor.java @@ -0,0 +1,69 @@ +package me.untouchedodin0.privatemines.iterator; + +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.block.BlockType; + +public class BlockVisitor { + + private final Clipboard clipboard; + private final BlockType cornerType; + private final BlockType spawnType; + private final BlockType npcType; + private final BlockType quarryType; + + private BlockVector3 corner1; + private BlockVector3 corner2; + private BlockVector3 spawn; + private BlockVector3 npc; + private BlockVector3 quarry; + + public BlockVisitor(Clipboard clipboard, BlockType cornerType, BlockType spawnType, + BlockType npcType, BlockType quarryType) { + this.clipboard = clipboard; + this.cornerType = cornerType; + this.spawnType = spawnType; + this.npcType = npcType; + this.quarryType = quarryType; + } + + public void visit(BlockVector3 blockVector3) { + BlockType blockType = clipboard.getBlock(blockVector3).getBlockType(); + int x = blockVector3.getX(); + int y = blockVector3.getY(); + int z = blockVector3.getZ(); + + if (blockType.equals(cornerType)) { + if (corner1 == null) { + this.corner1 = BlockVector3.at(x, y, z); + } else if (corner2 == null) { + if (x == corner1.getX()) return; + this.corner2 = BlockVector3.at(x, y, z); + } + } else if (blockType.equals(spawnType)) { + if (spawn == null) { + this.spawn = BlockVector3.at(x, y, z); + } + } else if (blockType.equals(npcType)) { + if (npc == null) { + npc = BlockVector3.at(x, y, z); + } + } else if (blockType.equals(quarryType)) { + if (quarry == null) { + quarry = BlockVector3.at(x, y, z); + } + } + } + + public BlockVector3 getSpawn() { + return spawn; + } + + public BlockVector3 getCorner1() { + return corner1; + } + + public BlockVector3 getCorner2() { + return corner2; + } +} diff --git a/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIterator.java b/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIterator.java index cde9bddd..4d638986 100644 --- a/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIterator.java +++ b/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIterator.java @@ -1,24 +1,3 @@ -/** - * MIT License - *

- * Copyright (c) 2021 - 2023 Kyle Hicks - *

- * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - *

- * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. - *

- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - package me.untouchedodin0.privatemines.iterator; import com.sk89q.worldedit.extent.clipboard.Clipboard; @@ -26,13 +5,23 @@ import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats; import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.regions.RegionOperationException; import com.sk89q.worldedit.world.block.BlockType; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.util.concurrent.CompletionService; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.logging.Level; import me.untouchedodin0.privatemines.PrivateMines; import me.untouchedodin0.privatemines.config.Config; +import me.untouchedodin0.privatemines.iterator.SchematicIteratorOriginal.MineBlocks; import me.untouchedodin0.privatemines.storage.SchematicStorage; import me.untouchedodin0.privatemines.utils.Utils; import org.bukkit.Material; @@ -72,32 +61,68 @@ public MineBlocks findRelativePoints(File file) { BlockType npcType = BlockType.REGISTRY.get(npcMaterial.getKey().getKey()); BlockType quarryType = BlockType.REGISTRY.get(quarryMaterial.getKey().getKey()); - clipboard.getRegion().forEach(blockVector3 -> { - BlockType blockType = clipboard.getBlock(blockVector3).getBlockType(); - int x = blockVector3.getX(); - int y = blockVector3.getY(); - int z = blockVector3.getZ(); + Region region = clipboard.getRegion(); + + // Split the region into four sub-regions + int midX = (region.getMinimumPoint().getX() + region.getMaximumPoint().getX()) / 2; + int midY = (region.getMinimumPoint().getY() + region.getMaximumPoint().getY()) / 2; + int midZ = (region.getMinimumPoint().getZ() + region.getMaximumPoint().getZ()) / 2; + + Region[] subRegions = new Region[]{ + new CuboidRegion(region.getMinimumPoint(), BlockVector3.at(midX, midY, midZ)), + new CuboidRegion(BlockVector3.at(midX + 1, region.getMinimumPoint().getY(), + region.getMinimumPoint().getZ()), region.getMaximumPoint()), new CuboidRegion( + BlockVector3.at(region.getMinimumPoint().getX(), midY + 1, + region.getMinimumPoint().getZ()), region.getMaximumPoint()), new CuboidRegion( + BlockVector3.at(region.getMinimumPoint().getX(), region.getMinimumPoint().getY(), + midZ + 1), region.getMaximumPoint())}; + + ExecutorService executorService = Executors.newFixedThreadPool(4); + CompletionService completionService = new ExecutorCompletionService<>( + executorService); + + for (Region subRegion : subRegions) { + privateMines.getLogger().info("Visiting Sub-Region: " + subRegion); + completionService.submit(() -> { + BlockVisitor blockVisitor = new BlockVisitor(clipboard, cornerType, spawnType, npcType, + quarryType); + subRegion.forEach(blockVisitor::visit); + return blockVisitor; + }); + } + + executorService.shutdown(); + + int cornerCount = 0; + for (int i = 0; i < subRegions.length; i++) { + Future future = completionService.take(); + BlockVisitor blockVisitor = future.get(); + + privateMines.getLogger().info("Sub-Region results: " + blockVisitor); + + if (spawn == null && blockVisitor.getSpawn() != null) { + spawn = blockVisitor.getSpawn(); + } - if (blockType.equals(cornerType)) { + if (blockVisitor.getCorner1() != null) { if (corner1 == null) { - corner1 = BlockVector3.at(x, y, z); + corner1 = blockVisitor.getCorner1(); + cornerCount++; } else if (corner2 == null) { - corner2 = BlockVector3.at(x, y, z); + corner2 = blockVisitor.getCorner1(); + cornerCount++; } - } else if (blockType.equals(spawnType)) { - if (spawn == null) { - spawn = BlockVector3.at(x, y, z); - } - } else if (blockType.equals(npcType)) { - if (npc == null) { - npc = BlockVector3.at(x, y, z); - } - } else if (blockType.equals(quarryType)) { - if (quarry == null) { - quarry = BlockVector3.at(x, y, z); + } + if (blockVisitor.getCorner2() != null) { + if (cornerCount == 1 && !blockVisitor.getCorner2().equals(corner1)) { + corner2 = blockVisitor.getCorner2(); + cornerCount++; + } else if (cornerCount == 0 && corner1 == null) { + corner1 = blockVisitor.getCorner2(); + cornerCount++; } } - }); + } if (spawn == null) { privateMines.getLogger().info( @@ -126,44 +151,24 @@ public MineBlocks findRelativePoints(File file) { mineBlocks.corners[0] = BlockVector3.at(corner1.getX(), corner1.getY(), corner1.getZ()); mineBlocks.corners[1] = BlockVector3.at(corner2.getX(), corner2.getY(), corner2.getZ()); + privateMines.getLogger().info("spawn = " + spawn); + privateMines.getLogger().info("npc = " + npc); + privateMines.getLogger().info("quarry = " + quarry); + privateMines.getLogger().info("corner1 = " + corner1); + privateMines.getLogger().info("corner2 = " + corner2); + + // Reset the variables spawn = null; npc = null; quarry = null; corner1 = null; corner2 = null; - } catch (IOException e) { + } catch (IOException | InterruptedException | ExecutionException | + RegionOperationException e) { privateMines.getLogger().log(Level.WARNING, "An error occurred whilst iterating the mine blocks in the schematic", e); } } return mineBlocks; } - - public static class MineBlocks { - - public BlockVector3 spawnLocation; - public BlockVector3 npcLocation; - public BlockVector3 quarryLocation; - public BlockVector3[] corners; - - public BlockVector3 getSpawnLocation() { - return spawnLocation; - } - - public BlockVector3 getNpcLocation() { - return npcLocation; - } - - public BlockVector3 getQuarryLocation() { - return quarryLocation; - } - - public BlockVector3 getCorner1() { - return corners[0]; - } - - public BlockVector3 getCorner2() { - return corners[1]; - } - } } diff --git a/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIteratorOriginal.java b/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIteratorOriginal.java new file mode 100644 index 00000000..d1420bb5 --- /dev/null +++ b/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIteratorOriginal.java @@ -0,0 +1,169 @@ +/** + * MIT License + *

+ * Copyright (c) 2021 - 2023 Kyle Hicks + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package me.untouchedodin0.privatemines.iterator; + +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat; +import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats; +import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.block.BlockType; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.logging.Level; +import me.untouchedodin0.privatemines.PrivateMines; +import me.untouchedodin0.privatemines.config.Config; +import me.untouchedodin0.privatemines.storage.SchematicStorage; +import me.untouchedodin0.privatemines.utils.Utils; +import org.bukkit.Material; + +public class SchematicIteratorOriginal { + + SchematicStorage schematicStorage; + BlockVector3 spawn; + BlockVector3 npc; + BlockVector3 quarry; + BlockVector3 corner1; + BlockVector3 corner2; + + public SchematicIteratorOriginal(SchematicStorage storage) { + this.schematicStorage = storage; + } + + public MineBlocks findRelativePoints(File file) { + PrivateMines privateMines = PrivateMines.getPrivateMines(); + MineBlocks mineBlocks = new MineBlocks(); + mineBlocks.corners = new BlockVector3[2]; + + Clipboard clipboard; + ClipboardFormat clipboardFormat = ClipboardFormats.findByFile(file); + + if (clipboardFormat != null) { + try (ClipboardReader clipboardReader = clipboardFormat.getReader(new FileInputStream(file))) { + clipboard = clipboardReader.read(); + + Material cornerMaterial = Config.mineCorner; + Material spawnMaterial = Config.spawnPoint; + Material npcMaterial = Config.sellNpc; + Material quarryMaterial = Config.quarryMaterial; + + BlockType cornerType = BlockType.REGISTRY.get(cornerMaterial.getKey().getKey()); + BlockType spawnType = BlockType.REGISTRY.get(spawnMaterial.getKey().getKey()); + BlockType npcType = BlockType.REGISTRY.get(npcMaterial.getKey().getKey()); + BlockType quarryType = BlockType.REGISTRY.get(quarryMaterial.getKey().getKey()); + + clipboard.getRegion().forEach(blockVector3 -> { + BlockType blockType = clipboard.getBlock(blockVector3).getBlockType(); + int x = blockVector3.getX(); + int y = blockVector3.getY(); + int z = blockVector3.getZ(); + + if (blockType.equals(cornerType)) { + if (corner1 == null) { + corner1 = BlockVector3.at(x, y, z); + } else if (corner2 == null) { + corner2 = BlockVector3.at(x, y, z); + } + } else if (blockType.equals(spawnType)) { + if (spawn == null) { + spawn = BlockVector3.at(x, y, z); + } + } else if (blockType.equals(npcType)) { + if (npc == null) { + npc = BlockVector3.at(x, y, z); + } + } else if (blockType.equals(quarryType)) { + if (quarry == null) { + quarry = BlockVector3.at(x, y, z); + } + } + }); + + if (spawn == null) { + privateMines.getLogger().info( + String.format("Failed to find a spawn block in the mine\nhave you placed a %s block?", + Utils.format(spawnMaterial))); + return null; + } else if (corner1 == null) { + privateMines.getLogger().info(String.format( + "Failed to find corner 1 in the mine\nhave you placed 2 %s blocks in the mining region?", + Utils.format(cornerMaterial))); + return null; + } else if (corner2 == null) { + privateMines.getLogger().info(String.format( + "Failed to find corner 2 in the mine\nhave you placed 2 %s blocks in the mining region?", + Utils.format(cornerMaterial))); + return null; + } + + mineBlocks.spawnLocation = BlockVector3.at(spawn.getX(), spawn.getY(), spawn.getZ()); + if (npc != null) { + mineBlocks.npcLocation = BlockVector3.at(npc.getX(), npc.getY(), npc.getZ()); + } + if (quarry != null) { + mineBlocks.quarryLocation = BlockVector3.at(quarry.getX(), quarry.getY(), quarry.getZ()); + } + mineBlocks.corners[0] = BlockVector3.at(corner1.getX(), corner1.getY(), corner1.getZ()); + mineBlocks.corners[1] = BlockVector3.at(corner2.getX(), corner2.getY(), corner2.getZ()); + + spawn = null; + npc = null; + quarry = null; + corner1 = null; + corner2 = null; + } catch (IOException e) { + privateMines.getLogger().log(Level.WARNING, + "An error occurred whilst iterating the mine blocks in the schematic", e); + } + } + return mineBlocks; + } + + public static class MineBlocks { + + public BlockVector3 spawnLocation; + public BlockVector3 npcLocation; + public BlockVector3 quarryLocation; + public BlockVector3[] corners; + + public BlockVector3 getSpawnLocation() { + return spawnLocation; + } + + public BlockVector3 getNpcLocation() { + return npcLocation; + } + + public BlockVector3 getQuarryLocation() { + return quarryLocation; + } + + public BlockVector3 getCorner1() { + return corners[0]; + } + + public BlockVector3 getCorner2() { + return corners[1]; + } + } +} diff --git a/src/main/java/me/untouchedodin0/privatemines/storage/SchematicStorage.java b/src/main/java/me/untouchedodin0/privatemines/storage/SchematicStorage.java index 5690cdcc..84fe7bfa 100644 --- a/src/main/java/me/untouchedodin0/privatemines/storage/SchematicStorage.java +++ b/src/main/java/me/untouchedodin0/privatemines/storage/SchematicStorage.java @@ -24,7 +24,7 @@ import java.io.File; import java.util.HashMap; import java.util.Map; -import me.untouchedodin0.privatemines.iterator.SchematicIterator.MineBlocks; +import me.untouchedodin0.privatemines.iterator.SchematicIteratorOriginal.MineBlocks; public class SchematicStorage { diff --git a/src/main/java/me/untouchedodin0/privatemines/utils/worldedit/objects/PastedMine.java b/src/main/java/me/untouchedodin0/privatemines/utils/worldedit/objects/PastedMine.java index 1b049b8c..fb65ccb1 100644 --- a/src/main/java/me/untouchedodin0/privatemines/utils/worldedit/objects/PastedMine.java +++ b/src/main/java/me/untouchedodin0/privatemines/utils/worldedit/objects/PastedMine.java @@ -3,7 +3,7 @@ import com.sk89q.worldedit.math.BlockVector3; import java.io.File; import me.untouchedodin0.privatemines.PrivateMines; -import me.untouchedodin0.privatemines.iterator.SchematicIterator.MineBlocks; +import me.untouchedodin0.privatemines.iterator.SchematicIteratorOriginal.MineBlocks; import me.untouchedodin0.privatemines.storage.SchematicStorage; import org.bukkit.Location; From 21d78f28e6694f0b59a24d60b1264ed7a3aadee7 Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Thu, 27 Jun 2024 18:18:37 +0100 Subject: [PATCH 20/43] Added the config option schematicThreads to config.yml and Config Refactored SchematicIterator to make it more tidy. --- .../privatemines/config/Config.java | 2 ++ .../iterator/SchematicIterator.java | 32 ++++++++++--------- src/main/resources/config.yml | 2 +- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/main/java/me/untouchedodin0/privatemines/config/Config.java b/src/main/java/me/untouchedodin0/privatemines/config/Config.java index 2a833bac..546a0ec0 100644 --- a/src/main/java/me/untouchedodin0/privatemines/config/Config.java +++ b/src/main/java/me/untouchedodin0/privatemines/config/Config.java @@ -63,4 +63,6 @@ public class Config { public static boolean enableResetCooldown = true; @Comment("Specifies the cooldown for the /privatemines reset command") public static int resetCooldown = 15; + @Comment("How many threads should be used for the schematic iterator") + public static int schematicThreads = 4; } diff --git a/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIterator.java b/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIterator.java index 4d638986..1e746bbb 100644 --- a/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIterator.java +++ b/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIterator.java @@ -62,22 +62,9 @@ public MineBlocks findRelativePoints(File file) { BlockType quarryType = BlockType.REGISTRY.get(quarryMaterial.getKey().getKey()); Region region = clipboard.getRegion(); + Region[] subRegions = getSubRegions(region); - // Split the region into four sub-regions - int midX = (region.getMinimumPoint().getX() + region.getMaximumPoint().getX()) / 2; - int midY = (region.getMinimumPoint().getY() + region.getMaximumPoint().getY()) / 2; - int midZ = (region.getMinimumPoint().getZ() + region.getMaximumPoint().getZ()) / 2; - - Region[] subRegions = new Region[]{ - new CuboidRegion(region.getMinimumPoint(), BlockVector3.at(midX, midY, midZ)), - new CuboidRegion(BlockVector3.at(midX + 1, region.getMinimumPoint().getY(), - region.getMinimumPoint().getZ()), region.getMaximumPoint()), new CuboidRegion( - BlockVector3.at(region.getMinimumPoint().getX(), midY + 1, - region.getMinimumPoint().getZ()), region.getMaximumPoint()), new CuboidRegion( - BlockVector3.at(region.getMinimumPoint().getX(), region.getMinimumPoint().getY(), - midZ + 1), region.getMaximumPoint())}; - - ExecutorService executorService = Executors.newFixedThreadPool(4); + ExecutorService executorService = Executors.newFixedThreadPool(Config.schematicThreads); CompletionService completionService = new ExecutorCompletionService<>( executorService); @@ -171,4 +158,19 @@ public MineBlocks findRelativePoints(File file) { } return mineBlocks; } + + private Region[] getSubRegions(Region region) { + int midX = (region.getMinimumPoint().getX() + region.getMaximumPoint().getX()) / 2; + int midY = (region.getMinimumPoint().getY() + region.getMaximumPoint().getY()) / 2; + int midZ = (region.getMinimumPoint().getZ() + region.getMaximumPoint().getZ()) / 2; + + return new Region[]{ + new CuboidRegion(region.getMinimumPoint(), BlockVector3.at(midX, midY, midZ)), + new CuboidRegion(BlockVector3.at(midX + 1, region.getMinimumPoint().getY(), + region.getMinimumPoint().getZ()), region.getMaximumPoint()), new CuboidRegion( + BlockVector3.at(region.getMinimumPoint().getX(), midY + 1, region.getMinimumPoint().getZ()), + region.getMaximumPoint()), new CuboidRegion( + BlockVector3.at(region.getMinimumPoint().getX(), region.getMinimumPoint().getY(), midZ + 1), + region.getMaximumPoint())}; + } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 331b3d10..e3d0882d 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -21,7 +21,7 @@ enableTax: true sendTaxMessages: true defaultClosed: true resetCooldown: 15 -formatResetMessage: true +schematicThreads: 4 mineTypes: Test: From f507098360593db8ed55ae9b44aa5f9aa7197822 Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Thu, 27 Jun 2024 18:38:16 +0100 Subject: [PATCH 21/43] Implemented the config options. --- .../privatemines/PrivateMines.java | 66 +++++++++++-------- .../privatemines/config/Config.java | 2 + src/main/resources/config.yml | 1 + 3 files changed, 42 insertions(+), 27 deletions(-) diff --git a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java index e45095a0..60027796 100644 --- a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java +++ b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java @@ -24,16 +24,29 @@ import co.aikar.commands.BukkitCommandManager; import java.io.File; import java.io.IOException; -import java.nio.file.*; -import java.time.*; -import java.util.*; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.time.Duration; +import java.time.Instant; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; import java.util.stream.Stream; import me.untouchedodin0.kotlin.mine.data.MineData; import me.untouchedodin0.kotlin.mine.storage.MineStorage; import me.untouchedodin0.kotlin.mine.storage.PregenStorage; import me.untouchedodin0.kotlin.mine.type.MineType; -import me.untouchedodin0.privatemines.commands.*; -import me.untouchedodin0.privatemines.config.*; +import me.untouchedodin0.privatemines.commands.AddonsCommand; +import me.untouchedodin0.privatemines.commands.PrivateMinesCommand; +import me.untouchedodin0.privatemines.commands.PublicMinesCommand; +import me.untouchedodin0.privatemines.config.Config; +import me.untouchedodin0.privatemines.config.MenuConfig; +import me.untouchedodin0.privatemines.config.MessagesConfig; +import me.untouchedodin0.privatemines.config.MineConfig; import me.untouchedodin0.privatemines.factory.MineFactory; import me.untouchedodin0.privatemines.iterator.SchematicIterator; import me.untouchedodin0.privatemines.iterator.SchematicIteratorOriginal; @@ -48,7 +61,8 @@ import me.untouchedodin0.privatemines.storage.StorageType; import me.untouchedodin0.privatemines.storage.sql.SQLUtils; import me.untouchedodin0.privatemines.storage.sql.SQLite; -import me.untouchedodin0.privatemines.utils.*; +import me.untouchedodin0.privatemines.utils.QueueUtils; +import me.untouchedodin0.privatemines.utils.UpdateChecker; import me.untouchedodin0.privatemines.utils.addon.AddonManager; import me.untouchedodin0.privatemines.utils.placeholderapi.PrivateMinesExpansion; import me.untouchedodin0.privatemines.utils.world.MineWorldManager; @@ -119,7 +133,6 @@ public void onEnable() { registerListeners(); setupSchematicUtils(); - if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) { boolean registered = new PrivateMinesExpansion().register(); if (registered) { @@ -185,30 +198,29 @@ public void onEnable() { return; } - // Measure the time taken to execute the findRelativePoints method on the original - long startTime = System.nanoTime(); - SchematicIteratorOriginal.MineBlocks mineBlocks = schematicIteratorOriginal.findRelativePoints(schematicFile); - long endTime = System.nanoTime(); - - // Calculate the duration in milliseconds - long durationInMillis = (endTime - startTime) / 1_000_000; - - // Output the duration - System.out.println("Original Iterator Execution time: " + durationInMillis + " ms"); + if (Config.useNewSchematicLoader) { + long startRewrite = System.nanoTime(); + MineBlocks mineBlocksRewrite = schematicIterator.findRelativePoints(schematicFile); + long endRewrite = System.nanoTime(); + // Calculate the duration in milliseconds + long durationInMillisRewrite = (endRewrite - startRewrite) / 1_000_000; - // Measure the time taken to execute the findRelativePoints method on the rewrite - long startRewrite = System.nanoTime(); - MineBlocks mineBlocksRewrite = schematicIterator.findRelativePoints(schematicFile); - long endRewrite = System.nanoTime(); + // Output the duration + System.out.println("Rewrite Iterator Execution time: " + durationInMillisRewrite + " ms"); + schematicStorage.addSchematic(schematicFile, mineBlocksRewrite); + } else { + long startOriginal = System.nanoTime(); + MineBlocks mineBlocksOriginal = schematicIteratorOriginal.findRelativePoints(schematicFile); + long endOriginal = System.nanoTime(); + // Calculate the duration in milliseconds + long durationInMillisOriginal = (endOriginal - startOriginal) / 1_000_000; - // Calculate the duration in milliseconds - long durationInMillisRewrite = (endRewrite - startRewrite) / 1_000_000; - - // Output the duration - System.out.println("Rewrite Iterator Execution time: " + durationInMillisRewrite + " ms"); - schematicStorage.addSchematic(schematicFile, mineBlocks); + // Output the duration + System.out.println("Original Iterator Execution time: " + durationInMillisOriginal + " ms"); + schematicStorage.addSchematic(schematicFile, mineBlocksOriginal); + } }); BukkitCommandManager bukkitCommandManager = new BukkitCommandManager(this); diff --git a/src/main/java/me/untouchedodin0/privatemines/config/Config.java b/src/main/java/me/untouchedodin0/privatemines/config/Config.java index 546a0ec0..41d198ab 100644 --- a/src/main/java/me/untouchedodin0/privatemines/config/Config.java +++ b/src/main/java/me/untouchedodin0/privatemines/config/Config.java @@ -63,6 +63,8 @@ public class Config { public static boolean enableResetCooldown = true; @Comment("Specifies the cooldown for the /privatemines reset command") public static int resetCooldown = 15; + @Comment("Use the new schematic loader?") + public static boolean useNewSchematicLoader = true; @Comment("How many threads should be used for the schematic iterator") public static int schematicThreads = 4; } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index e3d0882d..f6d8e633 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -21,6 +21,7 @@ enableTax: true sendTaxMessages: true defaultClosed: true resetCooldown: 15 +useNewSchematicLoader: true schematicThreads: 4 mineTypes: From a2dab3da90d15151baf0c9e0a7a9619564031da2 Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Thu, 27 Jun 2024 18:51:14 +0100 Subject: [PATCH 22/43] Using the config option correctly to assign how many threads to use. --- .../privatemines/iterator/SchematicIterator.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIterator.java b/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIterator.java index 1e746bbb..c8357b17 100644 --- a/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIterator.java +++ b/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIterator.java @@ -18,6 +18,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.ThreadPoolExecutor; import java.util.logging.Level; import me.untouchedodin0.privatemines.PrivateMines; import me.untouchedodin0.privatemines.config.Config; @@ -78,6 +79,10 @@ public MineBlocks findRelativePoints(File file) { }); } + if (executorService instanceof ThreadPoolExecutor threadPoolExecutor) { + privateMines.getLogger().info("I'm using " + threadPoolExecutor.getActiveCount() + " Threads to iterate the schematic"); + } + executorService.shutdown(); int cornerCount = 0; From b87744683ca95fdfc3540313fa3974244028cd48 Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Fri, 28 Jun 2024 21:12:12 +0100 Subject: [PATCH 23/43] Tidy up SchematicIterator more and make a RegionUtils class. --- .../privatemines/PrivateMines.java | 1 - .../iterator/SchematicIterator.java | 19 ++------------- .../utils/regions/RegionUtils.java | 24 +++++++++++++++++++ 3 files changed, 26 insertions(+), 18 deletions(-) create mode 100644 src/main/java/me/untouchedodin0/privatemines/utils/regions/RegionUtils.java diff --git a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java index 60027796..450b2812 100644 --- a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java +++ b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java @@ -311,7 +311,6 @@ public void onDisable() { sqlHelper.close(); } - public void setupSchematicUtils() { this.schematicStorage = new SchematicStorage(); this.schematicIteratorOriginal = new SchematicIteratorOriginal(getSchematicStorage()); diff --git a/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIterator.java b/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIterator.java index c8357b17..1e5e0d4d 100644 --- a/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIterator.java +++ b/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIterator.java @@ -5,7 +5,6 @@ import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats; import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; import com.sk89q.worldedit.math.BlockVector3; -import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.regions.RegionOperationException; import com.sk89q.worldedit.world.block.BlockType; @@ -25,6 +24,7 @@ import me.untouchedodin0.privatemines.iterator.SchematicIteratorOriginal.MineBlocks; import me.untouchedodin0.privatemines.storage.SchematicStorage; import me.untouchedodin0.privatemines.utils.Utils; +import me.untouchedodin0.privatemines.utils.regions.RegionUtils; import org.bukkit.Material; public class SchematicIterator { @@ -63,7 +63,7 @@ public MineBlocks findRelativePoints(File file) { BlockType quarryType = BlockType.REGISTRY.get(quarryMaterial.getKey().getKey()); Region region = clipboard.getRegion(); - Region[] subRegions = getSubRegions(region); + Region[] subRegions = RegionUtils.getSubRegions(region); ExecutorService executorService = Executors.newFixedThreadPool(Config.schematicThreads); CompletionService completionService = new ExecutorCompletionService<>( @@ -163,19 +163,4 @@ public MineBlocks findRelativePoints(File file) { } return mineBlocks; } - - private Region[] getSubRegions(Region region) { - int midX = (region.getMinimumPoint().getX() + region.getMaximumPoint().getX()) / 2; - int midY = (region.getMinimumPoint().getY() + region.getMaximumPoint().getY()) / 2; - int midZ = (region.getMinimumPoint().getZ() + region.getMaximumPoint().getZ()) / 2; - - return new Region[]{ - new CuboidRegion(region.getMinimumPoint(), BlockVector3.at(midX, midY, midZ)), - new CuboidRegion(BlockVector3.at(midX + 1, region.getMinimumPoint().getY(), - region.getMinimumPoint().getZ()), region.getMaximumPoint()), new CuboidRegion( - BlockVector3.at(region.getMinimumPoint().getX(), midY + 1, region.getMinimumPoint().getZ()), - region.getMaximumPoint()), new CuboidRegion( - BlockVector3.at(region.getMinimumPoint().getX(), region.getMinimumPoint().getY(), midZ + 1), - region.getMaximumPoint())}; - } } diff --git a/src/main/java/me/untouchedodin0/privatemines/utils/regions/RegionUtils.java b/src/main/java/me/untouchedodin0/privatemines/utils/regions/RegionUtils.java new file mode 100644 index 00000000..7d7e357f --- /dev/null +++ b/src/main/java/me/untouchedodin0/privatemines/utils/regions/RegionUtils.java @@ -0,0 +1,24 @@ +package me.untouchedodin0.privatemines.utils.regions; + +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.Region; + +public class RegionUtils { + + public static com.sk89q.worldedit.regions.Region[] getSubRegions( + com.sk89q.worldedit.regions.Region region) { + int midX = (region.getMinimumPoint().getX() + region.getMaximumPoint().getX()) / 2; + int midY = (region.getMinimumPoint().getY() + region.getMaximumPoint().getY()) / 2; + int midZ = (region.getMinimumPoint().getZ() + region.getMaximumPoint().getZ()) / 2; + + return new Region[]{ + new CuboidRegion(region.getMinimumPoint(), BlockVector3.at(midX, midY, midZ)), + new CuboidRegion(BlockVector3.at(midX + 1, region.getMinimumPoint().getY(), + region.getMinimumPoint().getZ()), region.getMaximumPoint()), new CuboidRegion( + BlockVector3.at(region.getMinimumPoint().getX(), midY + 1, region.getMinimumPoint().getZ()), + region.getMaximumPoint()), new CuboidRegion( + BlockVector3.at(region.getMinimumPoint().getX(), region.getMinimumPoint().getY(), midZ + 1), + region.getMaximumPoint())}; + } +} From 4ac1151720a318fda4c10fcd68591d4b4cc0414a Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Mon, 1 Jul 2024 22:15:27 +0100 Subject: [PATCH 24/43] Tidy up --- .../privatemines/iterator/SchematicIterator.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIterator.java b/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIterator.java index 1e5e0d4d..0ab5fa3c 100644 --- a/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIterator.java +++ b/src/main/java/me/untouchedodin0/privatemines/iterator/SchematicIterator.java @@ -17,7 +17,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; -import java.util.concurrent.ThreadPoolExecutor; import java.util.logging.Level; import me.untouchedodin0.privatemines.PrivateMines; import me.untouchedodin0.privatemines.config.Config; @@ -70,7 +69,6 @@ public MineBlocks findRelativePoints(File file) { executorService); for (Region subRegion : subRegions) { - privateMines.getLogger().info("Visiting Sub-Region: " + subRegion); completionService.submit(() -> { BlockVisitor blockVisitor = new BlockVisitor(clipboard, cornerType, spawnType, npcType, quarryType); @@ -79,10 +77,6 @@ public MineBlocks findRelativePoints(File file) { }); } - if (executorService instanceof ThreadPoolExecutor threadPoolExecutor) { - privateMines.getLogger().info("I'm using " + threadPoolExecutor.getActiveCount() + " Threads to iterate the schematic"); - } - executorService.shutdown(); int cornerCount = 0; @@ -90,8 +84,6 @@ public MineBlocks findRelativePoints(File file) { Future future = completionService.take(); BlockVisitor blockVisitor = future.get(); - privateMines.getLogger().info("Sub-Region results: " + blockVisitor); - if (spawn == null && blockVisitor.getSpawn() != null) { spawn = blockVisitor.getSpawn(); } From eb13fe24b33a9e455a36241ffbc7be24e99bfc04 Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Mon, 1 Jul 2024 22:20:36 +0100 Subject: [PATCH 25/43] Remove debug stuff and go out of dev build for 5.7 release. --- build.gradle | 2 +- .../privatemines/PrivateMines.java | 16 ---------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/build.gradle b/build.gradle index 8a6df827..b687882c 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ group 'me.untouchedodin0.privatemines' version = pluginVersion -def devBuild = true +def devBuild = false if (devBuild) { version = version + "-DEV" diff --git a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java index 450b2812..e82e9aa5 100644 --- a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java +++ b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java @@ -199,26 +199,10 @@ public void onEnable() { } if (Config.useNewSchematicLoader) { - long startRewrite = System.nanoTime(); MineBlocks mineBlocksRewrite = schematicIterator.findRelativePoints(schematicFile); - long endRewrite = System.nanoTime(); - - // Calculate the duration in milliseconds - long durationInMillisRewrite = (endRewrite - startRewrite) / 1_000_000; - - // Output the duration - System.out.println("Rewrite Iterator Execution time: " + durationInMillisRewrite + " ms"); schematicStorage.addSchematic(schematicFile, mineBlocksRewrite); } else { - long startOriginal = System.nanoTime(); MineBlocks mineBlocksOriginal = schematicIteratorOriginal.findRelativePoints(schematicFile); - long endOriginal = System.nanoTime(); - - // Calculate the duration in milliseconds - long durationInMillisOriginal = (endOriginal - startOriginal) / 1_000_000; - - // Output the duration - System.out.println("Original Iterator Execution time: " + durationInMillisOriginal + " ms"); schematicStorage.addSchematic(schematicFile, mineBlocksOriginal); } }); From 06fd9531916f8020adb150e50fcf9dd4e12721f8 Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Tue, 2 Jul 2024 14:55:18 +0100 Subject: [PATCH 26/43] Update PlaceholderAPI from 2.11.3 -> 2.11.6 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b687882c..a1a28562 100644 --- a/build.gradle +++ b/build.gradle @@ -54,7 +54,7 @@ dependencies { compileOnly 'com.grinderwolf:slimeworldmanager-api:2.7.0-SNAPSHOT' compileOnly 'com.github.MilkBowl:VaultAPI:1.7.1' compileOnly 'com.mojang:authlib:1.11' - compileOnly 'me.clip:placeholderapi:2.11.3' + compileOnly 'me.clip:placeholderapi:2.11.6' compileOnly 'com.github.oraxen:oraxen:1.157.2' compileOnly 'com.github.LoneDev6:API-ItemsAdder:3.6.1' compileOnly 'com.google.code.gson:gson:2.10' From 49b872a465f2718c3df7dada8ac97ec2628fd5c6 Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Tue, 16 Jul 2024 22:21:30 +0100 Subject: [PATCH 27/43] Working on a bit of player shop stuff. --- .../privatemines/PrivateMines.java | 15 ++++++++ .../commands/PlayerShopCommand.java | 21 ++++++++++ .../commands/UsePlayerShopCommand.java | 18 +++++++++ .../privatemines/factory/MineFactory.java | 5 +++ .../listener/sell/PlayerShopListener.java | 38 +++++++++++++++++++ .../privatemines/playershops/Shop.java | 13 +++---- .../privatemines/playershops/ShopBuilder.java | 5 ++- .../privatemines/storage/sql/SQLUtils.java | 22 +++++++++++ .../kotlin/mine/data/MineData.kt | 2 +- 9 files changed, 129 insertions(+), 10 deletions(-) create mode 100644 src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java create mode 100644 src/main/java/me/untouchedodin0/privatemines/commands/UsePlayerShopCommand.java create mode 100644 src/main/java/me/untouchedodin0/privatemines/listener/sell/PlayerShopListener.java diff --git a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java index e82e9aa5..24822379 100644 --- a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java +++ b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java @@ -41,8 +41,10 @@ import me.untouchedodin0.kotlin.mine.storage.PregenStorage; import me.untouchedodin0.kotlin.mine.type.MineType; import me.untouchedodin0.privatemines.commands.AddonsCommand; +import me.untouchedodin0.privatemines.commands.PlayerShopCommand; import me.untouchedodin0.privatemines.commands.PrivateMinesCommand; import me.untouchedodin0.privatemines.commands.PublicMinesCommand; +import me.untouchedodin0.privatemines.commands.UsePlayerShopCommand; import me.untouchedodin0.privatemines.config.Config; import me.untouchedodin0.privatemines.config.MenuConfig; import me.untouchedodin0.privatemines.config.MessagesConfig; @@ -54,6 +56,7 @@ import me.untouchedodin0.privatemines.listener.MineResetListener; import me.untouchedodin0.privatemines.listener.PlayerJoinListener; import me.untouchedodin0.privatemines.listener.sell.AutoSellListener; +import me.untouchedodin0.privatemines.listener.sell.PlayerShopListener; import me.untouchedodin0.privatemines.listener.sell.UPCSellListener; import me.untouchedodin0.privatemines.mine.Mine; import me.untouchedodin0.privatemines.mine.MineTypeManager; @@ -212,6 +215,8 @@ public void onEnable() { bukkitCommandManager.registerCommand(new PrivateMinesCommand()); bukkitCommandManager.registerCommand(new PublicMinesCommand()); bukkitCommandManager.registerCommand(new AddonsCommand()); + bukkitCommandManager.registerCommand(new PlayerShopCommand()); + bukkitCommandManager.registerCommand(new UsePlayerShopCommand()); File dataFolder = new File(privateMines.getDataFolder(), "privatemines.db"); if (!dataFolder.exists()) { @@ -255,6 +260,15 @@ min_full VARCHAR(20), max_full VARCHAR(20) ); """); + sqlHelper.executeUpdate(""" + CREATE TABLE IF NOT EXISTS shops ( + shop_owner VARCHAR(36) NOT NULL, + item VARCHAR(50) NOT NULL, + price DOUBLE NOT NULL, + PRIMARY KEY (shop_owner, item) + ); + """); + sqlHelper.setAutoCommit(true); Task.asyncDelayed(this::loadSQLMines); @@ -448,6 +462,7 @@ public void registerSellListener() { private void registerListeners() { getServer().getPluginManager().registerEvents(new MineResetListener(), this); + getServer().getPluginManager().registerEvents(new PlayerShopListener(), this); } public SQLHelper getSqlHelper() { diff --git a/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java b/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java new file mode 100644 index 00000000..7d0f4282 --- /dev/null +++ b/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java @@ -0,0 +1,21 @@ +package me.untouchedodin0.privatemines.commands; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Default; +import co.aikar.commands.annotation.Subcommand; +import me.untouchedodin0.privatemines.storage.sql.SQLUtils; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +@CommandAlias("playershop|playershops|pshop") +public class PlayerShopCommand extends BaseCommand { + + @Default + @CommandPermission("privatemines.playershop") + public void playerShop(Player player) { + + SQLUtils.updatePrice(player.getUniqueId(), Material.COBBLESTONE, 1.0); + } +} diff --git a/src/main/java/me/untouchedodin0/privatemines/commands/UsePlayerShopCommand.java b/src/main/java/me/untouchedodin0/privatemines/commands/UsePlayerShopCommand.java new file mode 100644 index 00000000..0905ed80 --- /dev/null +++ b/src/main/java/me/untouchedodin0/privatemines/commands/UsePlayerShopCommand.java @@ -0,0 +1,18 @@ +package me.untouchedodin0.privatemines.commands; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Default; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; + +@CommandAlias("useplayershop|usepshop") +public class UsePlayerShopCommand extends BaseCommand { + + @Default + @CommandPermission("privatemines.playershop.use") + public void usePlayerShop(Player player, OfflinePlayer target) { + + } +} \ No newline at end of file diff --git a/src/main/java/me/untouchedodin0/privatemines/factory/MineFactory.java b/src/main/java/me/untouchedodin0/privatemines/factory/MineFactory.java index 4e8e27fd..90a2ce51 100644 --- a/src/main/java/me/untouchedodin0/privatemines/factory/MineFactory.java +++ b/src/main/java/me/untouchedodin0/privatemines/factory/MineFactory.java @@ -45,6 +45,8 @@ import me.untouchedodin0.privatemines.config.Config; import me.untouchedodin0.privatemines.events.PrivateMineCreationEvent; import me.untouchedodin0.privatemines.mine.Mine; +import me.untouchedodin0.privatemines.playershops.Shop; +import me.untouchedodin0.privatemines.playershops.ShopBuilder; import me.untouchedodin0.privatemines.storage.sql.SQLUtils; import me.untouchedodin0.privatemines.utils.worldedit.PasteHelper; import me.untouchedodin0.privatemines.utils.worldedit.objects.PastedMine; @@ -109,6 +111,9 @@ public void create(Player player, Location location, MineType mineType) { Mine mine = new Mine(privateMines); MineData mineData = new MineData(uuid, corner2, corner1, minimum, maximum, location, spawn, mineType, Config.defaultClosed, 5.0); + Shop shop = new ShopBuilder().setOwner(uuid).setPrices(Map.of()).setRegion(miningRegion) + .build(); + mineData.setShop(shop); mine.setMineData(mineData); SQLUtils.insert(mine); diff --git a/src/main/java/me/untouchedodin0/privatemines/listener/sell/PlayerShopListener.java b/src/main/java/me/untouchedodin0/privatemines/listener/sell/PlayerShopListener.java new file mode 100644 index 00000000..84047c8c --- /dev/null +++ b/src/main/java/me/untouchedodin0/privatemines/listener/sell/PlayerShopListener.java @@ -0,0 +1,38 @@ +package me.untouchedodin0.privatemines.listener.sell; + +import java.util.Objects; +import me.untouchedodin0.kotlin.mine.data.MineData; +import me.untouchedodin0.kotlin.mine.storage.MineStorage; +import me.untouchedodin0.privatemines.PrivateMines; +import me.untouchedodin0.privatemines.mine.Mine; +import me.untouchedodin0.privatemines.playershops.Shop; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; + +public class PlayerShopListener implements Listener { + + PrivateMines privateMines = PrivateMines.getPrivateMines(); + MineStorage mineStorage = privateMines.getMineStorage(); + + @EventHandler(priority = EventPriority.LOWEST) + public void onBlockBreak(BlockBreakEvent event) { + Player player = event.getPlayer(); + Location location = player.getLocation(); + Mine mine = mineStorage.getClosest(location); + if (mine != null) { + MineData mineData = mine.getMineData(); + Shop shop = mineData.getShop(); + + Bukkit.broadcastMessage("Block break listener lol."); + Bukkit.broadcastMessage("nearest mine " + mine); + Bukkit.broadcastMessage("mine data " + mineData); + Bukkit.broadcastMessage("shop " + shop); + Bukkit.broadcastMessage("shop prices " + Objects.requireNonNull(shop).getPrices()); + } + } +} diff --git a/src/main/java/me/untouchedodin0/privatemines/playershops/Shop.java b/src/main/java/me/untouchedodin0/privatemines/playershops/Shop.java index aebee8d3..c97fd345 100644 --- a/src/main/java/me/untouchedodin0/privatemines/playershops/Shop.java +++ b/src/main/java/me/untouchedodin0/privatemines/playershops/Shop.java @@ -24,16 +24,15 @@ package me.untouchedodin0.privatemines.playershops; -import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.entity.Player; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - public class Shop { //todo Create a shop @@ -44,7 +43,7 @@ public class Shop { UUID owner; Map prices = new HashMap<>(); - Region region; + ProtectedRegion region; public UUID getOwner() { return owner; @@ -68,7 +67,7 @@ public void setPrice(Material material, Double price) { } } - public void setRegion(Region region) { + public void setRegion(ProtectedRegion region) { this.region = region; } diff --git a/src/main/java/me/untouchedodin0/privatemines/playershops/ShopBuilder.java b/src/main/java/me/untouchedodin0/privatemines/playershops/ShopBuilder.java index d0f66db7..916a7c13 100644 --- a/src/main/java/me/untouchedodin0/privatemines/playershops/ShopBuilder.java +++ b/src/main/java/me/untouchedodin0/privatemines/playershops/ShopBuilder.java @@ -25,6 +25,7 @@ package me.untouchedodin0.privatemines.playershops; import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; import org.bukkit.Material; import java.util.Map; @@ -32,7 +33,7 @@ public class ShopBuilder { UUID owner; - Region region; + ProtectedRegion region; Map prices; public ShopBuilder setOwner(UUID owner) { @@ -40,7 +41,7 @@ public ShopBuilder setOwner(UUID owner) { return this; } - public ShopBuilder setRegion(Region region) { + public ShopBuilder setRegion(ProtectedRegion region) { this.region = region; return this; } diff --git a/src/main/java/me/untouchedodin0/privatemines/storage/sql/SQLUtils.java b/src/main/java/me/untouchedodin0/privatemines/storage/sql/SQLUtils.java index c0904beb..dbb07b17 100644 --- a/src/main/java/me/untouchedodin0/privatemines/storage/sql/SQLUtils.java +++ b/src/main/java/me/untouchedodin0/privatemines/storage/sql/SQLUtils.java @@ -1,5 +1,6 @@ package me.untouchedodin0.privatemines.storage.sql; +import com.google.common.util.concurrent.AtomicDouble; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; @@ -16,6 +17,7 @@ import me.untouchedodin0.privatemines.utils.world.MineWorldManager; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.Material; import redempt.redlib.misc.LocationUtils; import redempt.redlib.misc.Task; import redempt.redlib.sql.SQLHelper; @@ -26,6 +28,7 @@ public class SQLUtils { private static final PrivateMines privateMines = PrivateMines.getPrivateMines(); private static final SQLHelper sqlHelper = privateMines.getSqlHelper(); static PregenStorage pregenStorage = privateMines.getPregenStorage(); + static AtomicDouble atomicDouble = new AtomicDouble(); public static Location getCurrentLocation() { MineWorldManager mineWorldManager = PrivateMines.getPrivateMines().getMineWorldManager(); @@ -227,4 +230,23 @@ public static void getPregen() { // // }); } + + public static void updatePrice(UUID uuid, Material material, Double price) { + String shopOwnerUUID = uuid.toString(); + String itemName = material.name(); + Bukkit.broadcastMessage("" + atomicDouble.getAndAdd(1)); + + String insertQuery = String.format( + "INSERT INTO shops (shop_owner, item, price) " + + "VALUES ('%s', '%s', %f) " + + "ON CONFLICT(shop_owner, item) DO UPDATE SET price = excluded.price;", + shopOwnerUUID, itemName, price); + + Task.asyncDelayed(() -> sqlHelper.executeUpdate(insertQuery)); + +// Task.asyncDelayed(() -> sqlHelper.executeUpdate(dropQuery)); + Bukkit.broadcastMessage("shop owner uuid " + shopOwnerUUID); + Bukkit.broadcastMessage("item name " + itemName); + Bukkit.broadcastMessage("price " + price); + } } diff --git a/src/main/kotlin/me/untouchedodin0/kotlin/mine/data/MineData.kt b/src/main/kotlin/me/untouchedodin0/kotlin/mine/data/MineData.kt index c6b6c58f..5c6be9c4 100644 --- a/src/main/kotlin/me/untouchedodin0/kotlin/mine/data/MineData.kt +++ b/src/main/kotlin/me/untouchedodin0/kotlin/mine/data/MineData.kt @@ -39,7 +39,7 @@ data class MineData( var isOpen: Boolean = false, var tax: Double = 5.0, ) { - private var shop: Shop? = null + var shop: Shop? = null var bannedPlayers: MutableList = mutableListOf() var friends: MutableList = mutableListOf() From aa58972c2f6e5fab3bff746e21356beb998c57f6 Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Thu, 18 Jul 2024 22:14:00 +0100 Subject: [PATCH 28/43] Working on sql loading stuff and the gui --- .../privatemines/PrivateMines.java | 40 +++++++++++++++++-- .../commands/PlayerShopCommand.java | 34 +++++++++++++++- .../privatemines/factory/MineFactory.java | 2 + .../privatemines/storage/sql/SQLUtils.java | 10 +++++ 4 files changed, 82 insertions(+), 4 deletions(-) diff --git a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java index 24822379..489f0667 100644 --- a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java +++ b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java @@ -271,7 +271,8 @@ PRIMARY KEY (shop_owner, item) sqlHelper.setAutoCommit(true); - Task.asyncDelayed(this::loadSQLMines); + Task.asyncDelayed(this::loadMines); + Task.asyncDelayed(this::loadShops); Task.asyncDelayed(SQLUtils::loadPregens); Task.asyncDelayed(this::loadAddons); @@ -328,7 +329,7 @@ private boolean setupEconomy() { return true; } - public void loadSQLMines() { + public void loadMines() { SQLHelper sqlHelper = getSqlHelper(); Results results = sqlHelper.queryResults("SELECT * FROM privatemines;"); @@ -373,13 +374,46 @@ public void loadSQLMines() { MineData mineData = new MineData(uuid, minMining, maxMining, fullMin, fullMax, location, spawnLocation, type, open, tax); - mineData.setMaterials(materials); //- This breaks it for some reason + mineData.setMaterials(materials); mine.setMineData(mineData); mineStorage.addMine(uuid, mine); }); } + public void loadShops() { + /* + sqlHelper.executeUpdate(""" + CREATE TABLE IF NOT EXISTS shops ( + shop_owner VARCHAR(36) NOT NULL, + item VARCHAR(50) NOT NULL, + price DOUBLE NOT NULL, + PRIMARY KEY (shop_owner, item) + ); + """); + */ + SQLHelper sqlHelper = getSqlHelper(); + Results results = sqlHelper.queryResults("SELECT * FROM shops"); + + privateMines.getLogger().info("------"); + privateMines.getLogger().info("- load shops -"); + privateMines.getLogger().info("sql helper " + sqlHelper); + privateMines.getLogger().info("results " + results); + + results.forEach(result -> { + String shop_owner = result.getString(1); + String item = result.getString(2); + double price = result.get(3); + + + + privateMines.getLogger().info("result " + result); + privateMines.getLogger().info("shop_owner " + shop_owner); + privateMines.getLogger().info("item " + item); + privateMines.getLogger().info("price " + price); + }); + } + public void loadAddons() { final PathMatcher jarMatcher = FileSystems.getDefault() .getPathMatcher("glob:**/*.jar"); // Credits to Brister Mitten diff --git a/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java b/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java index 7d0f4282..c6b96c86 100644 --- a/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java +++ b/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java @@ -5,17 +5,49 @@ import co.aikar.commands.annotation.CommandPermission; import co.aikar.commands.annotation.Default; import co.aikar.commands.annotation.Subcommand; +import dev.triumphteam.gui.components.ScrollType; +import dev.triumphteam.gui.guis.Gui; +import dev.triumphteam.gui.guis.ScrollingGui; +import me.untouchedodin0.kotlin.mine.data.MineData; +import me.untouchedodin0.kotlin.mine.storage.MineStorage; +import me.untouchedodin0.kotlin.utils.AudienceUtils; +import me.untouchedodin0.privatemines.PrivateMines; +import me.untouchedodin0.privatemines.config.MessagesConfig; +import me.untouchedodin0.privatemines.mine.Mine; +import me.untouchedodin0.privatemines.playershops.Shop; import me.untouchedodin0.privatemines.storage.sql.SQLUtils; +import net.kyori.adventure.text.Component; import org.bukkit.Material; import org.bukkit.entity.Player; @CommandAlias("playershop|playershops|pshop") public class PlayerShopCommand extends BaseCommand { + PrivateMines privateMines = PrivateMines.getPrivateMines(); + MineStorage mineStorage = privateMines.getMineStorage(); + AudienceUtils audienceUtils = new AudienceUtils(); + @Default @CommandPermission("privatemines.playershop") public void playerShop(Player player) { + if (!mineStorage.hasMine(player)) { + audienceUtils.sendMessage(player, MessagesConfig.dontOwnMine); + } else { + Mine mine = mineStorage.get(player); + if (mine != null) { + MineData mineData = mine.getMineData(); + Shop shop = mineData.getShop(); + player.sendMessage("owner " + shop.getOwner()); + player.sendMessage("prices " + shop.getPrices()); + + ScrollingGui scrollingGui = Gui.scrolling() + .title(Component.text("Title")) + .rows(6) + .pageSize(45) + .create(); - SQLUtils.updatePrice(player.getUniqueId(), Material.COBBLESTONE, 1.0); + scrollingGui.open(player); + } + } } } diff --git a/src/main/java/me/untouchedodin0/privatemines/factory/MineFactory.java b/src/main/java/me/untouchedodin0/privatemines/factory/MineFactory.java index 90a2ce51..b9407140 100644 --- a/src/main/java/me/untouchedodin0/privatemines/factory/MineFactory.java +++ b/src/main/java/me/untouchedodin0/privatemines/factory/MineFactory.java @@ -117,6 +117,8 @@ public void create(Player player, Location location, MineType mineType) { mine.setMineData(mineData); SQLUtils.insert(mine); + SQLUtils.updatePrice(uuid, Material.COBBLESTONE, 1.0); + mineStorage.addMine(uuid, mine); mine.handleReset(); Task.syncDelayed(() -> { diff --git a/src/main/java/me/untouchedodin0/privatemines/storage/sql/SQLUtils.java b/src/main/java/me/untouchedodin0/privatemines/storage/sql/SQLUtils.java index dbb07b17..cec6007a 100644 --- a/src/main/java/me/untouchedodin0/privatemines/storage/sql/SQLUtils.java +++ b/src/main/java/me/untouchedodin0/privatemines/storage/sql/SQLUtils.java @@ -132,8 +132,10 @@ public static void delete(Mine mine) { MineData mineData = mine.getMineData(); String owner = mineData.getMineOwner().toString(); String dropQuery = String.format("DELETE FROM privatemines WHERE owner = '%s'", owner); + String dropShopQuery = String.format("DELETE FROM shops WHERE shop_owner = '%s'", owner); Task.asyncDelayed(() -> sqlHelper.executeUpdate(dropQuery)); + Task.asyncDelayed(() -> sqlHelper.executeUpdate(dropShopQuery)); } public static void insertPregen(PregenMine pregenMine) { @@ -249,4 +251,12 @@ public static void updatePrice(UUID uuid, Material material, Double price) { Bukkit.broadcastMessage("item name " + itemName); Bukkit.broadcastMessage("price " + price); } + + public static void setDefaultPrices(Mine mine) { + MineData mineData = mine.getMineData(); + + for (Material material : mineData.getMaterials().keySet()) { + updatePrice(mineData.getMineOwner(), material, 1.0); + } + } } From be654dea41df6e9cd713343cffbcd5c324bd3e9f Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Fri, 19 Jul 2024 22:32:13 +0100 Subject: [PATCH 29/43] It's adding to the store. --- .../privatemines/PrivateMines.java | 21 +- .../commands/PlayerShopCommand.java | 59 ++++- .../privatemines/factory/MineFactory.java | 6 +- .../privatemines/playershops/ShopUtils.java | 221 ++++++++++++++++++ .../privatemines/storage/sql/SQLUtils.java | 31 +-- 5 files changed, 296 insertions(+), 42 deletions(-) create mode 100644 src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java diff --git a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java index 489f0667..10cdac60 100644 --- a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java +++ b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java @@ -262,10 +262,13 @@ max_full VARCHAR(20) """); sqlHelper.executeUpdate(""" CREATE TABLE IF NOT EXISTS shops ( - shop_owner VARCHAR(36) NOT NULL, + owner VARCHAR(36) NOT NULL, + seller VARCHAR(36) NOT NULL, item VARCHAR(50) NOT NULL, + quantity INT NOT NULL, price DOUBLE NOT NULL, - PRIMARY KEY (shop_owner, item) + tax DOUBLE NOT NULL, + PRIMARY KEY (owner, item) ); """); @@ -385,10 +388,10 @@ public void loadShops() { /* sqlHelper.executeUpdate(""" CREATE TABLE IF NOT EXISTS shops ( - shop_owner VARCHAR(36) NOT NULL, + owner VARCHAR(36) NOT NULL, item VARCHAR(50) NOT NULL, price DOUBLE NOT NULL, - PRIMARY KEY (shop_owner, item) + PRIMARY KEY (owner, item) ); """); */ @@ -401,15 +404,15 @@ PRIMARY KEY (shop_owner, item) privateMines.getLogger().info("results " + results); results.forEach(result -> { - String shop_owner = result.getString(1); + String owner = result.getString(1); String item = result.getString(2); - double price = result.get(3); - - + int quantity = result.get(3); + double price = result.get(4); privateMines.getLogger().info("result " + result); - privateMines.getLogger().info("shop_owner " + shop_owner); + privateMines.getLogger().info("owner " + owner); privateMines.getLogger().info("item " + item); + privateMines.getLogger().info("quantity " + quantity); privateMines.getLogger().info("price " + price); }); } diff --git a/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java b/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java index c6b96c86..62b792a1 100644 --- a/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java +++ b/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java @@ -5,7 +5,7 @@ import co.aikar.commands.annotation.CommandPermission; import co.aikar.commands.annotation.Default; import co.aikar.commands.annotation.Subcommand; -import dev.triumphteam.gui.components.ScrollType; +import com.google.common.util.concurrent.AtomicDouble; import dev.triumphteam.gui.guis.Gui; import dev.triumphteam.gui.guis.ScrollingGui; import me.untouchedodin0.kotlin.mine.data.MineData; @@ -15,10 +15,11 @@ import me.untouchedodin0.privatemines.config.MessagesConfig; import me.untouchedodin0.privatemines.mine.Mine; import me.untouchedodin0.privatemines.playershops.Shop; -import me.untouchedodin0.privatemines.storage.sql.SQLUtils; +import me.untouchedodin0.privatemines.playershops.ShopUtils; import net.kyori.adventure.text.Component; import org.bukkit.Material; import org.bukkit.entity.Player; +import redempt.redlib.sql.SQLHelper; @CommandAlias("playershop|playershops|pshop") public class PlayerShopCommand extends BaseCommand { @@ -34,7 +35,15 @@ public void playerShop(Player player) { audienceUtils.sendMessage(player, MessagesConfig.dontOwnMine); } else { Mine mine = mineStorage.get(player); + AtomicDouble atomicDouble = new AtomicDouble(1); + if (mine != null) { + SQLHelper sqlHelper = privateMines.getSqlHelper(); + sqlHelper.execute("UPDATE shops SET price=? WHERE item=? AND owner=?;", + atomicDouble.getAndAdd(1.0), "COBBLESTONE", player.getUniqueId().toString()); + +// SQLUtils.updatePrice(player.getUniqueId(), Material.COBBLESTONE, 1.0); + MineData mineData = mine.getMineData(); Shop shop = mineData.getShop(); player.sendMessage("owner " + shop.getOwner()); @@ -50,4 +59,50 @@ public void playerShop(Player player) { } } } + + @Subcommand("debug") + public void pricesDebug(Player player, double price, int quantity) { + if (!mineStorage.hasMine(player)) { + audienceUtils.sendMessage(player, MessagesConfig.dontOwnMine); + } else { + Mine mine = mineStorage.get(player); + + if (mine != null) { + SQLHelper sqlHelper = privateMines.getSqlHelper(); + int quantityBefore = sqlHelper.querySingleResult( + "SELECT quantity FROM shops WHERE item=? AND owner=?;", "COBBLESTONE", + player.getUniqueId().toString()); + sqlHelper.execute("UPDATE shops SET price=? WHERE item=? AND owner=?;", + price, "COBBLESTONE", player.getUniqueId().toString()); + sqlHelper.execute("UPDATE shops SET quantity=? WHERE item=? AND owner=?;", + quantity, "COBBLESTONE", player.getUniqueId().toString()); + + String[] messages = { + "Quantity before: " + quantityBefore, + "Set the prices of", "cobblestone to " + price, + "Set the quantity of", " cobblestone to " + quantity}; + player.sendMessage(messages); + } + } + } + + @Subcommand("add") + public void pricesDebugAdd(Player player, int quantity, double price) { + if (!mineStorage.hasMine(player)) { + audienceUtils.sendMessage(player, MessagesConfig.dontOwnMine); + } else { + ShopUtils.addItem(player.getUniqueId(), Material.COBBLESTONE, quantity, price); + } + } + + @Subcommand("sethandprice") + public void setHandPrice(Player player, double price) { + if (!mineStorage.hasMine(player)) { + audienceUtils.sendMessage(player, MessagesConfig.dontOwnMine); + } else { + ShopUtils.updatePrice(player.getUniqueId(), + player.getInventory().getItemInMainHand().getType(), price); + } + } } + diff --git a/src/main/java/me/untouchedodin0/privatemines/factory/MineFactory.java b/src/main/java/me/untouchedodin0/privatemines/factory/MineFactory.java index b9407140..62b7a6a7 100644 --- a/src/main/java/me/untouchedodin0/privatemines/factory/MineFactory.java +++ b/src/main/java/me/untouchedodin0/privatemines/factory/MineFactory.java @@ -47,6 +47,7 @@ import me.untouchedodin0.privatemines.mine.Mine; import me.untouchedodin0.privatemines.playershops.Shop; import me.untouchedodin0.privatemines.playershops.ShopBuilder; +import me.untouchedodin0.privatemines.playershops.ShopUtils; import me.untouchedodin0.privatemines.storage.sql.SQLUtils; import me.untouchedodin0.privatemines.utils.worldedit.PasteHelper; import me.untouchedodin0.privatemines.utils.worldedit.objects.PastedMine; @@ -111,16 +112,17 @@ public void create(Player player, Location location, MineType mineType) { Mine mine = new Mine(privateMines); MineData mineData = new MineData(uuid, corner2, corner1, minimum, maximum, location, spawn, mineType, Config.defaultClosed, 5.0); - Shop shop = new ShopBuilder().setOwner(uuid).setPrices(Map.of()).setRegion(miningRegion) + Shop shop = new ShopBuilder().setOwner(uuid).setPrices(materials).setRegion(miningRegion) .build(); mineData.setShop(shop); mine.setMineData(mineData); SQLUtils.insert(mine); - SQLUtils.updatePrice(uuid, Material.COBBLESTONE, 1.0); mineStorage.addMine(uuid, mine); mine.handleReset(); +// ShopUtils.setDefaultPrices(mine); + Task.syncDelayed(() -> { spawn.getBlock().setType(Material.AIR); player.teleport(spawn); diff --git a/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java b/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java new file mode 100644 index 00000000..62ff17ac --- /dev/null +++ b/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java @@ -0,0 +1,221 @@ +package me.untouchedodin0.privatemines.playershops; + +import java.util.Objects; +import java.util.UUID; +import me.untouchedodin0.kotlin.mine.data.MineData; +import me.untouchedodin0.kotlin.mine.storage.MineStorage; +import me.untouchedodin0.privatemines.PrivateMines; +import me.untouchedodin0.privatemines.mine.Mine; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import redempt.redlib.misc.Task; +import redempt.redlib.sql.SQLHelper; +import redempt.redlib.sql.SQLHelper.Results; + +public class ShopUtils { + + private static final PrivateMines privateMines = PrivateMines.getPrivateMines(); + private static final SQLHelper sqlHelper = privateMines.getSqlHelper(); + + public static void updatePrice(UUID uuid, Material material, double price) { + String insertQuery = String.format( + "INSERT INTO shops (owner, seller, item, quantity, price) " + + "VALUES ('%s', '%s', '%s', %d, %f) " + + "ON CONFLICT(owner, item) DO UPDATE SET price = excluded.price;", + uuid, uuid, material.name(), 0, price); + +// String updateQuery = String.format( +// "UPDATE shops SET price = %f WHERE owner = '%s' AND item = '%s';", price, uuid, +// material.name()); + + // Log the query for debugging purposes +// System.out.println("Executing query: " + updateQuery); + + Bukkit.broadcastMessage( + String.format("updating item %s in %s's mine to %f", material.name(), uuid.toString(), + price)); + + Task.asyncDelayed(() -> sqlHelper.execute(insertQuery)); + } + + public static void setDefaultPrices(Mine mine) { + MineData mineData = mine.getMineData(); + String uuid = mineData.getMineOwner().toString(); + + // Log the query for debugging purposes + for (Material material : Objects.requireNonNull(mineData.getMineType().getMaterials()).keySet()) { + double price = Objects.requireNonNull(mineData.getMineType().getPrices()).get(material); + double finalPrice = mineData.getTax() / 100 * price; + + String insertQuery = String.format( + "INSERT INTO shops (owner, seller, item, quantity, price) " + + "VALUES ('%s', '%s', '%s', %d, %f) " + + "ON CONFLICT(owner, item) DO UPDATE SET price = excluded.price;", + uuid, "", material.name(), 0, finalPrice); + + // Log the query being executed + System.out.println("Executing query: " + insertQuery); + + Task.asyncDelayed(() -> { + try { + sqlHelper.executeUpdate(insertQuery); + } catch (Exception e) { + // Print stack trace or log the exception + e.printStackTrace(); + System.err.println("Error executing query: " + insertQuery); + } + }); + } + } + + + public static void addItem(UUID uuid, Material material, int quantity, double price) { + MineStorage mineStorage = privateMines.getMineStorage(); + Mine mine = mineStorage.get(uuid); + + if (mine != null) { + MineData mineData = mine.getMineData(); + String ownerUUID = uuid.toString(); + String materialName = material.name(); + double taxRate = mineData.getTax() / 100.0; + double finalPrice = price * (1 + taxRate); + + Task.asyncDelayed(() -> { + try { + // Check if the item already exists + String checkQuery = "SELECT quantity FROM shops WHERE owner = ? AND item = ?"; + Integer currentQuantity = sqlHelper.querySingleResult(checkQuery, ownerUUID, materialName); + + if (currentQuantity != null) { + // Item exists, update the quantity and price + int newQuantity = currentQuantity + quantity; + String updateQuery = "UPDATE shops SET quantity = ?, price = ? WHERE owner = ? AND item = ?"; + sqlHelper.executeUpdate(updateQuery, newQuantity, finalPrice, ownerUUID, materialName); + + Bukkit.broadcastMessage("Updated item: " + materialName + " with new quantity: " + newQuantity); + } else { + // Item doesn't exist, insert a new row + String insertQuery = "INSERT INTO shops (owner, seller, item, quantity, price, tax) VALUES (?, ?, ?, ?, ?, ?)"; + sqlHelper.execute(insertQuery, ownerUUID, ownerUUID, materialName, quantity, finalPrice, taxRate); + + Bukkit.broadcastMessage("Inserted new item: " + materialName + " with quantity: " + quantity); + } + } catch (Exception e) { + e.printStackTrace(); + Bukkit.broadcastMessage("Error handling item: " + materialName); + } + }); + } + } + + +// public static void addItem(UUID uuid, Material material, int quantity, double price) { +// MineStorage mineStorage = privateMines.getMineStorage(); +// Mine mine = mineStorage.get(uuid); +// +// if (mine != null) { +// MineData mineData = mine.getMineData(); +// String ownerUUID = uuid.toString(); +// String materialName = material.name(); +// +// // Query to check if the item with the specific owner and item already exists +// String checkQuery = "SELECT quantity FROM shops WHERE owner = ? AND item = ?;"; +// +// String insertQuery = String.format( +// "INSERT INTO shops (shop_owner, item, quantity, price) " +// + "VALUES ('%s', '%s', %d, %f) " +// + "ON CONFLICT(shop_owner, item) DO UPDATE SET price = excluded.price;", +// uuid, material.name(), 0, 1.0); +// +// // Log the check query for debugging purposes +// Bukkit.broadcastMessage("Executing check query: " + checkQuery); +//// Bukkit.broadcastMessage("quantity before " + quantityBefore); +//// Bukkit.broadcastMessage("quantity after " + quantityBefore + quantity); +// +// Task.asyncDelayed(() -> { +// sqlHelper.executeUpdate(insertQuery); +//// sqlHelper.execute("UPDATE shops SET price=? WHERE item=? AND owner=?;", +//// price, "COBBLESTONE", uuid.toString()); +//// sqlHelper.execute("UPDATE shops SET quantity=? WHERE item=? AND owner=?;", +//// quantity, "COBBLESTONE", uuid.toString()); +// +// int quantityBefore = sqlHelper.querySingleResult("SELECT quantity FROM shops WHERE owner = ? AND item = ?", ownerUUID, materialName); +// +// // Item doesn't exist, insert a new row +//// String insertQuery = "INSERT INTO shops (owner, seller, item, quantity, price, tax) VALUES (?, ?, ?, ?, ?, ?)"; +//// sqlHelper.execute(insertQuery, ownerUUID, ownerUUID, materialName, quantity, +//// (mineData.getTax() / 100) * price, 0.0); // Adjust tax value as needed +// +//// sqlHelper.executeUpdate("UPDATE shops SET quantity=? WHERE item=? AND price =? AND owner=?;", +//// quantityBefore + quantity, "COBBLESTONE", uuid.toString()); +// +//// try { +//// +//// } +//// Results results = sqlHelper.queryResults(checkQuery, ownerUUID, materialName); +//// +//// if (results.next()) { +//// // Item exists, update the quantity and price +//// int currentQuantity = results.get(4); +//// int newQuantity = currentQuantity + quantity; +//// +//// String updateQuery = "UPDATE shops SET quantity = ?, price = ? WHERE owner = ? AND item = ?"; +//// sqlHelper.execute(updateQuery, newQuantity, (mineData.getTax() / 100) * price, +//// ownerUUID, materialName); +//// +//// Bukkit.broadcastMessage( +//// "Updated item: " + materialName + " with new quantity: " + newQuantity); +//// } else { +//// // Item doesn't exist, insert a new row +//// String insertQuery = "INSERT INTO shops (owner, seller, item, quantity, price, tax) VALUES (?, ?, ?, ?, ?, ?)"; +//// sqlHelper.execute(insertQuery, ownerUUID, ownerUUID, materialName, quantity, +//// (mineData.getTax() / 100) * price, 0.0); // Adjust tax value as needed +//// +//// Bukkit.broadcastMessage( +//// "Inserted new item: " + materialName + " with quantity: " + quantity); +//// } +//// +//// // Clean up the resources +//// results.close(); +//// } catch (Exception e) { +//// throw new RuntimeException(e); +//// } +// }); +// } +// } + +// public static void addItem(UUID uuid, Material material, int quantity, double price) { +// MineStorage mineStorage = privateMines.getMineStorage(); +// +// Mine mine = mineStorage.get(uuid); +// +// if (mine != null) { +// // Query to check if the item with the specific price already exists for the shop owner +// String checkQuery = "SELECT quantity FROM shops WHERE owner = ? AND item = ? AND price = ?;"; +// +// // Get the material name +// String materialName = material.name(); +// +// // Log the check query for debugging purposes +// System.out.println("Executing check query: " + checkQuery); +// +// Bukkit.broadcastMessage("materialName " + materialName); +// +// Task.asyncDelayed(() -> { +// int quantityBefore = sqlHelper.querySingleResult( +// "SELECT quantity FROM shops WHERE item=? AND owner=?;", "COBBLESTONE", +// uuid.toString()); +// sqlHelper.execute("UPDATE shops SET price=? WHERE item=? AND owner=?;", price, +// "COBBLESTONE", uuid.toString()); +// sqlHelper.execute("UPDATE shops SET quantity=? WHERE item=? AND owner=?;", +// quantityBefore + quantity, "COBBLESTONE", uuid.toString()); +// +// int quantityAdded = quantityBefore + quantity; +// +// Bukkit.broadcastMessage("addItem quantity before " + quantityBefore); +// Bukkit.broadcastMessage("addItem quantity added " + quantityAdded); +// }); +// } +// } +} + diff --git a/src/main/java/me/untouchedodin0/privatemines/storage/sql/SQLUtils.java b/src/main/java/me/untouchedodin0/privatemines/storage/sql/SQLUtils.java index cec6007a..4ff09c97 100644 --- a/src/main/java/me/untouchedodin0/privatemines/storage/sql/SQLUtils.java +++ b/src/main/java/me/untouchedodin0/privatemines/storage/sql/SQLUtils.java @@ -28,7 +28,6 @@ public class SQLUtils { private static final PrivateMines privateMines = PrivateMines.getPrivateMines(); private static final SQLHelper sqlHelper = privateMines.getSqlHelper(); static PregenStorage pregenStorage = privateMines.getPregenStorage(); - static AtomicDouble atomicDouble = new AtomicDouble(); public static Location getCurrentLocation() { MineWorldManager mineWorldManager = PrivateMines.getPrivateMines().getMineWorldManager(); @@ -132,7 +131,7 @@ public static void delete(Mine mine) { MineData mineData = mine.getMineData(); String owner = mineData.getMineOwner().toString(); String dropQuery = String.format("DELETE FROM privatemines WHERE owner = '%s'", owner); - String dropShopQuery = String.format("DELETE FROM shops WHERE shop_owner = '%s'", owner); + String dropShopQuery = String.format("DELETE FROM shops WHERE owner = '%s'", owner); Task.asyncDelayed(() -> sqlHelper.executeUpdate(dropQuery)); Task.asyncDelayed(() -> sqlHelper.executeUpdate(dropShopQuery)); @@ -232,31 +231,5 @@ public static void getPregen() { // // }); } - - public static void updatePrice(UUID uuid, Material material, Double price) { - String shopOwnerUUID = uuid.toString(); - String itemName = material.name(); - Bukkit.broadcastMessage("" + atomicDouble.getAndAdd(1)); - - String insertQuery = String.format( - "INSERT INTO shops (shop_owner, item, price) " - + "VALUES ('%s', '%s', %f) " - + "ON CONFLICT(shop_owner, item) DO UPDATE SET price = excluded.price;", - shopOwnerUUID, itemName, price); - - Task.asyncDelayed(() -> sqlHelper.executeUpdate(insertQuery)); - -// Task.asyncDelayed(() -> sqlHelper.executeUpdate(dropQuery)); - Bukkit.broadcastMessage("shop owner uuid " + shopOwnerUUID); - Bukkit.broadcastMessage("item name " + itemName); - Bukkit.broadcastMessage("price " + price); - } - - public static void setDefaultPrices(Mine mine) { - MineData mineData = mine.getMineData(); - - for (Material material : mineData.getMaterials().keySet()) { - updatePrice(mineData.getMineOwner(), material, 1.0); - } - } } + From 4894c1f84f1c13c3be5e2059bcc5ded1396887c4 Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Sun, 21 Jul 2024 20:18:39 +0100 Subject: [PATCH 30/43] Spigot block gradle fix. --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index a1a28562..9a2bd396 100644 --- a/build.gradle +++ b/build.gradle @@ -102,6 +102,7 @@ bukkit { } tasks.register('copyTo_DevServer', Copy) { + doNotTrackState("spigot-workaround") from shadowJar into "E:/Coding/Spigot Test Servers/1.19.2 Privates Mines (Paper)/plugins" } From 3c18ea2ba2b293cba15450a17bef3c14b24b96ff Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Sun, 21 Jul 2024 21:46:21 +0100 Subject: [PATCH 31/43] Player shop is working with adding to the sql and /pshop --- .../commands/PlayerShopCommand.java | 290 ++++++++++++++++-- .../commands/PrivateMinesCommand.java | 1 + .../privatemines/factory/MineFactory.java | 2 +- .../privatemines/playershops/ShopUtils.java | 42 ++- 4 files changed, 298 insertions(+), 37 deletions(-) diff --git a/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java b/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java index 62b792a1..4a5b9973 100644 --- a/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java +++ b/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java @@ -6,8 +6,13 @@ import co.aikar.commands.annotation.Default; import co.aikar.commands.annotation.Subcommand; import com.google.common.util.concurrent.AtomicDouble; +import dev.triumphteam.gui.builder.item.ItemBuilder; import dev.triumphteam.gui.guis.Gui; -import dev.triumphteam.gui.guis.ScrollingGui; +import dev.triumphteam.gui.guis.GuiItem; +import dev.triumphteam.gui.guis.PaginatedGui; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; import me.untouchedodin0.kotlin.mine.data.MineData; import me.untouchedodin0.kotlin.mine.storage.MineStorage; import me.untouchedodin0.kotlin.utils.AudienceUtils; @@ -17,9 +22,12 @@ import me.untouchedodin0.privatemines.playershops.Shop; import me.untouchedodin0.privatemines.playershops.ShopUtils; import net.kyori.adventure.text.Component; +import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.entity.Player; +import redempt.redlib.misc.Task; import redempt.redlib.sql.SQLHelper; +import redempt.redlib.sql.SQLHelper.Results; @CommandAlias("playershop|playershops|pshop") public class PlayerShopCommand extends BaseCommand { @@ -30,36 +38,276 @@ public class PlayerShopCommand extends BaseCommand { @Default @CommandPermission("privatemines.playershop") - public void playerShop(Player player) { + public synchronized void playerShop(Player player) { + Map shopItems = new HashMap<>(); + if (!mineStorage.hasMine(player)) { audienceUtils.sendMessage(player, MessagesConfig.dontOwnMine); - } else { - Mine mine = mineStorage.get(player); - AtomicDouble atomicDouble = new AtomicDouble(1); + return; + } - if (mine != null) { - SQLHelper sqlHelper = privateMines.getSqlHelper(); - sqlHelper.execute("UPDATE shops SET price=? WHERE item=? AND owner=?;", - atomicDouble.getAndAdd(1.0), "COBBLESTONE", player.getUniqueId().toString()); + Mine mine = mineStorage.get(player); + if (mine == null) { + audienceUtils.sendMessage(player, MessagesConfig.dontOwnMine); + return; + } + + MineData mineData = mine.getMineData(); + Shop shop = mineData.getShop(); -// SQLUtils.updatePrice(player.getUniqueId(), Material.COBBLESTONE, 1.0); + // Initialize the GUI + PaginatedGui paginatedGui = Gui + .paginated() + .title(Component.text("test")) + .rows(5) + .pageSize(36) + .create(); - MineData mineData = mine.getMineData(); - Shop shop = mineData.getShop(); - player.sendMessage("owner " + shop.getOwner()); - player.sendMessage("prices " + shop.getPrices()); + paginatedGui.setDefaultClickAction(event -> event.setCancelled(true)); - ScrollingGui scrollingGui = Gui.scrolling() - .title(Component.text("Title")) - .rows(6) - .pageSize(45) - .create(); + // Previous item + paginatedGui.setItem(5, 3, ItemBuilder.from(Material.PAPER).setName("Previous") + .asGuiItem(event -> paginatedGui.previous())); + // Next item + paginatedGui.setItem(5, 7, ItemBuilder.from(Material.PAPER).setName("Next") + .asGuiItem(event -> paginatedGui.next())); - scrollingGui.open(player); + // Asynchronous task to fetch shop data + Task.asyncDelayed(() -> { + SQLHelper sqlHelper = privateMines.getSqlHelper(); + String ownerUUID = player.getUniqueId().toString(); + Results results = sqlHelper.queryResults("SELECT * FROM shops WHERE owner=?;", ownerUUID); + + if (results == null) { + Bukkit.broadcastMessage("No results found."); + return; } - } + + results.forEach(result -> { + String owner = result.getString(1); + String seller = result.getString(2); + String item = result.getString(3); + int quantity = result.get(4); + + Material material = Material.getMaterial(item); + if (material != null) { + shopItems.put(material, quantity); + } else { + Bukkit.broadcastMessage("Invalid material: " + item); + } + }); + + // Use a synchronous task to update the shopItems map + Bukkit.getScheduler().runTask(PrivateMines.getPrivateMines(), () -> { +// for (Material material : Material.values()) { +// if (material.isBlock()) { +// GuiItem guiItem = ItemBuilder.from(material) +// .name(Component.text("test")) +// .lore(Component.text("Quantity: " + shopItems.get(material))) +// .asGuiItem(); +// paginatedGui.addItem(guiItem); +// } +// } + shopItems.forEach((material, quantity) -> { + GuiItem guiItem = ItemBuilder.from(material) + .name(Component.text("Item: " + material.name())) + .lore(Component.text("Quantity: " + quantity)) + .asGuiItem(); + paginatedGui.addItem(guiItem); + }); + + // Open the GUI for the player + paginatedGui.open(player); + }); + }); } + +// @Default +// @CommandPermission("privatemines.playershop") +// public synchronized void playerShop(Player player) { +// Map shopItems = new HashMap<>(); +// +// if (!mineStorage.hasMine(player)) { +// audienceUtils.sendMessage(player, MessagesConfig.dontOwnMine); +// } else { +// Mine mine = mineStorage.get(player); +// AtomicDouble atomicDouble = new AtomicDouble(1); +// +// if (mine != null) { +// SQLHelper sqlHelper = privateMines.getSqlHelper(); +// sqlHelper.execute("UPDATE shops SET price=? WHERE item=? AND owner=?;", +// atomicDouble.getAndAdd(1.0), "COBBLESTONE", player.getUniqueId().toString()); +// +// MineData mineData = mine.getMineData(); +// Shop shop = mineData.getShop(); +// +// player.sendMessage("owner " + shop.getOwner()); +// player.sendMessage("prices " + shop.getPrices()); +// +// PaginatedGui paginatedGui = Gui +// .paginated() +// .title(Component.text("test")) +// .rows(5) +// .pageSize(36) +// .create(); +// +// paginatedGui.setDefaultClickAction(event -> event.setCancelled(true)); +// +// // Previous item +// paginatedGui.setItem(5, 3, ItemBuilder.from(Material.PAPER).setName("Previous") +// .asGuiItem(event -> paginatedGui.previous())); +// // Next item +// paginatedGui.setItem(5, 7, ItemBuilder.from(Material.PAPER).setName("Next") +// .asGuiItem(event -> paginatedGui.next())); +// +// // Asynchronous task to fetch shop data +// Task task = Task.asyncDelayed(() -> { +// Results results = sqlHelper.queryResults("SELECT * FROM shops WHERE owner=?;", +// player.getUniqueId().toString()); +// Bukkit.broadcastMessage("results = " + results); +// +// results.forEach(result -> { +// String owner = result.getString(1); +// String seller = result.getString(2); +// String item = result.getString(3); +// int quantity = result.get(4); +// +// Bukkit.broadcastMessage("owner " + owner); +// Bukkit.broadcastMessage("seller " + seller); +// Bukkit.broadcastMessage("item " + item); +// Bukkit.broadcastMessage("quantity " + quantity); +// +// Material material = Material.getMaterial(item); +// if (material != null) { +// shopItems.put(material, quantity); +// +// +// // Use a synchronous task to update the shopItems map +// Bukkit.getScheduler().runTask(PrivateMines.getPrivateMines(), () -> { +// shopItems.forEach(((material1, qty) -> { +// GuiItem guiItem = ItemBuilder.from(material1) +// .name(Component.text("test")) +// .lore(Component.text(material1.name()) +// .append(Component.text(":").appendSpace().append(Component.text(qty)))) +// .asGuiItem(); +// paginatedGui.addItem(guiItem); +// +// Bukkit.broadcastMessage("gui item " + guiItem); +// })); +//// // Add items to the GUI once the shopItems map is updated +//// shopItems.forEach((mat, qty) -> { +//// Bukkit.broadcastMessage("mat? " + mat); +//// Bukkit.broadcastMessage("int? " + qty); +//// GuiItem guiItem = ItemBuilder.from(mat) +//// .name(Component.text(UUID.randomUUID().toString())) +//// .lore(Component.text(mat.name()).append(Component.text(":")).appendSpace() +//// .append(Component.text(qty))) +//// .asGuiItem(); +////// scrollingGui.addItem(guiItem); +//// Bukkit.broadcastMessage("gui item " + guiItem); +//// paginatedGui.addItem(guiItem); +//// }); +// +// // Open the GUI for the player +// }); +// } else { +// Bukkit.broadcastMessage("Invalid material: " + item); +// } +// }); +// }); +// +// paginatedGui.open(player); +// } +// } +// } + +// public void playerShop(Player player) { +// if (!mineStorage.hasMine(player)) { +// audienceUtils.sendMessage(player, MessagesConfig.dontOwnMine); +// } else { +// Mine mine = mineStorage.get(player); +// AtomicDouble atomicDouble = new AtomicDouble(1); +// Map shopItems = new HashMap<>(); +// +// if (mine != null) { +// SQLHelper sqlHelper = privateMines.getSqlHelper(); +// sqlHelper.execute("UPDATE shops SET price=? WHERE item=? AND owner=?;", +// atomicDouble.getAndAdd(1.0), "COBBLESTONE", player.getUniqueId().toString()); +// +//// SQLUtils.updatePrice(player.getUniqueId(), Material.COBBLESTONE, 1.0); +// +// MineData mineData = mine.getMineData(); +// Shop shop = mineData.getShop(); +// +// player.sendMessage("owner " + shop.getOwner()); +// player.sendMessage("prices " + shop.getPrices()); +// +// ScrollingGui scrollingGui = Gui.scrolling() +// .title(Component.text("Title")) +// .rows(6) +// .disableItemTake() +// .create(); +// +// Task.asyncDelayed(() -> { +// Results results = sqlHelper.queryResults("SELECT * FROM shops WHERE owner=?;", +// player.getUniqueId().toString()); +// Bukkit.broadcastMessage("results = " + results); +// +// results.forEach(result -> { +// String owner = result.getString(1); +// String seller = result.getString(2); +// String item = result.getString(3); +// int quantity = result.get(4); +// +// Bukkit.broadcastMessage("owner " + owner); +// Bukkit.broadcastMessage("seller " + seller); +// Bukkit.broadcastMessage("item " + item); +// Bukkit.broadcastMessage("quantity " + quantity); +// +// Task.syncDelayed(() -> { +// shopItems.put(Material.getMaterial(item), quantity); +// }); +// }); +// }); +// +// shopItems.forEach((material, integer) -> { +// Bukkit.broadcastMessage("mat? " + material); +// Bukkit.broadcastMessage("int? " + integer); +// }); +// +// +// for (Entry entry : shopItems.entrySet()) { +// Bukkit.broadcastMessage("entry " + entry); +// GuiItem guiItem = ItemBuilder.from( +// Objects.requireNonNullElse(entry.getKey(), Material.STONE)) +// .name(Component.text(UUID.randomUUID().toString())) +// .lore(Component.text(Material.STONE.name()).append(Component.text(":")).appendSpace() +// .append(Component.text(1))).asGuiItem(); +// scrollingGui.addItem(guiItem); +// +// Bukkit.broadcastMessage("gui item " + guiItem); +// Bukkit.broadcastMessage("scrolling gui " + scrollingGui); +// } +//// for (Material material : shop.getPrices().keySet()) { +//// GuiItem guiItem = ItemBuilder.from(Objects.requireNonNullElse(material, Material.STONE)) +//// .name(Component.text(UUID.randomUUID().toString())) +//// .asGuiItem(); +//// scrollingGui.addItem(guiItem); +//// } +// +// GuiItem guiItem = ItemBuilder.from(Material.STONE) +// .name(Component.text("Test")) +// .asGuiItem(); +// +//// scrollingGui.addItem(guiItem); +//// scrollingGui.addItem(guiItem); +// +// scrollingGui.open(player); +// } +// } +// } + @Subcommand("debug") public void pricesDebug(Player player, double price, int quantity) { if (!mineStorage.hasMine(player)) { diff --git a/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java b/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java index ac220a75..0f5b713e 100644 --- a/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java +++ b/src/main/java/me/untouchedodin0/privatemines/commands/PrivateMinesCommand.java @@ -269,6 +269,7 @@ public void forceReset(CommandSender sender, OfflinePlayer target) { } else { Mine mine = mineStorage.get(target.getUniqueId()); if (mine != null) { + mine.reset(); mine.handleReset(); } } diff --git a/src/main/java/me/untouchedodin0/privatemines/factory/MineFactory.java b/src/main/java/me/untouchedodin0/privatemines/factory/MineFactory.java index 62b7a6a7..08cec996 100644 --- a/src/main/java/me/untouchedodin0/privatemines/factory/MineFactory.java +++ b/src/main/java/me/untouchedodin0/privatemines/factory/MineFactory.java @@ -118,10 +118,10 @@ public void create(Player player, Location location, MineType mineType) { mine.setMineData(mineData); SQLUtils.insert(mine); + ShopUtils.setDefaultPrices(mine); mineStorage.addMine(uuid, mine); mine.handleReset(); -// ShopUtils.setDefaultPrices(mine); Task.syncDelayed(() -> { spawn.getBlock().setType(Material.AIR); diff --git a/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java b/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java index 62ff17ac..8a973275 100644 --- a/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java +++ b/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java @@ -6,8 +6,10 @@ import me.untouchedodin0.kotlin.mine.storage.MineStorage; import me.untouchedodin0.privatemines.PrivateMines; import me.untouchedodin0.privatemines.mine.Mine; +import net.royawesome.jlibnoise.module.combiner.Min; import org.bukkit.Bukkit; import org.bukkit.Material; +import org.bukkit.entity.Player; import redempt.redlib.misc.Task; import redempt.redlib.sql.SQLHelper; import redempt.redlib.sql.SQLHelper.Results; @@ -15,30 +17,38 @@ public class ShopUtils { private static final PrivateMines privateMines = PrivateMines.getPrivateMines(); - private static final SQLHelper sqlHelper = privateMines.getSqlHelper(); +// private SQLHelper sqlHelper = privateMines.getSqlHelper(); public static void updatePrice(UUID uuid, Material material, double price) { - String insertQuery = String.format( - "INSERT INTO shops (owner, seller, item, quantity, price) " + - "VALUES ('%s', '%s', '%s', %d, %f) " + - "ON CONFLICT(owner, item) DO UPDATE SET price = excluded.price;", - uuid, uuid, material.name(), 0, price); + SQLHelper sqlHelper = privateMines.getSqlHelper(); + MineStorage mineStorage = privateMines.getMineStorage(); + Mine mine = mineStorage.get(uuid); + if (mine != null) { + MineData mineData = mine.getMineData(); + + String insertQuery = String.format( + "INSERT INTO shops (owner, seller, item, quantity, price, tax) " + + "VALUES ('%s', '%s', '%s', %d, %f, %f) " + + "ON CONFLICT(owner, item) DO UPDATE SET price = excluded.price;", + uuid, uuid, material.name(), 0, price, mineData.getTax()); // String updateQuery = String.format( // "UPDATE shops SET price = %f WHERE owner = '%s' AND item = '%s';", price, uuid, // material.name()); - // Log the query for debugging purposes + // Log the query for debugging purposes // System.out.println("Executing query: " + updateQuery); - Bukkit.broadcastMessage( - String.format("updating item %s in %s's mine to %f", material.name(), uuid.toString(), - price)); + Bukkit.broadcastMessage( + String.format("updating item %s in %s's mine to %f", material.name(), uuid.toString(), + price)); - Task.asyncDelayed(() -> sqlHelper.execute(insertQuery)); + Task.asyncDelayed(() -> sqlHelper.executeUpdate(insertQuery)); + } } public static void setDefaultPrices(Mine mine) { + SQLHelper sqlHelper = privateMines.getSqlHelper(); MineData mineData = mine.getMineData(); String uuid = mineData.getMineOwner().toString(); @@ -46,12 +56,13 @@ public static void setDefaultPrices(Mine mine) { for (Material material : Objects.requireNonNull(mineData.getMineType().getMaterials()).keySet()) { double price = Objects.requireNonNull(mineData.getMineType().getPrices()).get(material); double finalPrice = mineData.getTax() / 100 * price; + double tax = mineData.getTax(); String insertQuery = String.format( - "INSERT INTO shops (owner, seller, item, quantity, price) " + - "VALUES ('%s', '%s', '%s', %d, %f) " + + "INSERT INTO shops (owner, seller, item, quantity, price, tax) " + + "VALUES ('%s', '%s', '%s', %d, %f, %f) " + "ON CONFLICT(owner, item) DO UPDATE SET price = excluded.price;", - uuid, "", material.name(), 0, finalPrice); + uuid, "", material.name(), 0, finalPrice, tax); // Log the query being executed System.out.println("Executing query: " + insertQuery); @@ -70,6 +81,7 @@ public static void setDefaultPrices(Mine mine) { public static void addItem(UUID uuid, Material material, int quantity, double price) { + SQLHelper sqlHelper = privateMines.getSqlHelper(); MineStorage mineStorage = privateMines.getMineStorage(); Mine mine = mineStorage.get(uuid); @@ -96,7 +108,7 @@ public static void addItem(UUID uuid, Material material, int quantity, double pr } else { // Item doesn't exist, insert a new row String insertQuery = "INSERT INTO shops (owner, seller, item, quantity, price, tax) VALUES (?, ?, ?, ?, ?, ?)"; - sqlHelper.execute(insertQuery, ownerUUID, ownerUUID, materialName, quantity, finalPrice, taxRate); + sqlHelper.executeUpdate(insertQuery, ownerUUID, ownerUUID, materialName, quantity, finalPrice, taxRate); Bukkit.broadcastMessage("Inserted new item: " + materialName + " with quantity: " + quantity); } From cde73c19f7420eecf1a7fa07416d32b261ea3479 Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Sun, 21 Jul 2024 22:15:20 +0100 Subject: [PATCH 32/43] Working on a /pshop menu for selling the items... Fixes the god forsaken error https://pastes.dev/Pneu9IbYbm --- .../privatemines/PrivateMines.java | 10 +++- .../commands/PlayerShopCommand.java | 56 +++++++++---------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java index 10cdac60..deb5e383 100644 --- a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java +++ b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java @@ -405,15 +405,19 @@ PRIMARY KEY (owner, item) results.forEach(result -> { String owner = result.getString(1); - String item = result.getString(2); - int quantity = result.get(3); - double price = result.get(4); + String seller = result.getString(2); + String item = result.getString(3); + int quantity = result.get(4); + double price = result.get(5); + double tax = result.get(6); privateMines.getLogger().info("result " + result); privateMines.getLogger().info("owner " + owner); + privateMines.getLogger().info("seller"); privateMines.getLogger().info("item " + item); privateMines.getLogger().info("quantity " + quantity); privateMines.getLogger().info("price " + price); + privateMines.getLogger().info("tax " + tax); }); } diff --git a/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java b/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java index 4a5b9973..e028b34d 100644 --- a/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java +++ b/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java @@ -5,14 +5,13 @@ import co.aikar.commands.annotation.CommandPermission; import co.aikar.commands.annotation.Default; import co.aikar.commands.annotation.Subcommand; -import com.google.common.util.concurrent.AtomicDouble; import dev.triumphteam.gui.builder.item.ItemBuilder; import dev.triumphteam.gui.guis.Gui; import dev.triumphteam.gui.guis.GuiItem; import dev.triumphteam.gui.guis.PaginatedGui; import java.util.HashMap; +import java.util.List; import java.util.Map; -import java.util.UUID; import me.untouchedodin0.kotlin.mine.data.MineData; import me.untouchedodin0.kotlin.mine.storage.MineStorage; import me.untouchedodin0.kotlin.utils.AudienceUtils; @@ -23,6 +22,7 @@ import me.untouchedodin0.privatemines.playershops.ShopUtils; import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; +import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.entity.Player; import redempt.redlib.misc.Task; @@ -56,11 +56,7 @@ public synchronized void playerShop(Player player) { Shop shop = mineData.getShop(); // Initialize the GUI - PaginatedGui paginatedGui = Gui - .paginated() - .title(Component.text("test")) - .rows(5) - .pageSize(36) + PaginatedGui paginatedGui = Gui.paginated().title(Component.text("test")).rows(5).pageSize(36) .create(); paginatedGui.setDefaultClickAction(event -> event.setCancelled(true)); @@ -68,9 +64,15 @@ public synchronized void playerShop(Player player) { // Previous item paginatedGui.setItem(5, 3, ItemBuilder.from(Material.PAPER).setName("Previous") .asGuiItem(event -> paginatedGui.previous())); + // Sell All Button + paginatedGui.setItem(5, 5, ItemBuilder.from(Material.CHEST).name(Component.text("Sell All")) + .lore(List.of(Component.text("Click to sell all your"), + Component.text("items in your shop!"))).asGuiItem(event -> { + player.sendMessage(ChatColor.GREEN + "Selling all items!"); + })); // Next item - paginatedGui.setItem(5, 7, ItemBuilder.from(Material.PAPER).setName("Next") - .asGuiItem(event -> paginatedGui.next())); + paginatedGui.setItem(5, 7, + ItemBuilder.from(Material.PAPER).setName("Next").asGuiItem(event -> paginatedGui.next())); // Asynchronous task to fetch shop data Task.asyncDelayed(() -> { @@ -99,30 +101,26 @@ public synchronized void playerShop(Player player) { // Use a synchronous task to update the shopItems map Bukkit.getScheduler().runTask(PrivateMines.getPrivateMines(), () -> { -// for (Material material : Material.values()) { -// if (material.isBlock()) { -// GuiItem guiItem = ItemBuilder.from(material) -// .name(Component.text("test")) -// .lore(Component.text("Quantity: " + shopItems.get(material))) -// .asGuiItem(); -// paginatedGui.addItem(guiItem); -// } -// } shopItems.forEach((material, quantity) -> { GuiItem guiItem = ItemBuilder.from(material) - .name(Component.text("Item: " + material.name())) - .lore(Component.text("Quantity: " + quantity)) - .asGuiItem(); + .name(Component.text("Item: " + material.name())).lore( + List.of(Component.text("Quantity: " + shopItems.get(material)), + Component.text(" "), Component.text("Click to sell all!"))).asGuiItem(); paginatedGui.addItem(guiItem); }); // Open the GUI for the player paginatedGui.open(player); }); + paginatedGui.setCloseGuiAction(event -> { + Bukkit.broadcastMessage("closed inventory " + paginatedGui); + Bukkit.broadcastMessage("using event " + event); + Bukkit.broadcastMessage("closing sql: " + sqlHelper); + sqlHelper.close(); + }); }); } - // @Default // @CommandPermission("privatemines.playershop") // public synchronized void playerShop(Player player) { @@ -320,15 +318,13 @@ public void pricesDebug(Player player, double price, int quantity) { int quantityBefore = sqlHelper.querySingleResult( "SELECT quantity FROM shops WHERE item=? AND owner=?;", "COBBLESTONE", player.getUniqueId().toString()); - sqlHelper.execute("UPDATE shops SET price=? WHERE item=? AND owner=?;", - price, "COBBLESTONE", player.getUniqueId().toString()); - sqlHelper.execute("UPDATE shops SET quantity=? WHERE item=? AND owner=?;", - quantity, "COBBLESTONE", player.getUniqueId().toString()); + sqlHelper.execute("UPDATE shops SET price=? WHERE item=? AND owner=?;", price, + "COBBLESTONE", player.getUniqueId().toString()); + sqlHelper.execute("UPDATE shops SET quantity=? WHERE item=? AND owner=?;", quantity, + "COBBLESTONE", player.getUniqueId().toString()); - String[] messages = { - "Quantity before: " + quantityBefore, - "Set the prices of", "cobblestone to " + price, - "Set the quantity of", " cobblestone to " + quantity}; + String[] messages = {"Quantity before: " + quantityBefore, "Set the prices of", + "cobblestone to " + price, "Set the quantity of", " cobblestone to " + quantity}; player.sendMessage(messages); } } From 0cc4ea80abbee9389e601be28a19a165a838f00f Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Wed, 24 Jul 2024 16:05:47 +0100 Subject: [PATCH 33/43] Player shop sell button clearing menu correctly. --- .../commands/PlayerShopCommand.java | 43 +++-- .../playershops/PlayerShopMenuUtils.java | 158 +++++++++++++++++ .../privatemines/playershops/ShopUtils.java | 167 ++++++------------ 3 files changed, 240 insertions(+), 128 deletions(-) create mode 100644 src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java diff --git a/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java b/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java index e028b34d..c7640181 100644 --- a/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java +++ b/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java @@ -18,6 +18,7 @@ import me.untouchedodin0.privatemines.PrivateMines; import me.untouchedodin0.privatemines.config.MessagesConfig; import me.untouchedodin0.privatemines.mine.Mine; +import me.untouchedodin0.privatemines.playershops.PlayerShopMenuUtils; import me.untouchedodin0.privatemines.playershops.Shop; import me.untouchedodin0.privatemines.playershops.ShopUtils; import net.kyori.adventure.text.Component; @@ -40,6 +41,7 @@ public class PlayerShopCommand extends BaseCommand { @CommandPermission("privatemines.playershop") public synchronized void playerShop(Player player) { Map shopItems = new HashMap<>(); + PlayerShopMenuUtils playerShopMenuUtils = new PlayerShopMenuUtils(); if (!mineStorage.hasMine(player)) { audienceUtils.sendMessage(player, MessagesConfig.dontOwnMine); @@ -55,8 +57,9 @@ public synchronized void playerShop(Player player) { MineData mineData = mine.getMineData(); Shop shop = mineData.getShop(); + playerShopMenuUtils.generateMenu(player); // Initialize the GUI - PaginatedGui paginatedGui = Gui.paginated().title(Component.text("test")).rows(5).pageSize(36) +/* PaginatedGui paginatedGui = Gui.paginated().title(Component.text("test")).rows(5).pageSize(36) .create(); paginatedGui.setDefaultClickAction(event -> event.setCancelled(true)); @@ -72,10 +75,11 @@ public synchronized void playerShop(Player player) { })); // Next item paginatedGui.setItem(5, 7, - ItemBuilder.from(Material.PAPER).setName("Next").asGuiItem(event -> paginatedGui.next())); + ItemBuilder.from(Material.PAPER).setName("Next").asGuiItem(event -> paginatedGui.next()));*/ // Asynchronous task to fetch shop data - Task.asyncDelayed(() -> { + +/* Task.asyncDelayed(() -> { SQLHelper sqlHelper = privateMines.getSqlHelper(); String ownerUUID = player.getUniqueId().toString(); Results results = sqlHelper.queryResults("SELECT * FROM shops WHERE owner=?;", ownerUUID); @@ -105,20 +109,24 @@ public synchronized void playerShop(Player player) { GuiItem guiItem = ItemBuilder.from(material) .name(Component.text("Item: " + material.name())).lore( List.of(Component.text("Quantity: " + shopItems.get(material)), - Component.text(" "), Component.text("Click to sell all!"))).asGuiItem(); + Component.text(" "), Component.text("Click to sell all!"))) + .asGuiItem(event -> { + }); paginatedGui.addItem(guiItem); }); // Open the GUI for the player paginatedGui.open(player); }); - paginatedGui.setCloseGuiAction(event -> { - Bukkit.broadcastMessage("closed inventory " + paginatedGui); - Bukkit.broadcastMessage("using event " + event); - Bukkit.broadcastMessage("closing sql: " + sqlHelper); - sqlHelper.close(); - }); - }); + });*/ + +// paginatedGui.setCloseGuiAction(event -> { +// Bukkit.broadcastMessage("closed inventory " + paginatedGui); +// Bukkit.broadcastMessage("using event " + event); +// Bukkit.broadcastMessage("closing sql: " + sqlHelper); +// sqlHelper.flushAllCaches(); +// sqlHelper.close(); +// }); } // @Default @@ -335,7 +343,18 @@ public void pricesDebugAdd(Player player, int quantity, double price) { if (!mineStorage.hasMine(player)) { audienceUtils.sendMessage(player, MessagesConfig.dontOwnMine); } else { - ShopUtils.addItem(player.getUniqueId(), Material.COBBLESTONE, quantity, price); + Material material = player.getInventory().getItemInMainHand().getType(); + ShopUtils.addItem(player.getUniqueId(), material, quantity, price); + } + } + + @Subcommand("remove") + public void pricesDebugRemove(Player player, int quantity) { + if (!mineStorage.hasMine(player)) { + audienceUtils.sendMessage(player, MessagesConfig.dontOwnMine); + } else { + Material material = player.getInventory().getItemInMainHand().getType(); + ShopUtils.removeItem(player.getUniqueId(), material, quantity); } } diff --git a/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java b/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java new file mode 100644 index 00000000..da79feeb --- /dev/null +++ b/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java @@ -0,0 +1,158 @@ +package me.untouchedodin0.privatemines.playershops; + +import dev.triumphteam.gui.builder.item.ItemBuilder; +import dev.triumphteam.gui.guis.Gui; +import dev.triumphteam.gui.guis.GuiItem; +import dev.triumphteam.gui.guis.PaginatedGui; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import me.untouchedodin0.privatemines.PrivateMines; +import net.kyori.adventure.text.Component; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import redempt.redlib.sql.SQLHelper; +import redempt.redlib.sql.SQLHelper.Results; + +public class PlayerShopMenuUtils { + + PrivateMines privateMines = PrivateMines.getPrivateMines(); + private Map shopItems; + private Map quantitySold; + + public void generateMenu(Player player) { + // Initialize the GUI + PaginatedGui paginatedGui = Gui.paginated().title(Component.text("test")).rows(5).pageSize(36) + .create(); + paginatedGui.setDefaultClickAction(event -> event.setCancelled(true)); + + shopItems = getShopItems(player.getUniqueId()); + quantitySold = new HashMap<>(); + + // Previous item + paginatedGui.setItem(5, 3, ItemBuilder.from(Material.PAPER).name(Component.text("Previous")) + .asGuiItem(event -> paginatedGui.previous())); + + // Sell All Button + paginatedGui.setItem(5, 5, ItemBuilder.from(Material.CHEST).name(Component.text("Sell All")) + .lore(List.of(Component.text("Click to sell all your"), + Component.text("items in your shop!"))).asGuiItem(event -> { + player.sendMessage(ChatColor.GREEN + "Selling all items!"); + + List items = paginatedGui.getPageItems().stream().filter(item -> { + String itemName = Objects.requireNonNull(ChatColor.stripColor( + Objects.requireNonNull(item.getItemStack().getItemMeta()).getDisplayName())); + return !(itemName.equals("Previous") || itemName.equals("Sell All") || itemName.equals( + "Next")); + }).toList(); + + for (GuiItem guiItem : items) { + Material material = guiItem.getItemStack().getType(); + int quantity = shopItems.get(material); + + ShopUtils.removeItem(player.getUniqueId(), material, quantity); + quantitySold.put(material, quantity); // Update quantitySold map + shopItems.remove(material); + } + + // Send a message with sold quantities + if (!quantitySold.isEmpty()) { + StringBuilder soldMessage = new StringBuilder(ChatColor.GREEN + "Sold items: "); + quantitySold.forEach( + (material, quantity) -> soldMessage.append(material.name()).append(" x") + .append(quantity).append(", ")); + // Remove trailing comma and space + if (soldMessage.length() > 2) { + soldMessage.setLength(soldMessage.length() - 2); + } + player.sendMessage(soldMessage.toString()); + } + + refreshGui(player, paginatedGui); + })); + + // Next item + paginatedGui.setItem(5, 7, ItemBuilder.from(Material.PAPER).name(Component.text("Next")) + .asGuiItem(event -> paginatedGui.next())); + + shopItems.forEach((material, quantity) -> { + GuiItem guiItem = ItemBuilder.from(material).name(Component.text("Item: " + material.name())) + .lore(List.of(Component.text("Quantity: " + shopItems.get(material)), Component.text(" "), + Component.text("Click to sell all!"))).asGuiItem(event -> { + quantitySold.put(material, quantity); // Update quantitySold map + + // Remove the item from shopItems + shopItems.remove(material); + + // Remove item from database + ShopUtils.removeItem(player.getUniqueId(), material, quantity); + + // Send a message about the sold item + player.sendMessage(ChatColor.GREEN + "Sold item: " + material.name() + " x" + quantity); + + // Refresh the menu + refreshGui(player, paginatedGui); + }); + paginatedGui.addItem(guiItem); + }); + + paginatedGui.open(player); + } + + private void refreshGui(Player player, PaginatedGui paginatedGui) { + paginatedGui.clearPageItems(true); + // Repopulate the GUI with the remaining items from shopItems + shopItems.forEach((material, quantity) -> { + GuiItem guiItem = ItemBuilder.from(material).name(Component.text("Item: " + material.name())) + .lore(List.of(Component.text("Quantity: " + quantity), Component.text(" "), + Component.text("Click to sell all!"))).asGuiItem(event -> { + quantitySold.put(material, quantity); // Update quantitySold map + + // Remove the item from shopItems + shopItems.remove(material); + + // Remove item from database + ShopUtils.removeItem(player.getUniqueId(), material, quantity); + + // Send a message about the sold item + player.sendMessage(ChatColor.GREEN + "Sold item: " + material.name() + " x" + quantity); + + // Refresh the menu + refreshGui(player, paginatedGui); + }); + paginatedGui.addItem(guiItem); + }); + } + + public Map getShopItems(UUID uuid) { + SQLHelper sqlHelper = privateMines.getSqlHelper(); + String ownerUUID = uuid.toString(); + Map shopItems = new HashMap<>(); + + Results results = sqlHelper.queryResults("SELECT * FROM shops WHERE owner=?;", ownerUUID); + + if (results == null) { + Bukkit.broadcastMessage("No results found."); + return null; + } + + results.forEach(result -> { + String owner = result.getString(1); + String seller = result.getString(2); + String item = result.getString(3); + int quantity = result.get(4); + + Material material = Material.getMaterial(item); + if (material != null) { + shopItems.put(material, quantity); + } else { + Bukkit.broadcastMessage("Invalid material: " + item); + } + }); + return shopItems; + } +} diff --git a/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java b/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java index 8a973275..6695f934 100644 --- a/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java +++ b/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java @@ -6,13 +6,10 @@ import me.untouchedodin0.kotlin.mine.storage.MineStorage; import me.untouchedodin0.privatemines.PrivateMines; import me.untouchedodin0.privatemines.mine.Mine; -import net.royawesome.jlibnoise.module.combiner.Min; import org.bukkit.Bukkit; import org.bukkit.Material; -import org.bukkit.entity.Player; import redempt.redlib.misc.Task; import redempt.redlib.sql.SQLHelper; -import redempt.redlib.sql.SQLHelper.Results; public class ShopUtils { @@ -53,7 +50,8 @@ public static void setDefaultPrices(Mine mine) { String uuid = mineData.getMineOwner().toString(); // Log the query for debugging purposes - for (Material material : Objects.requireNonNull(mineData.getMineType().getMaterials()).keySet()) { + for (Material material : Objects.requireNonNull(mineData.getMineType().getMaterials()) + .keySet()) { double price = Objects.requireNonNull(mineData.getMineType().getPrices()).get(material); double finalPrice = mineData.getTax() / 100 * price; double tax = mineData.getTax(); @@ -96,7 +94,8 @@ public static void addItem(UUID uuid, Material material, int quantity, double pr try { // Check if the item already exists String checkQuery = "SELECT quantity FROM shops WHERE owner = ? AND item = ?"; - Integer currentQuantity = sqlHelper.querySingleResult(checkQuery, ownerUUID, materialName); + Integer currentQuantity = sqlHelper.querySingleResult(checkQuery, ownerUUID, + materialName); if (currentQuantity != null) { // Item exists, update the quantity and price @@ -104,13 +103,16 @@ public static void addItem(UUID uuid, Material material, int quantity, double pr String updateQuery = "UPDATE shops SET quantity = ?, price = ? WHERE owner = ? AND item = ?"; sqlHelper.executeUpdate(updateQuery, newQuantity, finalPrice, ownerUUID, materialName); - Bukkit.broadcastMessage("Updated item: " + materialName + " with new quantity: " + newQuantity); + Bukkit.broadcastMessage( + "Updated item: " + materialName + " with new quantity: " + newQuantity); } else { // Item doesn't exist, insert a new row String insertQuery = "INSERT INTO shops (owner, seller, item, quantity, price, tax) VALUES (?, ?, ?, ?, ?, ?)"; - sqlHelper.executeUpdate(insertQuery, ownerUUID, ownerUUID, materialName, quantity, finalPrice, taxRate); + sqlHelper.executeUpdate(insertQuery, ownerUUID, ownerUUID, materialName, quantity, + finalPrice, taxRate); - Bukkit.broadcastMessage("Inserted new item: " + materialName + " with quantity: " + quantity); + Bukkit.broadcastMessage( + "Inserted new item: " + materialName + " with quantity: " + quantity); } } catch (Exception e) { e.printStackTrace(); @@ -120,114 +122,47 @@ public static void addItem(UUID uuid, Material material, int quantity, double pr } } + public static void removeItem(UUID uuid, Material material, int quantity) { + SQLHelper sqlHelper = privateMines.getSqlHelper(); + MineStorage mineStorage = privateMines.getMineStorage(); + Mine mine = mineStorage.get(uuid); + + if (mine != null) { + String ownerUUID = uuid.toString(); + String materialName = material.name(); + + Task.asyncDelayed(() -> { + try { + // Check if the item exists + String checkQuery = "SELECT quantity FROM shops WHERE owner = ? AND item = ?"; + Integer currentQuantity = sqlHelper.querySingleResult(checkQuery, ownerUUID, + materialName); -// public static void addItem(UUID uuid, Material material, int quantity, double price) { -// MineStorage mineStorage = privateMines.getMineStorage(); -// Mine mine = mineStorage.get(uuid); -// -// if (mine != null) { -// MineData mineData = mine.getMineData(); -// String ownerUUID = uuid.toString(); -// String materialName = material.name(); -// -// // Query to check if the item with the specific owner and item already exists -// String checkQuery = "SELECT quantity FROM shops WHERE owner = ? AND item = ?;"; -// -// String insertQuery = String.format( -// "INSERT INTO shops (shop_owner, item, quantity, price) " -// + "VALUES ('%s', '%s', %d, %f) " -// + "ON CONFLICT(shop_owner, item) DO UPDATE SET price = excluded.price;", -// uuid, material.name(), 0, 1.0); -// -// // Log the check query for debugging purposes -// Bukkit.broadcastMessage("Executing check query: " + checkQuery); -//// Bukkit.broadcastMessage("quantity before " + quantityBefore); -//// Bukkit.broadcastMessage("quantity after " + quantityBefore + quantity); -// -// Task.asyncDelayed(() -> { -// sqlHelper.executeUpdate(insertQuery); -//// sqlHelper.execute("UPDATE shops SET price=? WHERE item=? AND owner=?;", -//// price, "COBBLESTONE", uuid.toString()); -//// sqlHelper.execute("UPDATE shops SET quantity=? WHERE item=? AND owner=?;", -//// quantity, "COBBLESTONE", uuid.toString()); -// -// int quantityBefore = sqlHelper.querySingleResult("SELECT quantity FROM shops WHERE owner = ? AND item = ?", ownerUUID, materialName); -// -// // Item doesn't exist, insert a new row -//// String insertQuery = "INSERT INTO shops (owner, seller, item, quantity, price, tax) VALUES (?, ?, ?, ?, ?, ?)"; -//// sqlHelper.execute(insertQuery, ownerUUID, ownerUUID, materialName, quantity, -//// (mineData.getTax() / 100) * price, 0.0); // Adjust tax value as needed -// -//// sqlHelper.executeUpdate("UPDATE shops SET quantity=? WHERE item=? AND price =? AND owner=?;", -//// quantityBefore + quantity, "COBBLESTONE", uuid.toString()); -// -//// try { -//// -//// } -//// Results results = sqlHelper.queryResults(checkQuery, ownerUUID, materialName); -//// -//// if (results.next()) { -//// // Item exists, update the quantity and price -//// int currentQuantity = results.get(4); -//// int newQuantity = currentQuantity + quantity; -//// -//// String updateQuery = "UPDATE shops SET quantity = ?, price = ? WHERE owner = ? AND item = ?"; -//// sqlHelper.execute(updateQuery, newQuantity, (mineData.getTax() / 100) * price, -//// ownerUUID, materialName); -//// -//// Bukkit.broadcastMessage( -//// "Updated item: " + materialName + " with new quantity: " + newQuantity); -//// } else { -//// // Item doesn't exist, insert a new row -//// String insertQuery = "INSERT INTO shops (owner, seller, item, quantity, price, tax) VALUES (?, ?, ?, ?, ?, ?)"; -//// sqlHelper.execute(insertQuery, ownerUUID, ownerUUID, materialName, quantity, -//// (mineData.getTax() / 100) * price, 0.0); // Adjust tax value as needed -//// -//// Bukkit.broadcastMessage( -//// "Inserted new item: " + materialName + " with quantity: " + quantity); -//// } -//// -//// // Clean up the resources -//// results.close(); -//// } catch (Exception e) { -//// throw new RuntimeException(e); -//// } -// }); -// } -// } - -// public static void addItem(UUID uuid, Material material, int quantity, double price) { -// MineStorage mineStorage = privateMines.getMineStorage(); -// -// Mine mine = mineStorage.get(uuid); -// -// if (mine != null) { -// // Query to check if the item with the specific price already exists for the shop owner -// String checkQuery = "SELECT quantity FROM shops WHERE owner = ? AND item = ? AND price = ?;"; -// -// // Get the material name -// String materialName = material.name(); -// -// // Log the check query for debugging purposes -// System.out.println("Executing check query: " + checkQuery); -// -// Bukkit.broadcastMessage("materialName " + materialName); -// -// Task.asyncDelayed(() -> { -// int quantityBefore = sqlHelper.querySingleResult( -// "SELECT quantity FROM shops WHERE item=? AND owner=?;", "COBBLESTONE", -// uuid.toString()); -// sqlHelper.execute("UPDATE shops SET price=? WHERE item=? AND owner=?;", price, -// "COBBLESTONE", uuid.toString()); -// sqlHelper.execute("UPDATE shops SET quantity=? WHERE item=? AND owner=?;", -// quantityBefore + quantity, "COBBLESTONE", uuid.toString()); -// -// int quantityAdded = quantityBefore + quantity; -// -// Bukkit.broadcastMessage("addItem quantity before " + quantityBefore); -// Bukkit.broadcastMessage("addItem quantity added " + quantityAdded); -// }); -// } -// } + if (currentQuantity != null) { + // Item exists, update the quantity + int newQuantity = currentQuantity - quantity; + if (newQuantity > 0) { + String updateQuery = "UPDATE shops SET quantity = ? WHERE owner = ? AND item = ?"; + sqlHelper.executeUpdate(updateQuery, newQuantity, ownerUUID, materialName); + + Bukkit.broadcastMessage( + "Updated item: " + materialName + " with new quantity: " + newQuantity); + } else { + String deleteQuery = "DELETE FROM shops WHERE owner = ? AND item = ?"; + sqlHelper.executeUpdate(deleteQuery, ownerUUID, materialName); + + Bukkit.broadcastMessage("Removed item: " + materialName + " from the shop"); + } + } else { + // Item does not exist + Bukkit.broadcastMessage("Item: " + materialName + " does not exist in the shop"); + } + } catch (Exception e) { + e.printStackTrace(); + Bukkit.broadcastMessage("Error handling item removal: " + materialName); + } + }); + } + } } From 92c243bf8bc45d25d43532ee03e2928b4f102869 Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Wed, 24 Jul 2024 22:08:03 +0100 Subject: [PATCH 34/43] Player shop Menu working much cleaner. Changed quantity from int to long Added a Syntax to PlayerShopCommand#pricesDebugAdd --- .../privatemines/PrivateMines.java | 12 +- .../commands/PlayerShopCommand.java | 291 +----------------- .../listener/sell/PlayerShopListener.java | 12 +- .../playershops/PlayerShopMenuUtils.java | 109 +++---- .../privatemines/playershops/ShopUtils.java | 212 +++++++++---- 5 files changed, 228 insertions(+), 408 deletions(-) diff --git a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java index deb5e383..de387489 100644 --- a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java +++ b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java @@ -265,7 +265,7 @@ CREATE TABLE IF NOT EXISTS shops ( owner VARCHAR(36) NOT NULL, seller VARCHAR(36) NOT NULL, item VARCHAR(50) NOT NULL, - quantity INT NOT NULL, + quantity LONG NOT NULL, price DOUBLE NOT NULL, tax DOUBLE NOT NULL, PRIMARY KEY (owner, item) @@ -385,16 +385,6 @@ public void loadMines() { } public void loadShops() { - /* - sqlHelper.executeUpdate(""" - CREATE TABLE IF NOT EXISTS shops ( - owner VARCHAR(36) NOT NULL, - item VARCHAR(50) NOT NULL, - price DOUBLE NOT NULL, - PRIMARY KEY (owner, item) - ); - """); - */ SQLHelper sqlHelper = getSqlHelper(); Results results = sqlHelper.queryResults("SELECT * FROM shops"); diff --git a/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java b/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java index c7640181..68cf013e 100644 --- a/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java +++ b/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java @@ -5,30 +5,19 @@ import co.aikar.commands.annotation.CommandPermission; import co.aikar.commands.annotation.Default; import co.aikar.commands.annotation.Subcommand; -import dev.triumphteam.gui.builder.item.ItemBuilder; -import dev.triumphteam.gui.guis.Gui; -import dev.triumphteam.gui.guis.GuiItem; -import dev.triumphteam.gui.guis.PaginatedGui; +import co.aikar.commands.annotation.Syntax; import java.util.HashMap; -import java.util.List; import java.util.Map; -import me.untouchedodin0.kotlin.mine.data.MineData; import me.untouchedodin0.kotlin.mine.storage.MineStorage; import me.untouchedodin0.kotlin.utils.AudienceUtils; import me.untouchedodin0.privatemines.PrivateMines; import me.untouchedodin0.privatemines.config.MessagesConfig; import me.untouchedodin0.privatemines.mine.Mine; import me.untouchedodin0.privatemines.playershops.PlayerShopMenuUtils; -import me.untouchedodin0.privatemines.playershops.Shop; import me.untouchedodin0.privatemines.playershops.ShopUtils; -import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; -import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.entity.Player; -import redempt.redlib.misc.Task; -import redempt.redlib.sql.SQLHelper; -import redempt.redlib.sql.SQLHelper.Results; @CommandAlias("playershop|playershops|pshop") public class PlayerShopCommand extends BaseCommand { @@ -54,302 +43,32 @@ public synchronized void playerShop(Player player) { return; } - MineData mineData = mine.getMineData(); - Shop shop = mineData.getShop(); - playerShopMenuUtils.generateMenu(player); - // Initialize the GUI -/* PaginatedGui paginatedGui = Gui.paginated().title(Component.text("test")).rows(5).pageSize(36) - .create(); - - paginatedGui.setDefaultClickAction(event -> event.setCancelled(true)); - - // Previous item - paginatedGui.setItem(5, 3, ItemBuilder.from(Material.PAPER).setName("Previous") - .asGuiItem(event -> paginatedGui.previous())); - // Sell All Button - paginatedGui.setItem(5, 5, ItemBuilder.from(Material.CHEST).name(Component.text("Sell All")) - .lore(List.of(Component.text("Click to sell all your"), - Component.text("items in your shop!"))).asGuiItem(event -> { - player.sendMessage(ChatColor.GREEN + "Selling all items!"); - })); - // Next item - paginatedGui.setItem(5, 7, - ItemBuilder.from(Material.PAPER).setName("Next").asGuiItem(event -> paginatedGui.next()));*/ - - // Asynchronous task to fetch shop data - -/* Task.asyncDelayed(() -> { - SQLHelper sqlHelper = privateMines.getSqlHelper(); - String ownerUUID = player.getUniqueId().toString(); - Results results = sqlHelper.queryResults("SELECT * FROM shops WHERE owner=?;", ownerUUID); - - if (results == null) { - Bukkit.broadcastMessage("No results found."); - return; - } - - results.forEach(result -> { - String owner = result.getString(1); - String seller = result.getString(2); - String item = result.getString(3); - int quantity = result.get(4); - - Material material = Material.getMaterial(item); - if (material != null) { - shopItems.put(material, quantity); - } else { - Bukkit.broadcastMessage("Invalid material: " + item); - } - }); - - // Use a synchronous task to update the shopItems map - Bukkit.getScheduler().runTask(PrivateMines.getPrivateMines(), () -> { - shopItems.forEach((material, quantity) -> { - GuiItem guiItem = ItemBuilder.from(material) - .name(Component.text("Item: " + material.name())).lore( - List.of(Component.text("Quantity: " + shopItems.get(material)), - Component.text(" "), Component.text("Click to sell all!"))) - .asGuiItem(event -> { - }); - paginatedGui.addItem(guiItem); - }); - - // Open the GUI for the player - paginatedGui.open(player); - }); - });*/ - -// paginatedGui.setCloseGuiAction(event -> { -// Bukkit.broadcastMessage("closed inventory " + paginatedGui); -// Bukkit.broadcastMessage("using event " + event); -// Bukkit.broadcastMessage("closing sql: " + sqlHelper); -// sqlHelper.flushAllCaches(); -// sqlHelper.close(); -// }); } -// @Default -// @CommandPermission("privatemines.playershop") -// public synchronized void playerShop(Player player) { -// Map shopItems = new HashMap<>(); -// -// if (!mineStorage.hasMine(player)) { -// audienceUtils.sendMessage(player, MessagesConfig.dontOwnMine); -// } else { -// Mine mine = mineStorage.get(player); -// AtomicDouble atomicDouble = new AtomicDouble(1); -// -// if (mine != null) { -// SQLHelper sqlHelper = privateMines.getSqlHelper(); -// sqlHelper.execute("UPDATE shops SET price=? WHERE item=? AND owner=?;", -// atomicDouble.getAndAdd(1.0), "COBBLESTONE", player.getUniqueId().toString()); -// -// MineData mineData = mine.getMineData(); -// Shop shop = mineData.getShop(); -// -// player.sendMessage("owner " + shop.getOwner()); -// player.sendMessage("prices " + shop.getPrices()); -// -// PaginatedGui paginatedGui = Gui -// .paginated() -// .title(Component.text("test")) -// .rows(5) -// .pageSize(36) -// .create(); -// -// paginatedGui.setDefaultClickAction(event -> event.setCancelled(true)); -// -// // Previous item -// paginatedGui.setItem(5, 3, ItemBuilder.from(Material.PAPER).setName("Previous") -// .asGuiItem(event -> paginatedGui.previous())); -// // Next item -// paginatedGui.setItem(5, 7, ItemBuilder.from(Material.PAPER).setName("Next") -// .asGuiItem(event -> paginatedGui.next())); -// -// // Asynchronous task to fetch shop data -// Task task = Task.asyncDelayed(() -> { -// Results results = sqlHelper.queryResults("SELECT * FROM shops WHERE owner=?;", -// player.getUniqueId().toString()); -// Bukkit.broadcastMessage("results = " + results); -// -// results.forEach(result -> { -// String owner = result.getString(1); -// String seller = result.getString(2); -// String item = result.getString(3); -// int quantity = result.get(4); -// -// Bukkit.broadcastMessage("owner " + owner); -// Bukkit.broadcastMessage("seller " + seller); -// Bukkit.broadcastMessage("item " + item); -// Bukkit.broadcastMessage("quantity " + quantity); -// -// Material material = Material.getMaterial(item); -// if (material != null) { -// shopItems.put(material, quantity); -// -// -// // Use a synchronous task to update the shopItems map -// Bukkit.getScheduler().runTask(PrivateMines.getPrivateMines(), () -> { -// shopItems.forEach(((material1, qty) -> { -// GuiItem guiItem = ItemBuilder.from(material1) -// .name(Component.text("test")) -// .lore(Component.text(material1.name()) -// .append(Component.text(":").appendSpace().append(Component.text(qty)))) -// .asGuiItem(); -// paginatedGui.addItem(guiItem); -// -// Bukkit.broadcastMessage("gui item " + guiItem); -// })); -//// // Add items to the GUI once the shopItems map is updated -//// shopItems.forEach((mat, qty) -> { -//// Bukkit.broadcastMessage("mat? " + mat); -//// Bukkit.broadcastMessage("int? " + qty); -//// GuiItem guiItem = ItemBuilder.from(mat) -//// .name(Component.text(UUID.randomUUID().toString())) -//// .lore(Component.text(mat.name()).append(Component.text(":")).appendSpace() -//// .append(Component.text(qty))) -//// .asGuiItem(); -////// scrollingGui.addItem(guiItem); -//// Bukkit.broadcastMessage("gui item " + guiItem); -//// paginatedGui.addItem(guiItem); -//// }); -// -// // Open the GUI for the player -// }); -// } else { -// Bukkit.broadcastMessage("Invalid material: " + item); -// } -// }); -// }); -// -// paginatedGui.open(player); -// } -// } -// } - -// public void playerShop(Player player) { -// if (!mineStorage.hasMine(player)) { -// audienceUtils.sendMessage(player, MessagesConfig.dontOwnMine); -// } else { -// Mine mine = mineStorage.get(player); -// AtomicDouble atomicDouble = new AtomicDouble(1); -// Map shopItems = new HashMap<>(); -// -// if (mine != null) { -// SQLHelper sqlHelper = privateMines.getSqlHelper(); -// sqlHelper.execute("UPDATE shops SET price=? WHERE item=? AND owner=?;", -// atomicDouble.getAndAdd(1.0), "COBBLESTONE", player.getUniqueId().toString()); -// -//// SQLUtils.updatePrice(player.getUniqueId(), Material.COBBLESTONE, 1.0); -// -// MineData mineData = mine.getMineData(); -// Shop shop = mineData.getShop(); -// -// player.sendMessage("owner " + shop.getOwner()); -// player.sendMessage("prices " + shop.getPrices()); -// -// ScrollingGui scrollingGui = Gui.scrolling() -// .title(Component.text("Title")) -// .rows(6) -// .disableItemTake() -// .create(); -// -// Task.asyncDelayed(() -> { -// Results results = sqlHelper.queryResults("SELECT * FROM shops WHERE owner=?;", -// player.getUniqueId().toString()); -// Bukkit.broadcastMessage("results = " + results); -// -// results.forEach(result -> { -// String owner = result.getString(1); -// String seller = result.getString(2); -// String item = result.getString(3); -// int quantity = result.get(4); -// -// Bukkit.broadcastMessage("owner " + owner); -// Bukkit.broadcastMessage("seller " + seller); -// Bukkit.broadcastMessage("item " + item); -// Bukkit.broadcastMessage("quantity " + quantity); -// -// Task.syncDelayed(() -> { -// shopItems.put(Material.getMaterial(item), quantity); -// }); -// }); -// }); -// -// shopItems.forEach((material, integer) -> { -// Bukkit.broadcastMessage("mat? " + material); -// Bukkit.broadcastMessage("int? " + integer); -// }); -// -// -// for (Entry entry : shopItems.entrySet()) { -// Bukkit.broadcastMessage("entry " + entry); -// GuiItem guiItem = ItemBuilder.from( -// Objects.requireNonNullElse(entry.getKey(), Material.STONE)) -// .name(Component.text(UUID.randomUUID().toString())) -// .lore(Component.text(Material.STONE.name()).append(Component.text(":")).appendSpace() -// .append(Component.text(1))).asGuiItem(); -// scrollingGui.addItem(guiItem); -// -// Bukkit.broadcastMessage("gui item " + guiItem); -// Bukkit.broadcastMessage("scrolling gui " + scrollingGui); -// } -//// for (Material material : shop.getPrices().keySet()) { -//// GuiItem guiItem = ItemBuilder.from(Objects.requireNonNullElse(material, Material.STONE)) -//// .name(Component.text(UUID.randomUUID().toString())) -//// .asGuiItem(); -//// scrollingGui.addItem(guiItem); -//// } -// -// GuiItem guiItem = ItemBuilder.from(Material.STONE) -// .name(Component.text("Test")) -// .asGuiItem(); -// -//// scrollingGui.addItem(guiItem); -//// scrollingGui.addItem(guiItem); -// -// scrollingGui.open(player); -// } -// } -// } - @Subcommand("debug") public void pricesDebug(Player player, double price, int quantity) { if (!mineStorage.hasMine(player)) { audienceUtils.sendMessage(player, MessagesConfig.dontOwnMine); } else { Mine mine = mineStorage.get(player); - - if (mine != null) { - SQLHelper sqlHelper = privateMines.getSqlHelper(); - int quantityBefore = sqlHelper.querySingleResult( - "SELECT quantity FROM shops WHERE item=? AND owner=?;", "COBBLESTONE", - player.getUniqueId().toString()); - sqlHelper.execute("UPDATE shops SET price=? WHERE item=? AND owner=?;", price, - "COBBLESTONE", player.getUniqueId().toString()); - sqlHelper.execute("UPDATE shops SET quantity=? WHERE item=? AND owner=?;", quantity, - "COBBLESTONE", player.getUniqueId().toString()); - - String[] messages = {"Quantity before: " + quantityBefore, "Set the prices of", - "cobblestone to " + price, "Set the quantity of", " cobblestone to " + quantity}; - player.sendMessage(messages); - } } } @Subcommand("add") - public void pricesDebugAdd(Player player, int quantity, double price) { + @Syntax(" ") + public void pricesDebugAdd(Player player, long quantity, double price) { if (!mineStorage.hasMine(player)) { audienceUtils.sendMessage(player, MessagesConfig.dontOwnMine); } else { Material material = player.getInventory().getItemInMainHand().getType(); + Bukkit.broadcastMessage("" + material); ShopUtils.addItem(player.getUniqueId(), material, quantity, price); } } @Subcommand("remove") - public void pricesDebugRemove(Player player, int quantity) { + public void pricesDebugRemove(Player player, long quantity) { if (!mineStorage.hasMine(player)) { audienceUtils.sendMessage(player, MessagesConfig.dontOwnMine); } else { diff --git a/src/main/java/me/untouchedodin0/privatemines/listener/sell/PlayerShopListener.java b/src/main/java/me/untouchedodin0/privatemines/listener/sell/PlayerShopListener.java index 84047c8c..dc4d10ee 100644 --- a/src/main/java/me/untouchedodin0/privatemines/listener/sell/PlayerShopListener.java +++ b/src/main/java/me/untouchedodin0/privatemines/listener/sell/PlayerShopListener.java @@ -1,13 +1,16 @@ package me.untouchedodin0.privatemines.listener.sell; +import java.util.Map; import java.util.Objects; import me.untouchedodin0.kotlin.mine.data.MineData; import me.untouchedodin0.kotlin.mine.storage.MineStorage; import me.untouchedodin0.privatemines.PrivateMines; import me.untouchedodin0.privatemines.mine.Mine; import me.untouchedodin0.privatemines.playershops.Shop; +import me.untouchedodin0.privatemines.playershops.ShopUtils; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -27,12 +30,11 @@ public void onBlockBreak(BlockBreakEvent event) { if (mine != null) { MineData mineData = mine.getMineData(); Shop shop = mineData.getShop(); + ShopUtils shopUtils = new ShopUtils(); - Bukkit.broadcastMessage("Block break listener lol."); - Bukkit.broadcastMessage("nearest mine " + mine); - Bukkit.broadcastMessage("mine data " + mineData); - Bukkit.broadcastMessage("shop " + shop); - Bukkit.broadcastMessage("shop prices " + Objects.requireNonNull(shop).getPrices()); + Map shopItems = shopUtils.getShopItems(mineData.getMineOwner()); + + shopUtils.sellItems(player.getUniqueId(), true); } } } diff --git a/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java b/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java index da79feeb..2fc0b0d8 100644 --- a/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java +++ b/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java @@ -1,5 +1,7 @@ package me.untouchedodin0.privatemines.playershops; +import static me.untouchedodin0.privatemines.utils.Utils.format; + import dev.triumphteam.gui.builder.item.ItemBuilder; import dev.triumphteam.gui.guis.Gui; import dev.triumphteam.gui.guis.GuiItem; @@ -8,29 +10,28 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.UUID; import me.untouchedodin0.privatemines.PrivateMines; import net.kyori.adventure.text.Component; -import org.bukkit.Bukkit; +import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.entity.Player; -import redempt.redlib.sql.SQLHelper; -import redempt.redlib.sql.SQLHelper.Results; public class PlayerShopMenuUtils { PrivateMines privateMines = PrivateMines.getPrivateMines(); - private Map shopItems; - private Map quantitySold; + private Map shopItems; + private Map quantitySold; public void generateMenu(Player player) { + ShopUtils shopUtils = new ShopUtils(); + // Initialize the GUI PaginatedGui paginatedGui = Gui.paginated().title(Component.text("test")).rows(5).pageSize(36) .create(); paginatedGui.setDefaultClickAction(event -> event.setCancelled(true)); - shopItems = getShopItems(player.getUniqueId()); + shopItems = shopUtils.getShopItems(player.getUniqueId()); quantitySold = new HashMap<>(); // Previous item @@ -52,7 +53,7 @@ public void generateMenu(Player player) { for (GuiItem guiItem : items) { Material material = guiItem.getItemStack().getType(); - int quantity = shopItems.get(material); + long quantity = shopItems.get(material); ShopUtils.removeItem(player.getUniqueId(), material, quantity); quantitySold.put(material, quantity); // Update quantitySold map @@ -80,24 +81,28 @@ public void generateMenu(Player player) { .asGuiItem(event -> paginatedGui.next())); shopItems.forEach((material, quantity) -> { - GuiItem guiItem = ItemBuilder.from(material).name(Component.text("Item: " + material.name())) - .lore(List.of(Component.text("Quantity: " + shopItems.get(material)), Component.text(" "), - Component.text("Click to sell all!"))).asGuiItem(event -> { - quantitySold.put(material, quantity); // Update quantitySold map - - // Remove the item from shopItems - shopItems.remove(material); - - // Remove item from database - ShopUtils.removeItem(player.getUniqueId(), material, quantity); - - // Send a message about the sold item - player.sendMessage(ChatColor.GREEN + "Sold item: " + material.name() + " x" + quantity); - - // Refresh the menu - refreshGui(player, paginatedGui); - }); - paginatedGui.addItem(guiItem); + if (quantity > 0) { + GuiItem guiItem = ItemBuilder.from(material) + .name(Component.text(format(material))).lore( + List.of(Component.text("Quantity: " + shopItems.get(material)).color(NamedTextColor.GRAY), Component.text(" "), + Component.text("Click to sell all!").color(NamedTextColor.GREEN))).asGuiItem(event -> { + quantitySold.put(material, quantity); // Update quantitySold map + + // Remove the item from shopItems + shopItems.remove(material); + + // Remove item from database + ShopUtils.removeItem(player.getUniqueId(), material, quantity); + + // Send a message about the sold item + player.sendMessage( + ChatColor.GREEN + "Sold item: " + material.name() + " x" + quantity); + + // Refresh the menu + refreshGui(player, paginatedGui); + }); + paginatedGui.addItem(guiItem); + } }); paginatedGui.open(player); @@ -128,31 +133,29 @@ private void refreshGui(Player player, PaginatedGui paginatedGui) { }); } - public Map getShopItems(UUID uuid) { - SQLHelper sqlHelper = privateMines.getSqlHelper(); - String ownerUUID = uuid.toString(); - Map shopItems = new HashMap<>(); - - Results results = sqlHelper.queryResults("SELECT * FROM shops WHERE owner=?;", ownerUUID); - - if (results == null) { - Bukkit.broadcastMessage("No results found."); - return null; - } - - results.forEach(result -> { - String owner = result.getString(1); - String seller = result.getString(2); - String item = result.getString(3); - int quantity = result.get(4); - - Material material = Material.getMaterial(item); - if (material != null) { - shopItems.put(material, quantity); - } else { - Bukkit.broadcastMessage("Invalid material: " + item); - } - }); - return shopItems; - } +// public Map getShopItems(UUID uuid) { +// SQLHelper sqlHelper = privateMines.getSqlHelper(); +// String ownerUUID = uuid.toString(); +// Map shopItems = new HashMap<>(); +// +// Results results = sqlHelper.queryResults("SELECT * FROM shops WHERE owner=?;", ownerUUID); +// +// if (results == null) { +// Bukkit.broadcastMessage("No results found."); +// return null; +// } +// +// results.forEach(result -> { +// String item = result.getString(3); +// long quantity = result.getLong(4); +// +// Material material = Material.getMaterial(item); +// if (material != null) { +// shopItems.put(material, quantity); +// } else { +// Bukkit.broadcastMessage("Invalid material: " + item); +// } +// }); +// return shopItems; +// } } diff --git a/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java b/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java index 6695f934..91eebe65 100644 --- a/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java +++ b/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java @@ -1,5 +1,9 @@ package me.untouchedodin0.privatemines.playershops; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import java.util.UUID; import me.untouchedodin0.kotlin.mine.data.MineData; @@ -10,11 +14,11 @@ import org.bukkit.Material; import redempt.redlib.misc.Task; import redempt.redlib.sql.SQLHelper; +import redempt.redlib.sql.SQLHelper.Results; public class ShopUtils { private static final PrivateMines privateMines = PrivateMines.getPrivateMines(); -// private SQLHelper sqlHelper = privateMines.getSqlHelper(); public static void updatePrice(UUID uuid, Material material, double price) { SQLHelper sqlHelper = privateMines.getSqlHelper(); @@ -29,15 +33,8 @@ public static void updatePrice(UUID uuid, Material material, double price) { "ON CONFLICT(owner, item) DO UPDATE SET price = excluded.price;", uuid, uuid, material.name(), 0, price, mineData.getTax()); -// String updateQuery = String.format( -// "UPDATE shops SET price = %f WHERE owner = '%s' AND item = '%s';", price, uuid, -// material.name()); - - // Log the query for debugging purposes -// System.out.println("Executing query: " + updateQuery); - Bukkit.broadcastMessage( - String.format("updating item %s in %s's mine to %f", material.name(), uuid.toString(), + String.format("updating item %s in %s's mine to %f", material.name(), uuid, price)); Task.asyncDelayed(() -> sqlHelper.executeUpdate(insertQuery)); @@ -77,8 +74,7 @@ public static void setDefaultPrices(Mine mine) { } } - - public static void addItem(UUID uuid, Material material, int quantity, double price) { + public static void addItem(UUID uuid, Material material, long quantity, double price) { SQLHelper sqlHelper = privateMines.getSqlHelper(); MineStorage mineStorage = privateMines.getMineStorage(); Mine mine = mineStorage.get(uuid); @@ -90,39 +86,71 @@ public static void addItem(UUID uuid, Material material, int quantity, double pr double taxRate = mineData.getTax() / 100.0; double finalPrice = price * (1 + taxRate); + Bukkit.broadcastMessage("material name " + materialName); + Task.asyncDelayed(() -> { try { // Check if the item already exists String checkQuery = "SELECT quantity FROM shops WHERE owner = ? AND item = ?"; - Integer currentQuantity = sqlHelper.querySingleResult(checkQuery, ownerUUID, - materialName); + long currentQuantity = 0L; + boolean itemExists = false; - if (currentQuantity != null) { - // Item exists, update the quantity and price - int newQuantity = currentQuantity + quantity; - String updateQuery = "UPDATE shops SET quantity = ?, price = ? WHERE owner = ? AND item = ?"; - sqlHelper.executeUpdate(updateQuery, newQuantity, finalPrice, ownerUUID, materialName); + try (PreparedStatement statement = sqlHelper.getConnection() + .prepareStatement(checkQuery)) { + statement.setString(1, ownerUUID); + statement.setString(2, materialName); - Bukkit.broadcastMessage( - "Updated item: " + materialName + " with new quantity: " + newQuantity); + try (ResultSet resultSet = statement.executeQuery()) { + if (resultSet.next()) { + currentQuantity = resultSet.getLong("quantity"); // Retrieves BIGINT as long + itemExists = true; + } + } + } + + // Calculate the new quantity and check for overflow + long newQuantity; + if (quantity > 0 && (Long.MAX_VALUE - currentQuantity < quantity)) { + // Overflow detected + newQuantity = Long.MAX_VALUE; // Cap to max value + Bukkit.broadcastMessage("Overflow detected. Quantity capped to " + newQuantity); } else { - // Item doesn't exist, insert a new row - String insertQuery = "INSERT INTO shops (owner, seller, item, quantity, price, tax) VALUES (?, ?, ?, ?, ?, ?)"; - sqlHelper.executeUpdate(insertQuery, ownerUUID, ownerUUID, materialName, quantity, - finalPrice, taxRate); + newQuantity = currentQuantity + quantity; + } - Bukkit.broadcastMessage( - "Inserted new item: " + materialName + " with quantity: " + quantity); + // Update or insert the item in the database + String updateQuery; + if (itemExists) { + updateQuery = "UPDATE shops SET quantity = ?, price = ? WHERE owner = ? AND item = ?"; + } else { + updateQuery = "INSERT INTO shops (owner, item, quantity, price) VALUES (?, ?, ?, ?)"; + } + + try (PreparedStatement statement = sqlHelper.getConnection() + .prepareStatement(updateQuery)) { + statement.setLong(1, newQuantity); + statement.setDouble(2, finalPrice); + statement.setString(3, ownerUUID); + statement.setString(4, materialName); + + statement.executeUpdate(); } + + Bukkit.broadcastMessage( + "Updated item: " + materialName + " with new quantity: " + newQuantity); + } catch (Exception e) { + Bukkit.getLogger().severe("Error updating item: " + e.getMessage()); e.printStackTrace(); - Bukkit.broadcastMessage("Error handling item: " + materialName); } }); + } else { + Bukkit.getLogger().warning("Mine not found for UUID: " + uuid.toString()); } } - public static void removeItem(UUID uuid, Material material, int quantity) { + + public static void removeItem(UUID uuid, Material material, long quantity) { SQLHelper sqlHelper = privateMines.getSqlHelper(); MineStorage mineStorage = privateMines.getMineStorage(); Mine mine = mineStorage.get(uuid); @@ -132,34 +160,112 @@ public static void removeItem(UUID uuid, Material material, int quantity) { String materialName = material.name(); Task.asyncDelayed(() -> { - try { - // Check if the item exists - String checkQuery = "SELECT quantity FROM shops WHERE owner = ? AND item = ?"; - Integer currentQuantity = sqlHelper.querySingleResult(checkQuery, ownerUUID, - materialName); - - if (currentQuantity != null) { - // Item exists, update the quantity - int newQuantity = currentQuantity - quantity; - if (newQuantity > 0) { - String updateQuery = "UPDATE shops SET quantity = ? WHERE owner = ? AND item = ?"; - sqlHelper.executeUpdate(updateQuery, newQuantity, ownerUUID, materialName); - - Bukkit.broadcastMessage( - "Updated item: " + materialName + " with new quantity: " + newQuantity); - } else { - String deleteQuery = "DELETE FROM shops WHERE owner = ? AND item = ?"; - sqlHelper.executeUpdate(deleteQuery, ownerUUID, materialName); + int maxRetries = 5; + int retryDelay = 1000; // 1 second delay between retries - Bukkit.broadcastMessage("Removed item: " + materialName + " from the shop"); + for (int attempt = 1; attempt <= maxRetries; attempt++) { + try { + // Check if the item exists and get the current quantity + String checkQuery = "SELECT quantity FROM shops WHERE owner = ? AND item = ?"; + long currentQuantity = 0L; + + try (PreparedStatement statement = sqlHelper.getConnection() + .prepareStatement(checkQuery)) { + statement.setString(1, ownerUUID); + statement.setString(2, materialName); + + try (ResultSet resultSet = statement.executeQuery()) { + if (resultSet.next()) { + currentQuantity = resultSet.getLong("quantity"); // Retrieves BIGINT as long + } + } + } + + // Calculate the new quantity and check for underflow + long newQuantity = Math.max(currentQuantity - quantity, 0); + + // Update the quantity in the database + String updateQuery = "UPDATE shops SET quantity = ? WHERE owner = ? AND item = ?"; + sqlHelper.executeUpdate(updateQuery, newQuantity, ownerUUID, materialName); + + Bukkit.broadcastMessage( + "Updated item: " + materialName + " with new quantity: " + newQuantity); + break; // Exit the retry loop on success + + } catch (Exception e) { + // Log the exception and retry if needed + Bukkit.broadcastMessage("Error handling item removal: " + materialName); + + if (attempt < maxRetries) { + try { + Thread.sleep(retryDelay); // Wait before retrying + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); // Restore interrupted status + } + } else { + throw new RuntimeException("Failed to remove item after " + maxRetries + " attempts", + e); } - } else { - // Item does not exist - Bukkit.broadcastMessage("Item: " + materialName + " does not exist in the shop"); } - } catch (Exception e) { - e.printStackTrace(); - Bukkit.broadcastMessage("Error handling item removal: " + materialName); + } + }); + } + } + + public Map getShopItems(UUID uuid) { + SQLHelper sqlHelper = privateMines.getSqlHelper(); + String ownerUUID = uuid.toString(); + Map shopItems = new HashMap<>(); + + Results results = sqlHelper.queryResults("SELECT * FROM shops WHERE owner=?;", ownerUUID); + + if (results == null) { + Bukkit.broadcastMessage("No results found."); + return null; + } + + results.forEach(result -> { + String item = result.getString(3); + long quantity = result.getLong(4); + + Material material = Material.getMaterial(item); + if (material != null) { + shopItems.put(material, quantity); + } else { + Bukkit.broadcastMessage("Invalid material: " + item); + } + }); + return shopItems; + } + + public void sellItems(UUID uuid, boolean includeTax) { + SQLHelper sqlHelper = privateMines.getSqlHelper(); + String ownerUUID = uuid.toString(); + Map shopItems = new HashMap<>(); + + Results results = sqlHelper.queryResults("SELECT * FROM shops WHERE owner=?;", ownerUUID); + + if (results == null) { + Bukkit.broadcastMessage("No results found."); + } + + if (results != null) { + results.forEach(result -> { + String item = result.getString(3); + long price = result.getLong(5); + double tax = result.get(6); + + if (!includeTax) { + shopItems.put(Material.getMaterial(item), price); + } else { + // This calculates the tax amount (how much is subtracted for tax purposes) + double taxAmount = price * tax / 100.0; + + // This calculates the earner's amount (how much the seller receives after tax) + double earnerAmount = price - taxAmount; + + Bukkit.getPlayer(uuid).sendMessage("You earnt $" + earnerAmount); + Bukkit.getPlayer(uuid).sendMessage("You paid $" + taxAmount + " in taxes.."); } }); } From 703760dfc596ffb2bfb67f3e6c406ba048e64e0f Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Wed, 24 Jul 2024 22:11:35 +0100 Subject: [PATCH 35/43] Dev build warning --- .../me/untouchedodin0/privatemines/PrivateMines.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java index de387489..802c65fb 100644 --- a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java +++ b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java @@ -114,6 +114,18 @@ public static PrivateMines getPrivateMines() { @Override public void onEnable() { + System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + System.out.println(" "); + System.out.println("Hello, this a warning message"); + System.out.println("to inform you that you're using"); + System.out.println("a heavily in development version."); + System.out.println("I do not take any responsibility"); + System.out.println("for this being used on production"); + System.out.println("and breaking stuff."); + System.out.println("YOU HAVE BEEN WARNED."); + System.out.println(" "); + System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + Instant start = Instant.now(); getLogger().info("Loading Private Mines v" + getDescription().getVersion()); saveDefaultConfig(); From 4410d451b0c30ded29dfe05d87b9379811b620bf Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Sun, 28 Jul 2024 22:22:46 +0100 Subject: [PATCH 36/43] Player Shop working better. --- .../commands/PlayerShopCommand.java | 5 +- .../listener/sell/PlayerShopListener.java | 14 ++- .../playershops/PlayerShopMenuUtils.java | 111 ++++++++++-------- .../privatemines/playershops/Shop.java | 4 + .../privatemines/playershops/ShopItem.java | 35 ++++++ .../privatemines/playershops/ShopUtils.java | 27 +++-- 6 files changed, 129 insertions(+), 67 deletions(-) create mode 100644 src/main/java/me/untouchedodin0/privatemines/playershops/ShopItem.java diff --git a/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java b/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java index 68cf013e..b6570f3a 100644 --- a/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java +++ b/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java @@ -14,6 +14,7 @@ import me.untouchedodin0.privatemines.config.MessagesConfig; import me.untouchedodin0.privatemines.mine.Mine; import me.untouchedodin0.privatemines.playershops.PlayerShopMenuUtils; +import me.untouchedodin0.privatemines.playershops.ShopItem; import me.untouchedodin0.privatemines.playershops.ShopUtils; import org.bukkit.Bukkit; import org.bukkit.Material; @@ -63,7 +64,9 @@ public void pricesDebugAdd(Player player, long quantity, double price) { } else { Material material = player.getInventory().getItemInMainHand().getType(); Bukkit.broadcastMessage("" + material); - ShopUtils.addItem(player.getUniqueId(), material, quantity, price); + ShopItem shopItem = new ShopItem(material, quantity, price, 0); + ShopUtils.addItem(player.getUniqueId(), shopItem); +// ShopUtils.addItem(player.getUniqueId(), material, quantity, price); } } diff --git a/src/main/java/me/untouchedodin0/privatemines/listener/sell/PlayerShopListener.java b/src/main/java/me/untouchedodin0/privatemines/listener/sell/PlayerShopListener.java index dc4d10ee..f4c733d2 100644 --- a/src/main/java/me/untouchedodin0/privatemines/listener/sell/PlayerShopListener.java +++ b/src/main/java/me/untouchedodin0/privatemines/listener/sell/PlayerShopListener.java @@ -7,6 +7,7 @@ import me.untouchedodin0.privatemines.PrivateMines; import me.untouchedodin0.privatemines.mine.Mine; import me.untouchedodin0.privatemines.playershops.Shop; +import me.untouchedodin0.privatemines.playershops.ShopItem; import me.untouchedodin0.privatemines.playershops.ShopUtils; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -25,16 +26,19 @@ public class PlayerShopListener implements Listener { @EventHandler(priority = EventPriority.LOWEST) public void onBlockBreak(BlockBreakEvent event) { Player player = event.getPlayer(); - Location location = player.getLocation(); + Location location = event.getBlock().getLocation(); Mine mine = mineStorage.getClosest(location); if (mine != null) { MineData mineData = mine.getMineData(); Shop shop = mineData.getShop(); ShopUtils shopUtils = new ShopUtils(); - - Map shopItems = shopUtils.getShopItems(mineData.getMineOwner()); - - shopUtils.sellItems(player.getUniqueId(), true); + Material material = location.getBlock().getType(); + int quantity = event.getBlock().getDrops().size(); + double price = shop != null ? shop.getPrice(material) : 0; + ShopItem shopItem = new ShopItem(material, quantity, price, 0); + ShopUtils.addItem(mineData.getMineOwner(), shopItem); +// ShopUtils.addItem(player.getUniqueId(), material, quantity, price); + //shopUtils.sellItems(player.getUniqueId(), true); } } } diff --git a/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java b/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java index 2fc0b0d8..1e9abdc9 100644 --- a/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java +++ b/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java @@ -20,7 +20,8 @@ public class PlayerShopMenuUtils { PrivateMines privateMines = PrivateMines.getPrivateMines(); - private Map shopItems; + // private Map shopItems; + private List shopItems; private Map quantitySold; public void generateMenu(Player player) { @@ -44,6 +45,15 @@ public void generateMenu(Player player) { Component.text("items in your shop!"))).asGuiItem(event -> { player.sendMessage(ChatColor.GREEN + "Selling all items!"); + shopItems.forEach(shopItem -> { + quantitySold.put(shopItem.getItem(), shopItem.getQuantity()); + ShopUtils.removeItem(player.getUniqueId(), shopItem.getItem(), shopItem.getQuantity()); + }); + shopItems.clear(); + quantitySold.forEach((material, aLong) -> { + player.sendMessage("Sold " + aLong + "x " + material.name()); + }); + List items = paginatedGui.getPageItems().stream().filter(item -> { String itemName = Objects.requireNonNull(ChatColor.stripColor( Objects.requireNonNull(item.getItemStack().getItemMeta()).getDisplayName())); @@ -51,27 +61,20 @@ public void generateMenu(Player player) { "Next")); }).toList(); - for (GuiItem guiItem : items) { - Material material = guiItem.getItemStack().getType(); - long quantity = shopItems.get(material); - - ShopUtils.removeItem(player.getUniqueId(), material, quantity); - quantitySold.put(material, quantity); // Update quantitySold map - shopItems.remove(material); - } - // Send a message with sold quantities - if (!quantitySold.isEmpty()) { - StringBuilder soldMessage = new StringBuilder(ChatColor.GREEN + "Sold items: "); - quantitySold.forEach( - (material, quantity) -> soldMessage.append(material.name()).append(" x") - .append(quantity).append(", ")); - // Remove trailing comma and space - if (soldMessage.length() > 2) { - soldMessage.setLength(soldMessage.length() - 2); - } - player.sendMessage(soldMessage.toString()); - } +// if (!quantitySold.isEmpty()) { +// StringBuilder soldMessage = new StringBuilder(ChatColor.GREEN + "Sold items: "); +// quantitySold.forEach( +// (material, quantity) -> soldMessage.append(material.name()).append(" x") +// .append(quantity).append(", ")); +// // Remove trailing comma and space +// if (soldMessage.length() > 2) { +// soldMessage.setLength(soldMessage.length() - 2); +// } +// +// +// player.sendMessage(soldMessage.toString()); +// } refreshGui(player, paginatedGui); })); @@ -80,19 +83,25 @@ public void generateMenu(Player player) { paginatedGui.setItem(5, 7, ItemBuilder.from(Material.PAPER).name(Component.text("Next")) .asGuiItem(event -> paginatedGui.next())); - shopItems.forEach((material, quantity) -> { - if (quantity > 0) { - GuiItem guiItem = ItemBuilder.from(material) - .name(Component.text(format(material))).lore( - List.of(Component.text("Quantity: " + shopItems.get(material)).color(NamedTextColor.GRAY), Component.text(" "), - Component.text("Click to sell all!").color(NamedTextColor.GREEN))).asGuiItem(event -> { + shopItems.forEach(shopItem -> { + if (shopItem.getQuantity() > 0) { + double price = shopItem.getPrice(); + Material material = shopItem.getItem(); + long quantity = shopItem.getQuantity(); + + GuiItem guiItem = ItemBuilder.from(material).name(Component.text(format(material))).lore( + List.of( + Component.text("Quantity: " + shopItem.getQuantity()).color(NamedTextColor.GRAY), + Component.text("Price: " + price), Component.text(" "), + Component.text("Click to sell all!").color(NamedTextColor.GREEN))) + .asGuiItem(event -> { quantitySold.put(material, quantity); // Update quantitySold map // Remove the item from shopItems shopItems.remove(material); // Remove item from database - ShopUtils.removeItem(player.getUniqueId(), material, quantity); +// ShopUtils.removeItem(player.getUniqueId(), material, quantity); // Send a message about the sold item player.sendMessage( @@ -111,7 +120,11 @@ public void generateMenu(Player player) { private void refreshGui(Player player, PaginatedGui paginatedGui) { paginatedGui.clearPageItems(true); // Repopulate the GUI with the remaining items from shopItems - shopItems.forEach((material, quantity) -> { + + shopItems.forEach(shopItem -> { + Material material = shopItem.getItem(); + long quantity = shopItem.getQuantity(); + GuiItem guiItem = ItemBuilder.from(material).name(Component.text("Item: " + material.name())) .lore(List.of(Component.text("Quantity: " + quantity), Component.text(" "), Component.text("Click to sell all!"))).asGuiItem(event -> { @@ -132,30 +145,24 @@ private void refreshGui(Player player, PaginatedGui paginatedGui) { paginatedGui.addItem(guiItem); }); } - -// public Map getShopItems(UUID uuid) { -// SQLHelper sqlHelper = privateMines.getSqlHelper(); -// String ownerUUID = uuid.toString(); -// Map shopItems = new HashMap<>(); +} +// shopItems.forEach((material, quantity) -> { +// GuiItem guiItem = ItemBuilder.from(material).name(Component.text("Item: " + material.name())) +// .lore(List.of(Component.text("Quantity: " + quantity), Component.text(" "), +// Component.text("Click to sell all!"))).asGuiItem(event -> { +// quantitySold.put(material, quantity); // Update quantitySold map // -// Results results = sqlHelper.queryResults("SELECT * FROM shops WHERE owner=?;", ownerUUID); +// // Remove the item from shopItems +// shopItems.remove(material); // -// if (results == null) { -// Bukkit.broadcastMessage("No results found."); -// return null; -// } +// // Remove item from database +// ShopUtils.removeItem(player.getUniqueId(), material, quantity); // -// results.forEach(result -> { -// String item = result.getString(3); -// long quantity = result.getLong(4); +// // Send a message about the sold item +// player.sendMessage(ChatColor.GREEN + "Sold item: " + material.name() + " x" + quantity); // -// Material material = Material.getMaterial(item); -// if (material != null) { -// shopItems.put(material, quantity); -// } else { -// Bukkit.broadcastMessage("Invalid material: " + item); -// } -// }); -// return shopItems; -// } -} +// // Refresh the menu +// refreshGui(player, paginatedGui); +// }); +// paginatedGui.addItem(guiItem); + diff --git a/src/main/java/me/untouchedodin0/privatemines/playershops/Shop.java b/src/main/java/me/untouchedodin0/privatemines/playershops/Shop.java index c97fd345..e975f5c7 100644 --- a/src/main/java/me/untouchedodin0/privatemines/playershops/Shop.java +++ b/src/main/java/me/untouchedodin0/privatemines/playershops/Shop.java @@ -74,4 +74,8 @@ public void setRegion(ProtectedRegion region) { public void setPrices(Map prices) { this.prices = prices; } + + public double getPrice(Material material) { + return prices.get(material); + } } diff --git a/src/main/java/me/untouchedodin0/privatemines/playershops/ShopItem.java b/src/main/java/me/untouchedodin0/privatemines/playershops/ShopItem.java new file mode 100644 index 00000000..28b1f71c --- /dev/null +++ b/src/main/java/me/untouchedodin0/privatemines/playershops/ShopItem.java @@ -0,0 +1,35 @@ +package me.untouchedodin0.privatemines.playershops; + +import java.util.UUID; +import org.bukkit.Material; + +public class ShopItem { + + private Material item; + private long quantity; + private double price; + private double tax; + + public ShopItem(Material material, long quantity, double price, double tax) { + this.item = material; + this.quantity = quantity; + this.price = price; + this.tax = tax; + } + + public Material getItem() { + return item; + } + + public long getQuantity() { + return quantity; + } + + public double getPrice() { + return price; + } + + public double getTax() { + return tax; + } +} diff --git a/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java b/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java index 91eebe65..bef5a1d3 100644 --- a/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java +++ b/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java @@ -1,8 +1,12 @@ package me.untouchedodin0.privatemines.playershops; +import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.UUID; @@ -74,7 +78,8 @@ public static void setDefaultPrices(Mine mine) { } } - public static void addItem(UUID uuid, Material material, long quantity, double price) { + + public static void addItem(UUID uuid, ShopItem shopItem) { SQLHelper sqlHelper = privateMines.getSqlHelper(); MineStorage mineStorage = privateMines.getMineStorage(); Mine mine = mineStorage.get(uuid); @@ -82,9 +87,9 @@ public static void addItem(UUID uuid, Material material, long quantity, double p if (mine != null) { MineData mineData = mine.getMineData(); String ownerUUID = uuid.toString(); - String materialName = material.name(); + String materialName = shopItem.getItem().name(); double taxRate = mineData.getTax() / 100.0; - double finalPrice = price * (1 + taxRate); + double finalPrice = shopItem.getPrice() * (1 + taxRate); Bukkit.broadcastMessage("material name " + materialName); @@ -110,12 +115,12 @@ public static void addItem(UUID uuid, Material material, long quantity, double p // Calculate the new quantity and check for overflow long newQuantity; - if (quantity > 0 && (Long.MAX_VALUE - currentQuantity < quantity)) { + if (shopItem.getQuantity() > 0 && (Long.MAX_VALUE - currentQuantity < shopItem.getQuantity())) { // Overflow detected newQuantity = Long.MAX_VALUE; // Cap to max value Bukkit.broadcastMessage("Overflow detected. Quantity capped to " + newQuantity); } else { - newQuantity = currentQuantity + quantity; + newQuantity = currentQuantity + shopItem.getQuantity(); } // Update or insert the item in the database @@ -149,7 +154,6 @@ public static void addItem(UUID uuid, Material material, long quantity, double p } } - public static void removeItem(UUID uuid, Material material, long quantity) { SQLHelper sqlHelper = privateMines.getSqlHelper(); MineStorage mineStorage = privateMines.getMineStorage(); @@ -212,10 +216,10 @@ public static void removeItem(UUID uuid, Material material, long quantity) { } } - public Map getShopItems(UUID uuid) { + public List getShopItems(UUID uuid) { SQLHelper sqlHelper = privateMines.getSqlHelper(); String ownerUUID = uuid.toString(); - Map shopItems = new HashMap<>(); + List shopItems = new ArrayList<>(); Results results = sqlHelper.queryResults("SELECT * FROM shops WHERE owner=?;", ownerUUID); @@ -227,10 +231,13 @@ public Map getShopItems(UUID uuid) { results.forEach(result -> { String item = result.getString(3); long quantity = result.getLong(4); + double price = result.get(5); + double tax = result.get(6); Material material = Material.getMaterial(item); if (material != null) { - shopItems.put(material, quantity); + ShopItem shopItem = new ShopItem(material, quantity, price, tax); + shopItems.add(shopItem); } else { Bukkit.broadcastMessage("Invalid material: " + item); } @@ -266,6 +273,8 @@ public void sellItems(UUID uuid, boolean includeTax) { Bukkit.getPlayer(uuid).sendMessage("You earnt $" + earnerAmount); Bukkit.getPlayer(uuid).sendMessage("You paid $" + taxAmount + " in taxes.."); + + shopItems.put(Material.getMaterial(item), (long) taxAmount); } }); } From 51f412a706f822a4c7879875bc7194ff8b4b53b1 Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Sun, 28 Jul 2024 22:25:24 +0100 Subject: [PATCH 37/43] Tidied up PlayerShopListener --- .../privatemines/listener/sell/PlayerShopListener.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/main/java/me/untouchedodin0/privatemines/listener/sell/PlayerShopListener.java b/src/main/java/me/untouchedodin0/privatemines/listener/sell/PlayerShopListener.java index f4c733d2..1f2981aa 100644 --- a/src/main/java/me/untouchedodin0/privatemines/listener/sell/PlayerShopListener.java +++ b/src/main/java/me/untouchedodin0/privatemines/listener/sell/PlayerShopListener.java @@ -1,7 +1,5 @@ package me.untouchedodin0.privatemines.listener.sell; -import java.util.Map; -import java.util.Objects; import me.untouchedodin0.kotlin.mine.data.MineData; import me.untouchedodin0.kotlin.mine.storage.MineStorage; import me.untouchedodin0.privatemines.PrivateMines; @@ -9,10 +7,8 @@ import me.untouchedodin0.privatemines.playershops.Shop; import me.untouchedodin0.privatemines.playershops.ShopItem; import me.untouchedodin0.privatemines.playershops.ShopUtils; -import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; -import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; @@ -25,20 +21,16 @@ public class PlayerShopListener implements Listener { @EventHandler(priority = EventPriority.LOWEST) public void onBlockBreak(BlockBreakEvent event) { - Player player = event.getPlayer(); Location location = event.getBlock().getLocation(); Mine mine = mineStorage.getClosest(location); if (mine != null) { MineData mineData = mine.getMineData(); Shop shop = mineData.getShop(); - ShopUtils shopUtils = new ShopUtils(); Material material = location.getBlock().getType(); int quantity = event.getBlock().getDrops().size(); double price = shop != null ? shop.getPrice(material) : 0; ShopItem shopItem = new ShopItem(material, quantity, price, 0); ShopUtils.addItem(mineData.getMineOwner(), shopItem); -// ShopUtils.addItem(player.getUniqueId(), material, quantity, price); - //shopUtils.sellItems(player.getUniqueId(), true); } } } From b90d33692ccff953002c3091f7fc269c51c5b2aa Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Sun, 28 Jul 2024 22:25:59 +0100 Subject: [PATCH 38/43] Remove old code --- .../privatemines/commands/PlayerShopCommand.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java b/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java index b6570f3a..6aee03d4 100644 --- a/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java +++ b/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java @@ -16,7 +16,6 @@ import me.untouchedodin0.privatemines.playershops.PlayerShopMenuUtils; import me.untouchedodin0.privatemines.playershops.ShopItem; import me.untouchedodin0.privatemines.playershops.ShopUtils; -import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.entity.Player; @@ -63,10 +62,8 @@ public void pricesDebugAdd(Player player, long quantity, double price) { audienceUtils.sendMessage(player, MessagesConfig.dontOwnMine); } else { Material material = player.getInventory().getItemInMainHand().getType(); - Bukkit.broadcastMessage("" + material); ShopItem shopItem = new ShopItem(material, quantity, price, 0); ShopUtils.addItem(player.getUniqueId(), shopItem); -// ShopUtils.addItem(player.getUniqueId(), material, quantity, price); } } From 6134e7ee2f5a5730270a71c09dfd0af3a8ce5cdc Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Sun, 28 Jul 2024 22:27:40 +0100 Subject: [PATCH 39/43] Remove old code --- .../privatemines/commands/PlayerShopCommand.java | 1 - .../privatemines/playershops/PlayerShopMenuUtils.java | 6 +----- .../untouchedodin0/privatemines/playershops/ShopItem.java | 1 - .../untouchedodin0/privatemines/playershops/ShopUtils.java | 4 +--- 4 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java b/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java index 6aee03d4..0b5138e3 100644 --- a/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java +++ b/src/main/java/me/untouchedodin0/privatemines/commands/PlayerShopCommand.java @@ -29,7 +29,6 @@ public class PlayerShopCommand extends BaseCommand { @Default @CommandPermission("privatemines.playershop") public synchronized void playerShop(Player player) { - Map shopItems = new HashMap<>(); PlayerShopMenuUtils playerShopMenuUtils = new PlayerShopMenuUtils(); if (!mineStorage.hasMine(player)) { diff --git a/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java b/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java index 1e9abdc9..7130ca2f 100644 --- a/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java +++ b/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java @@ -19,8 +19,6 @@ public class PlayerShopMenuUtils { - PrivateMines privateMines = PrivateMines.getPrivateMines(); - // private Map shopItems; private List shopItems; private Map quantitySold; @@ -50,9 +48,7 @@ public void generateMenu(Player player) { ShopUtils.removeItem(player.getUniqueId(), shopItem.getItem(), shopItem.getQuantity()); }); shopItems.clear(); - quantitySold.forEach((material, aLong) -> { - player.sendMessage("Sold " + aLong + "x " + material.name()); - }); + quantitySold.forEach((material, aLong) -> player.sendMessage("Sold " + aLong + "x " + material.name())); List items = paginatedGui.getPageItems().stream().filter(item -> { String itemName = Objects.requireNonNull(ChatColor.stripColor( diff --git a/src/main/java/me/untouchedodin0/privatemines/playershops/ShopItem.java b/src/main/java/me/untouchedodin0/privatemines/playershops/ShopItem.java index 28b1f71c..6aac1004 100644 --- a/src/main/java/me/untouchedodin0/privatemines/playershops/ShopItem.java +++ b/src/main/java/me/untouchedodin0/privatemines/playershops/ShopItem.java @@ -1,6 +1,5 @@ package me.untouchedodin0.privatemines.playershops; -import java.util.UUID; import org.bukkit.Material; public class ShopItem { diff --git a/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java b/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java index bef5a1d3..db167868 100644 --- a/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java +++ b/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java @@ -1,9 +1,7 @@ package me.untouchedodin0.privatemines.playershops; -import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; -import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -150,7 +148,7 @@ public static void addItem(UUID uuid, ShopItem shopItem) { } }); } else { - Bukkit.getLogger().warning("Mine not found for UUID: " + uuid.toString()); + Bukkit.getLogger().warning("Mine not found for UUID: " + uuid); } } From 0eab4b7fd6c93ef46a4bcc694c32a9c4289e1da8 Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Sun, 28 Jul 2024 22:29:35 +0100 Subject: [PATCH 40/43] dev mode activated! --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9a2bd396..7ecc9301 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ group 'me.untouchedodin0.privatemines' version = pluginVersion -def devBuild = false +def devBuild = true if (devBuild) { version = version + "-DEV" From 19660afae7e7e362300b767a420040d9438412dd Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Mon, 29 Jul 2024 21:25:06 +0100 Subject: [PATCH 41/43] Remove some debug stuff --- .../listener/sell/PlayerShopListener.java | 1 + .../playershops/PlayerShopMenuUtils.java | 19 +------------------ .../privatemines/playershops/ShopUtils.java | 15 --------------- 3 files changed, 2 insertions(+), 33 deletions(-) diff --git a/src/main/java/me/untouchedodin0/privatemines/listener/sell/PlayerShopListener.java b/src/main/java/me/untouchedodin0/privatemines/listener/sell/PlayerShopListener.java index 1f2981aa..c6a96ad8 100644 --- a/src/main/java/me/untouchedodin0/privatemines/listener/sell/PlayerShopListener.java +++ b/src/main/java/me/untouchedodin0/privatemines/listener/sell/PlayerShopListener.java @@ -31,6 +31,7 @@ public void onBlockBreak(BlockBreakEvent event) { double price = shop != null ? shop.getPrice(material) : 0; ShopItem shopItem = new ShopItem(material, quantity, price, 0); ShopUtils.addItem(mineData.getMineOwner(), shopItem); + event.setDropItems(false); } } } diff --git a/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java b/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java index 7130ca2f..dc719b69 100644 --- a/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java +++ b/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java @@ -10,7 +10,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import me.untouchedodin0.privatemines.PrivateMines; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.ChatColor; @@ -51,27 +50,11 @@ public void generateMenu(Player player) { quantitySold.forEach((material, aLong) -> player.sendMessage("Sold " + aLong + "x " + material.name())); List items = paginatedGui.getPageItems().stream().filter(item -> { - String itemName = Objects.requireNonNull(ChatColor.stripColor( - Objects.requireNonNull(item.getItemStack().getItemMeta()).getDisplayName())); + String itemName = Objects.requireNonNull(item.getItemStack().getItemMeta()).getDisplayName(); return !(itemName.equals("Previous") || itemName.equals("Sell All") || itemName.equals( "Next")); }).toList(); - // Send a message with sold quantities -// if (!quantitySold.isEmpty()) { -// StringBuilder soldMessage = new StringBuilder(ChatColor.GREEN + "Sold items: "); -// quantitySold.forEach( -// (material, quantity) -> soldMessage.append(material.name()).append(" x") -// .append(quantity).append(", ")); -// // Remove trailing comma and space -// if (soldMessage.length() > 2) { -// soldMessage.setLength(soldMessage.length() - 2); -// } -// -// -// player.sendMessage(soldMessage.toString()); -// } - refreshGui(player, paginatedGui); })); diff --git a/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java b/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java index db167868..04ce4f9e 100644 --- a/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java +++ b/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java @@ -89,8 +89,6 @@ public static void addItem(UUID uuid, ShopItem shopItem) { double taxRate = mineData.getTax() / 100.0; double finalPrice = shopItem.getPrice() * (1 + taxRate); - Bukkit.broadcastMessage("material name " + materialName); - Task.asyncDelayed(() -> { try { // Check if the item already exists @@ -116,7 +114,6 @@ public static void addItem(UUID uuid, ShopItem shopItem) { if (shopItem.getQuantity() > 0 && (Long.MAX_VALUE - currentQuantity < shopItem.getQuantity())) { // Overflow detected newQuantity = Long.MAX_VALUE; // Cap to max value - Bukkit.broadcastMessage("Overflow detected. Quantity capped to " + newQuantity); } else { newQuantity = currentQuantity + shopItem.getQuantity(); } @@ -138,10 +135,6 @@ public static void addItem(UUID uuid, ShopItem shopItem) { statement.executeUpdate(); } - - Bukkit.broadcastMessage( - "Updated item: " + materialName + " with new quantity: " + newQuantity); - } catch (Exception e) { Bukkit.getLogger().severe("Error updating item: " + e.getMessage()); e.printStackTrace(); @@ -189,15 +182,10 @@ public static void removeItem(UUID uuid, Material material, long quantity) { // Update the quantity in the database String updateQuery = "UPDATE shops SET quantity = ? WHERE owner = ? AND item = ?"; sqlHelper.executeUpdate(updateQuery, newQuantity, ownerUUID, materialName); - - Bukkit.broadcastMessage( - "Updated item: " + materialName + " with new quantity: " + newQuantity); break; // Exit the retry loop on success } catch (Exception e) { // Log the exception and retry if needed - Bukkit.broadcastMessage("Error handling item removal: " + materialName); - if (attempt < maxRetries) { try { Thread.sleep(retryDelay); // Wait before retrying @@ -222,7 +210,6 @@ public List getShopItems(UUID uuid) { Results results = sqlHelper.queryResults("SELECT * FROM shops WHERE owner=?;", ownerUUID); if (results == null) { - Bukkit.broadcastMessage("No results found."); return null; } @@ -236,8 +223,6 @@ public List getShopItems(UUID uuid) { if (material != null) { ShopItem shopItem = new ShopItem(material, quantity, price, tax); shopItems.add(shopItem); - } else { - Bukkit.broadcastMessage("Invalid material: " + item); } }); return shopItems; From 9a84d1556e3c8c89538f3ad65cc04f6f7d6c4a08 Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Tue, 30 Jul 2024 22:10:03 +0100 Subject: [PATCH 42/43] Fixes for shops --- .../playershops/PlayerShopMenuUtils.java | 34 +++++------------ .../privatemines/playershops/ShopUtils.java | 37 ++++++++++++++----- 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java b/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java index dc719b69..e6483f93 100644 --- a/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java +++ b/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java @@ -15,6 +15,7 @@ import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; public class PlayerShopMenuUtils { @@ -46,16 +47,18 @@ public void generateMenu(Player player) { quantitySold.put(shopItem.getItem(), shopItem.getQuantity()); ShopUtils.removeItem(player.getUniqueId(), shopItem.getItem(), shopItem.getQuantity()); }); - shopItems.clear(); - quantitySold.forEach((material, aLong) -> player.sendMessage("Sold " + aLong + "x " + material.name())); + quantitySold.forEach( + (material, aLong) -> player.sendMessage("Sold " + aLong + "x " + material.name())); List items = paginatedGui.getPageItems().stream().filter(item -> { - String itemName = Objects.requireNonNull(item.getItemStack().getItemMeta()).getDisplayName(); + String itemName = Objects.requireNonNull(item.getItemStack().getItemMeta()) + .getDisplayName(); return !(itemName.equals("Previous") || itemName.equals("Sell All") || itemName.equals( "Next")); }).toList(); refreshGui(player, paginatedGui); + shopUtils.clearShopItems(player.getUniqueId()); })); // Next item @@ -80,14 +83,13 @@ public void generateMenu(Player player) { shopItems.remove(material); // Remove item from database -// ShopUtils.removeItem(player.getUniqueId(), material, quantity); + ShopUtils.removeItem(player.getUniqueId(), material, quantity); // Send a message about the sold item player.sendMessage( ChatColor.GREEN + "Sold item: " + material.name() + " x" + quantity); - // Refresh the menu - refreshGui(player, paginatedGui); + paginatedGui.updatePageItem(event.getSlot(), new ItemStack(Material.AIR)); }); paginatedGui.addItem(guiItem); } @@ -125,23 +127,5 @@ private void refreshGui(Player player, PaginatedGui paginatedGui) { }); } } -// shopItems.forEach((material, quantity) -> { -// GuiItem guiItem = ItemBuilder.from(material).name(Component.text("Item: " + material.name())) -// .lore(List.of(Component.text("Quantity: " + quantity), Component.text(" "), -// Component.text("Click to sell all!"))).asGuiItem(event -> { -// quantitySold.put(material, quantity); // Update quantitySold map -// -// // Remove the item from shopItems -// shopItems.remove(material); -// -// // Remove item from database -// ShopUtils.removeItem(player.getUniqueId(), material, quantity); -// -// // Send a message about the sold item -// player.sendMessage(ChatColor.GREEN + "Sold item: " + material.name() + " x" + quantity); -// -// // Refresh the menu -// refreshGui(player, paginatedGui); -// }); -// paginatedGui.addItem(guiItem); + diff --git a/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java b/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java index 04ce4f9e..250867a0 100644 --- a/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java +++ b/src/main/java/me/untouchedodin0/privatemines/playershops/ShopUtils.java @@ -122,18 +122,29 @@ public static void addItem(UUID uuid, ShopItem shopItem) { String updateQuery; if (itemExists) { updateQuery = "UPDATE shops SET quantity = ?, price = ? WHERE owner = ? AND item = ?"; - } else { - updateQuery = "INSERT INTO shops (owner, item, quantity, price) VALUES (?, ?, ?, ?)"; - } - try (PreparedStatement statement = sqlHelper.getConnection() - .prepareStatement(updateQuery)) { - statement.setLong(1, newQuantity); - statement.setDouble(2, finalPrice); - statement.setString(3, ownerUUID); - statement.setString(4, materialName); + try (PreparedStatement statement = sqlHelper.getConnection() + .prepareStatement(updateQuery)) { + statement.setLong(1, newQuantity); + statement.setDouble(2, finalPrice); + statement.setString(3, ownerUUID); + statement.setString(4, materialName); + + statement.executeUpdate(); + } + } else { + updateQuery = "INSERT INTO shops (owner, seller, item, quantity, price, tax) VALUES (?, ?, ?, ?, ?, ?)"; - statement.executeUpdate(); + try (PreparedStatement statement = sqlHelper.getConnection() + .prepareStatement(updateQuery)) { + statement.setString(1, ownerUUID); + statement.setString(2, ownerUUID); + statement.setString(3, materialName); + statement.setLong(4, newQuantity); + statement.setDouble(5, finalPrice); + statement.setDouble(6, taxRate); + statement.executeUpdate(); + } } } catch (Exception e) { Bukkit.getLogger().severe("Error updating item: " + e.getMessage()); @@ -228,6 +239,12 @@ public List getShopItems(UUID uuid) { return shopItems; } + public void clearShopItems(UUID uuid) { + SQLHelper sqlHelper = privateMines.getSqlHelper(); + String ownerUUID = uuid.toString(); + sqlHelper.executeUpdate("DELETE FROM shops WHERE owner=?;", ownerUUID); + } + public void sellItems(UUID uuid, boolean includeTax) { SQLHelper sqlHelper = privateMines.getSqlHelper(); String ownerUUID = uuid.toString(); From 26b37a5187d341ebbcc2a4c240b3f4ba22b9b645 Mon Sep 17 00:00:00 2001 From: UntouchedOdin0 Date: Mon, 21 Oct 2024 22:52:27 +0100 Subject: [PATCH 43/43] Latest code. --- build.gradle | 2 +- .../privatemines/PrivateMines.java | 29 +------ .../privatemines/mine/Mine.java | 6 +- .../playershops/PlayerShopMenuUtils.java | 46 +++++++++- .../privatemines/storage/sql/SQLUtils.java | 22 +++-- .../utils/regions/RegionUtils.java | 85 ++++++++++++++++++- 6 files changed, 147 insertions(+), 43 deletions(-) diff --git a/build.gradle b/build.gradle index 7ecc9301..8e805c70 100644 --- a/build.gradle +++ b/build.gradle @@ -72,7 +72,7 @@ dependencies { implementation 'co.aikar:acf-paper:0.5.1-SNAPSHOT' implementation 'org.apache.httpcomponents:httpclient:4.5.14' implementation "dev.triumphteam:triumph-gui:3.1.2" // Replace version here - implementation 'com.convallyria.languagy:api:3.0.2' + implementation 'de.rapha149.signgui:signgui:2.3.2' implementation files('libs/PrivateMinesAPI-1.3-SNAPSHOT.jar') implementation files('libs/Ocelot-API-1.0.0.jar') diff --git a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java index 802c65fb..128bec41 100644 --- a/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java +++ b/src/main/java/me/untouchedodin0/privatemines/PrivateMines.java @@ -22,6 +22,7 @@ package me.untouchedodin0.privatemines; import co.aikar.commands.BukkitCommandManager; +import co.aikar.commands.CommandManager; import java.io.File; import java.io.IOException; import java.nio.file.FileSystems; @@ -114,18 +115,6 @@ public static PrivateMines getPrivateMines() { @Override public void onEnable() { - System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); - System.out.println(" "); - System.out.println("Hello, this a warning message"); - System.out.println("to inform you that you're using"); - System.out.println("a heavily in development version."); - System.out.println("I do not take any responsibility"); - System.out.println("for this being used on production"); - System.out.println("and breaking stuff."); - System.out.println("YOU HAVE BEEN WARNED."); - System.out.println(" "); - System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); - Instant start = Instant.now(); getLogger().info("Loading Private Mines v" + getDescription().getVersion()); saveDefaultConfig(); @@ -223,6 +212,7 @@ public void onEnable() { }); BukkitCommandManager bukkitCommandManager = new BukkitCommandManager(this); + bukkitCommandManager.enableUnstableAPI("help"); bukkitCommandManager.registerCommand(new PrivateMinesCommand()); bukkitCommandManager.registerCommand(new PublicMinesCommand()); @@ -287,7 +277,7 @@ PRIMARY KEY (owner, item) sqlHelper.setAutoCommit(true); Task.asyncDelayed(this::loadMines); - Task.asyncDelayed(this::loadShops); +// Task.asyncDelayed(this::loadShops); Task.asyncDelayed(SQLUtils::loadPregens); Task.asyncDelayed(this::loadAddons); @@ -400,11 +390,6 @@ public void loadShops() { SQLHelper sqlHelper = getSqlHelper(); Results results = sqlHelper.queryResults("SELECT * FROM shops"); - privateMines.getLogger().info("------"); - privateMines.getLogger().info("- load shops -"); - privateMines.getLogger().info("sql helper " + sqlHelper); - privateMines.getLogger().info("results " + results); - results.forEach(result -> { String owner = result.getString(1); String seller = result.getString(2); @@ -412,14 +397,6 @@ public void loadShops() { int quantity = result.get(4); double price = result.get(5); double tax = result.get(6); - - privateMines.getLogger().info("result " + result); - privateMines.getLogger().info("owner " + owner); - privateMines.getLogger().info("seller"); - privateMines.getLogger().info("item " + item); - privateMines.getLogger().info("quantity " + quantity); - privateMines.getLogger().info("price " + price); - privateMines.getLogger().info("tax " + tax); }); } diff --git a/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java b/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java index 6856122d..5a781b55 100644 --- a/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java +++ b/src/main/java/me/untouchedodin0/privatemines/mine/Mine.java @@ -67,6 +67,8 @@ import me.untouchedodin0.privatemines.storage.sql.SQLUtils; import me.untouchedodin0.privatemines.utils.ExpansionUtils; import me.untouchedodin0.privatemines.utils.Utils; +import me.untouchedodin0.privatemines.utils.regions.RegionUtils; +import me.untouchedodin0.privatemines.utils.regions.RegionUtils.SplitMode; import me.untouchedodin0.privatemines.utils.world.MineWorldManager; import me.untouchedodin0.privatemines.utils.worldedit.PasteHelper; import me.untouchedodin0.privatemines.utils.worldedit.objects.PastedMine; @@ -512,7 +514,7 @@ public void handleReset() { } } - if (percentageTask == null) { +/* if (percentageTask == null) { //Create a new Bukkit task async this.percentageTask = Task.syncRepeating(() -> { double percentage = getPercentage(); @@ -521,7 +523,7 @@ public void handleReset() { handleReset(); } }, 0, 80); - } + }*/ if (owner != null) { audienceUtils.sendMessage(owner, MessagesConfig.mineReset); } diff --git a/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java b/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java index e6483f93..ed292616 100644 --- a/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java +++ b/src/main/java/me/untouchedodin0/privatemines/playershops/PlayerShopMenuUtils.java @@ -2,17 +2,21 @@ import static me.untouchedodin0.privatemines.utils.Utils.format; +import de.rapha149.signgui.SignGUI; import dev.triumphteam.gui.builder.item.ItemBuilder; import dev.triumphteam.gui.guis.Gui; import dev.triumphteam.gui.guis.GuiItem; import dev.triumphteam.gui.guis.PaginatedGui; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; import org.bukkit.ChatColor; +import org.bukkit.DyeColor; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -74,10 +78,46 @@ public void generateMenu(Player player) { GuiItem guiItem = ItemBuilder.from(material).name(Component.text(format(material))).lore( List.of( Component.text("Quantity: " + shopItem.getQuantity()).color(NamedTextColor.GRAY), - Component.text("Price: " + price), Component.text(" "), + Component.text("Price: " + price), + Component.text(" "), + Component.text("Right-Click to set price"), Component.text("Click to sell all!").color(NamedTextColor.GREEN))) .asGuiItem(event -> { - quantitySold.put(material, quantity); // Update quantitySold map + + switch (event.getClick()) { + case LEFT -> { + player.sendMessage("selling all items lol"); + quantitySold.put(material, quantity); // Update quantitySold map + + // Remove the item from shopItems + shopItems.remove(material); + + // Remove item from database + ShopUtils.removeItem(player.getUniqueId(), material, quantity); + + // Send a message about the sold item + player.sendMessage( + ChatColor.GREEN + "Sold item: " + material.name() + " x" + quantity); + + paginatedGui.updatePageItem(event.getSlot(), new ItemStack(Material.AIR)); + } + case SHIFT_RIGHT -> { + SignGUI gui = SignGUI.builder() + .setLine(0, "Enter Price") + .setType(Material.SIGN) + .setColor(DyeColor.YELLOW) + .setHandler(((player1, result) -> { + + String line0 = result.getLine(0); + String[] lines = result.getLines(); + + Bukkit.broadcastMessage("line0 " + line0); + Bukkit.broadcastMessage("lines " + lines); + return Collections.emptyList(); + })).build(); + } + } +/* quantitySold.put(material, quantity); // Update quantitySold map // Remove the item from shopItems shopItems.remove(material); @@ -89,7 +129,7 @@ public void generateMenu(Player player) { player.sendMessage( ChatColor.GREEN + "Sold item: " + material.name() + " x" + quantity); - paginatedGui.updatePageItem(event.getSlot(), new ItemStack(Material.AIR)); + paginatedGui.updatePageItem(event.getSlot(), new ItemStack(Material.AIR));*/ }); paginatedGui.addItem(guiItem); } diff --git a/src/main/java/me/untouchedodin0/privatemines/storage/sql/SQLUtils.java b/src/main/java/me/untouchedodin0/privatemines/storage/sql/SQLUtils.java index 4ff09c97..a85ad518 100644 --- a/src/main/java/me/untouchedodin0/privatemines/storage/sql/SQLUtils.java +++ b/src/main/java/me/untouchedodin0/privatemines/storage/sql/SQLUtils.java @@ -1,6 +1,5 @@ package me.untouchedodin0.privatemines.storage.sql; -import com.google.common.util.concurrent.AtomicDouble; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; @@ -17,7 +16,6 @@ import me.untouchedodin0.privatemines.utils.world.MineWorldManager; import org.bukkit.Bukkit; import org.bukkit.Location; -import org.bukkit.Material; import redempt.redlib.misc.LocationUtils; import redempt.redlib.misc.Task; import redempt.redlib.sql.SQLHelper; @@ -32,16 +30,22 @@ public class SQLUtils { public static Location getCurrentLocation() { MineWorldManager mineWorldManager = PrivateMines.getPrivateMines().getMineWorldManager(); SQLHelper sqlHelper = privateMines.getSqlHelper(); - Results results = sqlHelper.queryResults("SELECT * FROM privatemines;"); - var ref = new Object() { - Location location = null; - }; - if (results.isEmpty()) { + if (sqlHelper == null) { + privateMines.getLogger().warning("sqlHelper is null in SQLUtils."); return mineWorldManager.getDefaultLocation(); } else { - results.forEach(results1 -> ref.location = LocationUtils.fromString(results1.getString(3))); - return ref.location; + Results results = sqlHelper.queryResults("SELECT * FROM privatemines;"); + var ref = new Object() { + Location location = null; + }; + + if (results.isEmpty()) { + return mineWorldManager.getDefaultLocation(); + } else { + results.forEach(results1 -> ref.location = LocationUtils.fromString(results1.getString(3))); + return ref.location; + } } } diff --git a/src/main/java/me/untouchedodin0/privatemines/utils/regions/RegionUtils.java b/src/main/java/me/untouchedodin0/privatemines/utils/regions/RegionUtils.java index 7d7e357f..6c16cd90 100644 --- a/src/main/java/me/untouchedodin0/privatemines/utils/regions/RegionUtils.java +++ b/src/main/java/me/untouchedodin0/privatemines/utils/regions/RegionUtils.java @@ -4,10 +4,18 @@ import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Region; +import java.util.ArrayList; +import java.util.List; + public class RegionUtils { - public static com.sk89q.worldedit.regions.Region[] getSubRegions( - com.sk89q.worldedit.regions.Region region) { + // Enum to specify the split mode (NUMBER or SIZE) + public enum SplitMode { + NUMBER, SIZE + } + + public static Region[] getSubRegions( + Region region) { int midX = (region.getMinimumPoint().getX() + region.getMaximumPoint().getX()) / 2; int midY = (region.getMinimumPoint().getY() + region.getMaximumPoint().getY()) / 2; int midZ = (region.getMinimumPoint().getZ() + region.getMaximumPoint().getZ()) / 2; @@ -21,4 +29,77 @@ public static com.sk89q.worldedit.regions.Region[] getSubRegions( BlockVector3.at(region.getMinimumPoint().getX(), region.getMinimumPoint().getY(), midZ + 1), region.getMaximumPoint())}; } + + public static Region[] splitRegions(Region region, int value, SplitMode mode) { + int minX = region.getMinimumPoint().getX(); + int minY = region.getMinimumPoint().getY(); + int minZ = region.getMinimumPoint().getZ(); + + int maxX = region.getMaximumPoint().getX(); + int maxY = region.getMaximumPoint().getY(); + int maxZ = region.getMaximumPoint().getZ(); + + // Calculate total size of the region + int totalX = maxX - minX + 1; + int totalY = maxY - minY + 1; + int totalZ = maxZ - minZ + 1; + + List subRegions = new ArrayList<>(); + + // Determine the number of splits in each dimension based on the mode + int splitsX, splitsY, splitsZ; + + if (mode == SplitMode.NUMBER) { + // Assuming a cubic distribution of regions for a roughly equal split + splitsX = (int) Math.ceil(Math.cbrt(value)); + splitsY = (int) Math.ceil(Math.cbrt(value)); + splitsZ = (int) Math.ceil(Math.cbrt(value)); + + // Ensure the total number of splits + while (splitsX * splitsY * splitsZ < value) { + // Increment splits in Z until we have enough regions + if (splitsZ < splitsY || splitsZ < splitsX) { + splitsZ++; + } else if (splitsY < splitsX) { + splitsY++; + } else { + splitsX++; + } + } + + } else if (mode == SplitMode.SIZE) { + splitsX = splitsY = splitsZ = value; + } else { + throw new IllegalArgumentException("Invalid split mode"); + } + + // Calculate sizes for each subregion based on splits + int sizeX = (int) Math.ceil((double) totalX / splitsX); + int sizeY = (int) Math.ceil((double) totalY / splitsY); + int sizeZ = (int) Math.ceil((double) totalZ / splitsZ); + + // Divide the region into subregions + for (int x = minX; x <= maxX; x += sizeX) { + for (int y = minY; y <= maxY; y += sizeY) { + for (int z = minZ; z <= maxZ; z += sizeZ) { + // Define the minimum point of the subregion (current iteration point) + BlockVector3 minPoint = BlockVector3.at(x, y, z); + + // Calculate the maximum point of the subregion, ensuring we don't exceed the original region's boundaries + int newMaxX = Math.min(x + sizeX - 1, maxX); + int newMaxY = Math.min(y + sizeY - 1, maxY); + int newMaxZ = Math.min(z + sizeZ - 1, maxZ); + + BlockVector3 maxPoint = BlockVector3.at(newMaxX, newMaxY, newMaxZ); + + // Create the subregion and add it to the list + subRegions.add(new CuboidRegion(minPoint, maxPoint)); + } + } + } + + // Return the subregions as an array + return subRegions.toArray(new Region[0]); + } } +