From 36ffadd27bd9490e86db1e93d7f3041b6386078d Mon Sep 17 00:00:00 2001 From: Michael Burgess Date: Mon, 10 Feb 2025 14:46:05 -0500 Subject: [PATCH 01/14] Update to 1.21.4 --- README.md | 2 +- pom.xml | 12 +- .../RealEstate/ClaimPermissionListener.java | 120 +++++++++++------- src/me/EtienneDx/RealEstate/Config.java | 20 ++- src/me/EtienneDx/RealEstate/REListener.java | 6 +- src/me/EtienneDx/RealEstate/RealEstate.java | 4 +- .../RealEstate/Transactions/ClaimLease.java | 22 +++- .../RealEstate/Transactions/ClaimRent.java | 35 +++-- .../RealEstate/Transactions/ClaimSell.java | 22 +++- .../RealEstate/Transactions/Transaction.java | 25 ++-- 10 files changed, 176 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 4d7e442..c73fa11 100644 --- a/README.md +++ b/README.md @@ -10,4 +10,4 @@ Please feel free to report any issue in the [GitHub Issue section](https://githu This plugin is dependent on GriefPrevention version 16.18 and up. -GriefPrevention plugin can be found [here](https://github.com/TechFortress/GriefPrevention). \ No newline at end of file +GriefPrevention plugin can be found [here](https://github.com/GriefPrevention/GriefPrevention). \ No newline at end of file diff --git a/pom.xml b/pom.xml index 4f1c245..ea4e513 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 Me.EtienneDx real-estate - 1.4.1 + 1.4.2 RealEstate A spigot plugin for selling, renting and leasing GriefPrevention claims @@ -131,7 +131,7 @@ org.spigotmc spigot-api - 1.18.1-R0.1-SNAPSHOT + 1.21.4-R0.1-SNAPSHOT provided @@ -143,18 +143,18 @@ co.aikar acf-bukkit - 0.5.0-SNAPSHOT + 0.5.1-SNAPSHOT net.ess3 EssentialsX - 2.16.1 + 2.17.2 provided - com.github.TechFortress + com.github.GriefPrevention GriefPrevention - 16.18 + 16.18.4 provided diff --git a/src/me/EtienneDx/RealEstate/ClaimPermissionListener.java b/src/me/EtienneDx/RealEstate/ClaimPermissionListener.java index a016bba..52bae3d 100644 --- a/src/me/EtienneDx/RealEstate/ClaimPermissionListener.java +++ b/src/me/EtienneDx/RealEstate/ClaimPermissionListener.java @@ -19,53 +19,81 @@ void registerEvents() pm.registerEvents(this, RealEstate.instance); } - @EventHandler - public void onClaimPermission(ClaimPermissionCheckEvent event) { - Transaction transaction = RealEstate.transactionsStore.getTransaction(event.getClaim()); - // we only have to remove the owner's access, the rest is handled by GP - if( - // if there is a transaction and the player is the owner - transaction != null && - ( - event.getCheckedUUID().equals(transaction.getOwner()) || - (event.getClaim().isAdminClaim() && event.getCheckedPlayer().hasPermission("griefprevention.adminclaims")) - ) && - transaction instanceof BoughtTransaction && - ((BoughtTransaction)transaction).getBuyer() != null - ) { - switch(event.getRequiredPermission()) { - case Edit: - event.setDenialReason(() -> Messages.getMessage(RealEstate.instance.messages.msgErrorClaimInTransactionCantEdit)); - break; - case Access: - event.setDenialReason(() -> Messages.getMessage(RealEstate.instance.messages.msgErrorClaimInTransactionCantAccess)); - break; - case Build: - event.setDenialReason(() -> Messages.getMessage(RealEstate.instance.messages.msgErrorClaimInTransactionCantBuild)); - break; - case Inventory: - event.setDenialReason(() -> Messages.getMessage(RealEstate.instance.messages.msgErrorClaimInTransactionCantInventory)); - break; - case Manage: - event.setDenialReason(() -> Messages.getMessage(RealEstate.instance.messages.msgErrorClaimInTransactionCantManage)); - break; - default: - break; - } - } + @EventHandler + public void onClaimPermission(ClaimPermissionCheckEvent event) { + if (event == null || event.getClaim() == null || event.getDenialReason() == null) { + return; + } + + // Retrieve the active transaction for this claim + Transaction transaction = RealEstate.transactionsStore.getTransaction(event.getClaim()); + + // Ensure transaction exists and player is valid + if (transaction == null || event.getCheckedPlayer() == null || event.getCheckedUUID() == null) { + return; + } + + // Debugging: Log transaction type + RealEstate.instance.getLogger().info("Transaction found: " + transaction.getClass().getSimpleName()); + + // Check if the player is the owner or has admin permissions + boolean isOwner = event.getCheckedUUID().equals(transaction.getOwner()); + boolean isAdmin = event.getClaim().isAdminClaim() && event.getCheckedPlayer().hasPermission("griefprevention.adminclaims"); + + if (RealEstate.instance.config.DebugMode) { + RealEstate.instance.getLogger().info("isOwner: " + isOwner); + RealEstate.instance.getLogger().info("isAdmin: " + isAdmin); + RealEstate.instance.getLogger().info("isAdminClaim: " + event.getClaim().isAdminClaim()); + RealEstate.instance.getLogger().info("Player has GP.AdminClaims: " + event.getCheckedPlayer().hasPermission("griefprevention.adminclaims")); + RealEstate.instance.getLogger().info("isRental: " + transaction.isRental()); + RealEstate.instance.getLogger().info("User Who Triggered: " + event.getCheckedUUID()); + RealEstate.instance.getLogger().info("Reason for Denial: " + event.getDenialReason()); + } + + // Ensure it's a valid "BoughtTransaction" with an actual buyer, but NOT a rental + if ((isOwner || isAdmin) && transaction instanceof BoughtTransaction && !transaction.isRental()) { + BoughtTransaction boughtTransaction = (BoughtTransaction) transaction; + if (boughtTransaction.getBuyer() != null) { + switch (event.getRequiredPermission()) { + case Edit: + event.setDenialReason(() -> Messages.getMessage(RealEstate.instance.messages.msgErrorClaimInTransactionCantEdit)); + break; + case Access: + event.setDenialReason(() -> Messages.getMessage(RealEstate.instance.messages.msgErrorClaimInTransactionCantAccess)); + break; + case Build: + event.setDenialReason(() -> Messages.getMessage(RealEstate.instance.messages.msgErrorClaimInTransactionCantBuild)); + break; + case Inventory: + event.setDenialReason(() -> Messages.getMessage(RealEstate.instance.messages.msgErrorClaimInTransactionCantInventory)); + break; + case Manage: + event.setDenialReason(() -> Messages.getMessage(RealEstate.instance.messages.msgErrorClaimInTransactionCantManage)); + break; + default: + break; + } + } + } + + // Check if permission is Edit or Manage and deny access to subclaims in transactions + if (event.getRequiredPermission() == ClaimPermission.Edit || event.getRequiredPermission() == ClaimPermission.Manage) { + for (Claim child : event.getClaim().children) { + if (child == null) continue; // Avoid potential null references + + Transaction childTransaction = RealEstate.transactionsStore.getTransaction(child); + if (childTransaction instanceof BoughtTransaction && !childTransaction.isRental()) { + BoughtTransaction boughtTransaction = (BoughtTransaction) childTransaction; + if (boughtTransaction.getBuyer() != null) { + event.setDenialReason(() -> Messages.getMessage(RealEstate.instance.messages.msgErrorSubclaimInTransaction)); + return; // Stop further checks after setting a denial reason + } + } + } + } + } + - if(event.getRequiredPermission() == ClaimPermission.Edit || event.getRequiredPermission() == ClaimPermission.Manage) { - for (Claim child : event.getClaim().children) { - Transaction tr = RealEstate.transactionsStore.getTransaction(child); - if(tr != null && - tr instanceof BoughtTransaction && - ((BoughtTransaction)tr).getBuyer() != null - ) { - event.setDenialReason(() -> Messages.getMessage(RealEstate.instance.messages.msgErrorSubclaimInTransaction)); - } - } - } - } // more of a safety measure, normally it shouldn't be needed @EventHandler diff --git a/src/me/EtienneDx/RealEstate/Config.java b/src/me/EtienneDx/RealEstate/Config.java index f57d493..6d7e716 100644 --- a/src/me/EtienneDx/RealEstate/Config.java +++ b/src/me/EtienneDx/RealEstate/Config.java @@ -11,7 +11,7 @@ import me.EtienneDx.AnnotationConfig.ConfigField; import me.EtienneDx.AnnotationConfig.ConfigFile; -@ConfigFile(header = "RealEstate wiki and newest versions are available at http://www.github.com/EtienneDx/RealEstate") +@ConfigFile(header = "RealEstate wiki and newest versions are available at https://github.com/msburgess3200/RealEstate") public class Config extends AnnotationConfig { public PluginDescriptionFile pdf; @@ -102,6 +102,24 @@ public class Config extends AnnotationConfig @ConfigField(name="RealEstate.Settings.MessagesFiles", comment="Language file to be used. You can see all languages files in the languages directory. If the language file does not exist, it will be created and you'll be able to modify it later on.") public String languageFile = "en.yml"; + @ConfigField(name="RealEstate.Settings.DebugMode", comment="Enable/Disable Debug Logs") + public boolean DebugMode = true; + + @ConfigField(name="RealEstate.Settings.Database.Type", comment="Database Type: Possible values: Serial, MySQL, SQLite") + public String databaseType = "Serial"; + + @ConfigField(name="RealEstate.Settings.Database.Username", comment="Database Username (MySQL)") + public String Username = ""; + + @ConfigField(name="RealEstate.Settings.Database.Password", comment="Database Password (MySQL)") + public String Password = ""; + + @ConfigField(name="RealEstate.Settings.Database.DatabaseName", comment="Database Name (MySQL)") + public String DatabaseName = ""; + + @ConfigField(name="RealEstate.Settings.Database.Port", comment="Database Port (MySQL)") + public int Port = 3306; + public Config() { this.pdf = RealEstate.instance.getDescription(); diff --git a/src/me/EtienneDx/RealEstate/REListener.java b/src/me/EtienneDx/RealEstate/REListener.java index decec38..c356c47 100644 --- a/src/me/EtienneDx/RealEstate/REListener.java +++ b/src/me/EtienneDx/RealEstate/REListener.java @@ -6,6 +6,7 @@ import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.block.Sign; +import org.bukkit.block.sign.Side; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -399,9 +400,10 @@ public void onPlayerInteract(PlayerInteractEvent event) event.getClickedBlock().getState() instanceof Sign) { Sign sign = (Sign)event.getClickedBlock().getState(); - RealEstate.instance.log.info(sign.getLine(0)); + //RealEstate.instance.log.info(sign.getLine(0)); + RealEstate.instance.log.info(sign.getSide(Side.FRONT).getLine(0)); // it is a real estate sign - if(ChatColor.stripColor(sign.getLine(0)).equalsIgnoreCase(ChatColor.stripColor( + if(ChatColor.stripColor(sign.getSide(Side.FRONT).getLine(0)).equalsIgnoreCase(ChatColor.stripColor( Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)))) { Player player = event.getPlayer(); diff --git a/src/me/EtienneDx/RealEstate/RealEstate.java b/src/me/EtienneDx/RealEstate/RealEstate.java index 27136b8..d7fdcc9 100644 --- a/src/me/EtienneDx/RealEstate/RealEstate.java +++ b/src/me/EtienneDx/RealEstate/RealEstate.java @@ -54,7 +54,7 @@ public class RealEstate extends JavaPlugin public static TransactionsStore transactionsStore = null; - @SuppressWarnings("deprecation") + //@SuppressWarnings("deprecation") public void onEnable() { RealEstate.instance = this; @@ -110,7 +110,7 @@ public void onEnable() new ClaimPermissionListener().registerEvents(); manager = new BukkitCommandManager(this); - manager.enableUnstableAPI("help"); + registerConditions(); manager.registerCommand(new RECommand()); diff --git a/src/me/EtienneDx/RealEstate/Transactions/ClaimLease.java b/src/me/EtienneDx/RealEstate/Transactions/ClaimLease.java index 9a7e122..cb4ee38 100644 --- a/src/me/EtienneDx/RealEstate/Transactions/ClaimLease.java +++ b/src/me/EtienneDx/RealEstate/Transactions/ClaimLease.java @@ -11,6 +11,7 @@ import org.bukkit.Location; import org.bukkit.OfflinePlayer; import org.bukkit.block.Sign; +import org.bukkit.block.sign.Side; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -66,33 +67,40 @@ public boolean update() if(sign.getBlock().getState() instanceof Sign) { Sign s = (Sign)sign.getBlock().getState(); - s.setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); - s.setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceLease); + s.getSide(Side.FRONT).setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader)); + s.getSide(Side.FRONT).setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceLease); + //s.setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); + //s.setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceLease); //s.setLine(2, owner != null ? Bukkit.getOfflinePlayer(owner).getName() : "SERVER"); //s.setLine(2, paymentsLeft + "x " + price + " " + RealEstate.econ.currencyNamePlural()); if(RealEstate.instance.config.cfgUseCurrencySymbol) { if(RealEstate.instance.config.cfgUseDecimalCurrency == false) { - s.setLine(2, paymentsLeft + "x " + RealEstate.instance.config.cfgCurrencySymbol + " " + (int)Math.round(price)); + s.getSide(Side.FRONT).setLine(2, paymentsLeft + "x " + RealEstate.instance.config.cfgCurrencySymbol + " " + (int)Math.round(price)); + //s.setLine(2, paymentsLeft + "x " + RealEstate.instance.config.cfgCurrencySymbol + " " + (int)Math.round(price)); } else { - s.setLine(2, paymentsLeft + "x " + RealEstate.instance.config.cfgCurrencySymbol + " " + price); + s.getSide(Side.FRONT).setLine(2, paymentsLeft + "x " + RealEstate.instance.config.cfgCurrencySymbol + " " + price); + //s.setLine(2, paymentsLeft + "x " + RealEstate.instance.config.cfgCurrencySymbol + " " + price); } } else { if(RealEstate.instance.config.cfgUseDecimalCurrency == false) { - s.setLine(2, paymentsLeft + "x " + (int)Math.round(price) + " " + RealEstate.econ.currencyNamePlural()); + s.getSide(Side.FRONT).setLine(2, paymentsLeft + "x " + (int)Math.round(price) + " " + RealEstate.econ.currencyNamePlural()); + //s.setLine(2, paymentsLeft + "x " + (int)Math.round(price) + " " + RealEstate.econ.currencyNamePlural()); } else { - s.setLine(2, paymentsLeft + "x " + price + " " + RealEstate.econ.currencyNamePlural()); + s.getSide(Side.FRONT).setLine(2, paymentsLeft + "x " + price + " " + RealEstate.econ.currencyNamePlural()); + //s.setLine(2, paymentsLeft + "x " + price + " " + RealEstate.econ.currencyNamePlural()); } } - s.setLine(3, Utils.getTime(frequency, null, false)); + s.getSide(Side.FRONT).setLine(3, Utils.getTime(frequency, null, false)); + //s.setLine(3, Utils.getTime(frequency, null, false)); s.update(true); } else diff --git a/src/me/EtienneDx/RealEstate/Transactions/ClaimRent.java b/src/me/EtienneDx/RealEstate/Transactions/ClaimRent.java index 1d1c1f6..ed90bd3 100644 --- a/src/me/EtienneDx/RealEstate/Transactions/ClaimRent.java +++ b/src/me/EtienneDx/RealEstate/Transactions/ClaimRent.java @@ -11,6 +11,7 @@ import org.bukkit.Location; import org.bukkit.OfflinePlayer; import org.bukkit.block.Sign; +import org.bukkit.block.sign.Side; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -81,8 +82,11 @@ public boolean update() if(sign.getBlock().getState() instanceof Sign) { Sign s = (Sign) sign.getBlock().getState(); - s.setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); - s.setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceRent); + s.getSide(Side.FRONT).setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); + s.getSide(Side.FRONT).setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceRent); + + //s.setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); + //s.setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceRent); //s.setLine(2, owner != null ? Bukkit.getOfflinePlayer(owner).getName() : "SERVER"); String price_line = ""; if(RealEstate.instance.config.cfgUseCurrencySymbol) @@ -110,11 +114,17 @@ public boolean update() } String period = (maxPeriod > 1 ? maxPeriod + "x " : "") + Utils.getTime(duration, null, false); if(this.buildTrust) { - s.setLine(2, price_line); - s.setLine(3, period); + s.getSide(Side.FRONT).setLine(2, price_line); + s.getSide(Side.FRONT).setLine(3, period); + + //s.setLine(2, price_line); + //s.setLine(3, period); } else { - s.setLine(2, RealEstate.instance.config.cfgContainerRentLine); - s.setLine(3, price_line + " - " + period); + s.getSide(Side.FRONT).setLine(2, RealEstate.instance.config.cfgContainerRentLine); + s.getSide(Side.FRONT).setLine(3, price_line + " - " + period); + + //s.setLine(2, RealEstate.instance.config.cfgContainerRentLine); + //s.setLine(3, price_line + " - " + period); } s.update(true); } @@ -140,14 +150,19 @@ public boolean update() else if(sign.getBlock().getState() instanceof Sign) { Sign s = (Sign) sign.getBlock().getState(); - s.setLine(0, ChatColor.GOLD + RealEstate.instance.config.cfgReplaceOngoingRent); //Changed the header to "[Rented]" so that it won't waste space on the next line and allow the name of the player to show underneath. - s.setLine(1, Utils.getSignString(Bukkit.getOfflinePlayer(buyer).getName()));//remove "Rented by" - s.setLine(2, "Time remaining : "); + s.getSide(Side.FRONT).setLine(0, ChatColor.GOLD + RealEstate.instance.config.cfgReplaceOngoingRent); + s.getSide(Side.FRONT).setLine(1, Utils.getSignString(Bukkit.getOfflinePlayer(buyer).getName())); + s.getSide(Side.FRONT).setLine(2, "Time remaining : "); + //s.setLine(0, ChatColor.GOLD + RealEstate.instance.config.cfgReplaceOngoingRent); //Changed the header to "[Rented]" so that it won't waste space on the next line and allow the name of the player to show underneath. + //s.setLine(1, Utils.getSignString(Bukkit.getOfflinePlayer(buyer).getName()));//remove "Rented by" + //s.setLine(2, "Time remaining : "); int daysLeft = duration - days - 1;// we need to remove the current day Duration timeRemaining = Duration.ofHours(24).minus(hours); - s.setLine(3, Utils.getTime(daysLeft, timeRemaining, false)); + s.getSide(Side.FRONT).setLine(3, Utils.getTime(daysLeft, timeRemaining, false)); + + //s.setLine(3, Utils.getTime(daysLeft, timeRemaining, false)); s.update(true); } } diff --git a/src/me/EtienneDx/RealEstate/Transactions/ClaimSell.java b/src/me/EtienneDx/RealEstate/Transactions/ClaimSell.java index 406e2b3..0ca4478 100644 --- a/src/me/EtienneDx/RealEstate/Transactions/ClaimSell.java +++ b/src/me/EtienneDx/RealEstate/Transactions/ClaimSell.java @@ -18,6 +18,7 @@ import org.bukkit.Location; import org.bukkit.OfflinePlayer; import org.bukkit.block.Sign; +import org.bukkit.block.sign.Side; import org.bukkit.command.CommandSender; public class ClaimSell extends ClaimTransaction @@ -38,29 +39,36 @@ public boolean update() if(sign.getBlock().getState() instanceof Sign) { Sign s = (Sign) sign.getBlock().getState(); - s.setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); - s.setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceSell); - s.setLine(2, owner != null ? Utils.getSignString(Bukkit.getOfflinePlayer(owner).getName()) : "SERVER"); + s.getSide(Side.FRONT).setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); + s.getSide(Side.FRONT).setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceSell); + s.getSide(Side.FRONT).setLine(2, owner != null ? Utils.getSignString(Bukkit.getOfflinePlayer(owner).getName()) : "SERVER"); + //s.setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); + //s.setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceSell); + //s.setLine(2, owner != null ? Utils.getSignString(Bukkit.getOfflinePlayer(owner).getName()) : "SERVER"); if(RealEstate.instance.config.cfgUseCurrencySymbol) { if(RealEstate.instance.config.cfgUseDecimalCurrency == false) { - s.setLine(3, RealEstate.instance.config.cfgCurrencySymbol + " " + (int)Math.round(price)); + s.getSide(Side.FRONT).setLine(3, RealEstate.instance.config.cfgCurrencySymbol + " " + (int)Math.round(price)); + //s.setLine(3, RealEstate.instance.config.cfgCurrencySymbol + " " + (int)Math.round(price)); } else { - s.setLine(3, RealEstate.instance.config.cfgCurrencySymbol + " " + price); + s.getSide(Side.FRONT).setLine(3, RealEstate.instance.config.cfgCurrencySymbol + " " + price); + //s.setLine(3, RealEstate.instance.config.cfgCurrencySymbol + " " + price); } } else { if(RealEstate.instance.config.cfgUseDecimalCurrency == false) { - s.setLine(3, (int)Math.round(price) + " " + RealEstate.econ.currencyNamePlural()); + s.getSide(Side.FRONT).setLine(3, (int)Math.round(price) + " " + RealEstate.econ.currencyNamePlural()); + //s.setLine(3, (int)Math.round(price) + " " + RealEstate.econ.currencyNamePlural()); } else { - s.setLine(3, price + " " + RealEstate.econ.currencyNamePlural()); + s.getSide(Side.FRONT).setLine(3, price + " " + RealEstate.econ.currencyNamePlural()); + //s.setLine(3, price + " " + RealEstate.econ.currencyNamePlural()); } } s.update(true); diff --git a/src/me/EtienneDx/RealEstate/Transactions/Transaction.java b/src/me/EtienneDx/RealEstate/Transactions/Transaction.java index 6e15b50..909d1d1 100644 --- a/src/me/EtienneDx/RealEstate/Transactions/Transaction.java +++ b/src/me/EtienneDx/RealEstate/Transactions/Transaction.java @@ -8,13 +8,18 @@ public interface Transaction { - public Block getHolder(); - public UUID getOwner(); - public void setOwner(UUID newOwner); - public void interact(Player player); - public void preview(Player player); - public boolean update(); - public boolean tryCancelTransaction(Player p); - public boolean tryCancelTransaction(Player p, boolean force); - public void msgInfo(CommandSender cs); -} + public Block getHolder(); + public UUID getOwner(); + public void setOwner(UUID newOwner); + public void interact(Player player); + public void preview(Player player); + public boolean update(); + public boolean tryCancelTransaction(Player p); + public boolean tryCancelTransaction(Player p, boolean force); + public void msgInfo(CommandSender cs); + + // Add this method to differentiate rental transactions + public default boolean isRental() { + return false; // Override this in rental-related transactions + } +} \ No newline at end of file From 2ea3c428ee830a3b878af5e1a03035a46bd8d83f Mon Sep 17 00:00:00 2001 From: Michael Burgess Date: Mon, 10 Feb 2025 22:33:53 -0500 Subject: [PATCH 02/14] Cleaned Code --- .../RealEstate/ClaimPermissionListener.java | 5 ++--- src/me/EtienneDx/RealEstate/REListener.java | 1 - .../RealEstate/Transactions/ClaimLease.java | 15 ++++----------- .../RealEstate/Transactions/ClaimRent.java | 15 +-------------- .../RealEstate/Transactions/Transaction.java | 5 ----- .../Transactions/TransactionsStore.java | 2 +- 6 files changed, 8 insertions(+), 35 deletions(-) diff --git a/src/me/EtienneDx/RealEstate/ClaimPermissionListener.java b/src/me/EtienneDx/RealEstate/ClaimPermissionListener.java index 52bae3d..898e437 100644 --- a/src/me/EtienneDx/RealEstate/ClaimPermissionListener.java +++ b/src/me/EtienneDx/RealEstate/ClaimPermissionListener.java @@ -45,13 +45,12 @@ public void onClaimPermission(ClaimPermissionCheckEvent event) { RealEstate.instance.getLogger().info("isAdmin: " + isAdmin); RealEstate.instance.getLogger().info("isAdminClaim: " + event.getClaim().isAdminClaim()); RealEstate.instance.getLogger().info("Player has GP.AdminClaims: " + event.getCheckedPlayer().hasPermission("griefprevention.adminclaims")); - RealEstate.instance.getLogger().info("isRental: " + transaction.isRental()); RealEstate.instance.getLogger().info("User Who Triggered: " + event.getCheckedUUID()); RealEstate.instance.getLogger().info("Reason for Denial: " + event.getDenialReason()); } // Ensure it's a valid "BoughtTransaction" with an actual buyer, but NOT a rental - if ((isOwner || isAdmin) && transaction instanceof BoughtTransaction && !transaction.isRental()) { + if ((isOwner || isAdmin) && transaction instanceof BoughtTransaction) { BoughtTransaction boughtTransaction = (BoughtTransaction) transaction; if (boughtTransaction.getBuyer() != null) { switch (event.getRequiredPermission()) { @@ -82,7 +81,7 @@ public void onClaimPermission(ClaimPermissionCheckEvent event) { if (child == null) continue; // Avoid potential null references Transaction childTransaction = RealEstate.transactionsStore.getTransaction(child); - if (childTransaction instanceof BoughtTransaction && !childTransaction.isRental()) { + if (childTransaction instanceof BoughtTransaction) { BoughtTransaction boughtTransaction = (BoughtTransaction) childTransaction; if (boughtTransaction.getBuyer() != null) { event.setDenialReason(() -> Messages.getMessage(RealEstate.instance.messages.msgErrorSubclaimInTransaction)); diff --git a/src/me/EtienneDx/RealEstate/REListener.java b/src/me/EtienneDx/RealEstate/REListener.java index c356c47..5cf9c92 100644 --- a/src/me/EtienneDx/RealEstate/REListener.java +++ b/src/me/EtienneDx/RealEstate/REListener.java @@ -400,7 +400,6 @@ public void onPlayerInteract(PlayerInteractEvent event) event.getClickedBlock().getState() instanceof Sign) { Sign sign = (Sign)event.getClickedBlock().getState(); - //RealEstate.instance.log.info(sign.getLine(0)); RealEstate.instance.log.info(sign.getSide(Side.FRONT).getLine(0)); // it is a real estate sign if(ChatColor.stripColor(sign.getSide(Side.FRONT).getLine(0)).equalsIgnoreCase(ChatColor.stripColor( diff --git a/src/me/EtienneDx/RealEstate/Transactions/ClaimLease.java b/src/me/EtienneDx/RealEstate/Transactions/ClaimLease.java index cb4ee38..8f2f375 100644 --- a/src/me/EtienneDx/RealEstate/Transactions/ClaimLease.java +++ b/src/me/EtienneDx/RealEstate/Transactions/ClaimLease.java @@ -69,21 +69,18 @@ public boolean update() Sign s = (Sign)sign.getBlock().getState(); s.getSide(Side.FRONT).setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader)); s.getSide(Side.FRONT).setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceLease); - //s.setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); - //s.setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceLease); - //s.setLine(2, owner != null ? Bukkit.getOfflinePlayer(owner).getName() : "SERVER"); - //s.setLine(2, paymentsLeft + "x " + price + " " + RealEstate.econ.currencyNamePlural()); + s.getSide(Side.FRONT).setLine(2, owner != null ? Bukkit.getOfflinePlayer(owner).getName() : "SERVER"); + s.getSide(Side.FRONT).setLine(3, paymentsLeft + "x " + price + " " + RealEstate.econ.currencyNamePlural()); + if(RealEstate.instance.config.cfgUseCurrencySymbol) { if(RealEstate.instance.config.cfgUseDecimalCurrency == false) { s.getSide(Side.FRONT).setLine(2, paymentsLeft + "x " + RealEstate.instance.config.cfgCurrencySymbol + " " + (int)Math.round(price)); - //s.setLine(2, paymentsLeft + "x " + RealEstate.instance.config.cfgCurrencySymbol + " " + (int)Math.round(price)); } else { s.getSide(Side.FRONT).setLine(2, paymentsLeft + "x " + RealEstate.instance.config.cfgCurrencySymbol + " " + price); - //s.setLine(2, paymentsLeft + "x " + RealEstate.instance.config.cfgCurrencySymbol + " " + price); } } else @@ -91,23 +88,19 @@ public boolean update() if(RealEstate.instance.config.cfgUseDecimalCurrency == false) { s.getSide(Side.FRONT).setLine(2, paymentsLeft + "x " + (int)Math.round(price) + " " + RealEstate.econ.currencyNamePlural()); - //s.setLine(2, paymentsLeft + "x " + (int)Math.round(price) + " " + RealEstate.econ.currencyNamePlural()); } else { s.getSide(Side.FRONT).setLine(2, paymentsLeft + "x " + price + " " + RealEstate.econ.currencyNamePlural()); - //s.setLine(2, paymentsLeft + "x " + price + " " + RealEstate.econ.currencyNamePlural()); } } s.getSide(Side.FRONT).setLine(3, Utils.getTime(frequency, null, false)); - //s.setLine(3, Utils.getTime(frequency, null, false)); s.update(true); } else { return true; } - } else { @@ -288,7 +281,7 @@ else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) } else { - getHolder().breakNaturally();// the sign should still be there since the lease has netver begun + getHolder().breakNaturally();// the sign should still be there since the lease has never begun } RealEstate.transactionsStore.cancelTransaction(this); } diff --git a/src/me/EtienneDx/RealEstate/Transactions/ClaimRent.java b/src/me/EtienneDx/RealEstate/Transactions/ClaimRent.java index ed90bd3..cb395d0 100644 --- a/src/me/EtienneDx/RealEstate/Transactions/ClaimRent.java +++ b/src/me/EtienneDx/RealEstate/Transactions/ClaimRent.java @@ -84,10 +84,8 @@ public boolean update() Sign s = (Sign) sign.getBlock().getState(); s.getSide(Side.FRONT).setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); s.getSide(Side.FRONT).setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceRent); + s.getSide(Side.FRONT).setLine(2, owner != null ? Bukkit.getOfflinePlayer(owner).getName() : "SERVER"); - //s.setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); - //s.setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceRent); - //s.setLine(2, owner != null ? Bukkit.getOfflinePlayer(owner).getName() : "SERVER"); String price_line = ""; if(RealEstate.instance.config.cfgUseCurrencySymbol) { @@ -116,15 +114,9 @@ public boolean update() if(this.buildTrust) { s.getSide(Side.FRONT).setLine(2, price_line); s.getSide(Side.FRONT).setLine(3, period); - - //s.setLine(2, price_line); - //s.setLine(3, period); } else { s.getSide(Side.FRONT).setLine(2, RealEstate.instance.config.cfgContainerRentLine); s.getSide(Side.FRONT).setLine(3, price_line + " - " + period); - - //s.setLine(2, RealEstate.instance.config.cfgContainerRentLine); - //s.setLine(3, price_line + " - " + period); } s.update(true); } @@ -153,16 +145,11 @@ else if(sign.getBlock().getState() instanceof Sign) s.getSide(Side.FRONT).setLine(0, ChatColor.GOLD + RealEstate.instance.config.cfgReplaceOngoingRent); s.getSide(Side.FRONT).setLine(1, Utils.getSignString(Bukkit.getOfflinePlayer(buyer).getName())); s.getSide(Side.FRONT).setLine(2, "Time remaining : "); - //s.setLine(0, ChatColor.GOLD + RealEstate.instance.config.cfgReplaceOngoingRent); //Changed the header to "[Rented]" so that it won't waste space on the next line and allow the name of the player to show underneath. - //s.setLine(1, Utils.getSignString(Bukkit.getOfflinePlayer(buyer).getName()));//remove "Rented by" - //s.setLine(2, "Time remaining : "); int daysLeft = duration - days - 1;// we need to remove the current day Duration timeRemaining = Duration.ofHours(24).minus(hours); s.getSide(Side.FRONT).setLine(3, Utils.getTime(daysLeft, timeRemaining, false)); - - //s.setLine(3, Utils.getTime(daysLeft, timeRemaining, false)); s.update(true); } } diff --git a/src/me/EtienneDx/RealEstate/Transactions/Transaction.java b/src/me/EtienneDx/RealEstate/Transactions/Transaction.java index 909d1d1..2620d5c 100644 --- a/src/me/EtienneDx/RealEstate/Transactions/Transaction.java +++ b/src/me/EtienneDx/RealEstate/Transactions/Transaction.java @@ -17,9 +17,4 @@ public interface Transaction public boolean tryCancelTransaction(Player p); public boolean tryCancelTransaction(Player p, boolean force); public void msgInfo(CommandSender cs); - - // Add this method to differentiate rental transactions - public default boolean isRental() { - return false; // Override this in rental-related transactions - } } \ No newline at end of file diff --git a/src/me/EtienneDx/RealEstate/Transactions/TransactionsStore.java b/src/me/EtienneDx/RealEstate/Transactions/TransactionsStore.java index 4385385..a14d928 100644 --- a/src/me/EtienneDx/RealEstate/Transactions/TransactionsStore.java +++ b/src/me/EtienneDx/RealEstate/Transactions/TransactionsStore.java @@ -26,7 +26,7 @@ public class TransactionsStore { - public final String dataFilePath = RealEstate.pluginDirPath + "transactions.data"; + public final String dataFilePath = RealEstate.pluginDirPath + "transactions.yml"; DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); Date date = new Date(); From b81b00a2d939cae816cfaecce6e248388b020d6e Mon Sep 17 00:00:00 2001 From: Michael Burgess Date: Fri, 14 Feb 2025 01:02:59 -0500 Subject: [PATCH 03/14] Version 1.4.2 Please see change log. --- .gitignore | 5 +- CHANGELOG.md | 33 +- README.md | 2 +- paper-plugin.yml | 106 ++ plugin.yml | 65 +- pom.xml | 33 +- resources/languages/pt-br.yml | 2 + .../RealEstate/ClaimAPI/ClaimPermission.java | 10 + .../ClaimAPI/GriefDefender/GDClaim.java | 174 ++++ .../GriefDefender/GDPermissionListener.java | 135 +++ .../ClaimAPI/GriefDefender/GDPlayerData.java | 40 + .../GriefDefender/GriefDefenderAPI.java | 47 + .../ClaimPermissionListener.java | 57 ++ .../ClaimAPI/GriefPrevention/GPClaim.java | 174 ++++ .../GriefPrevention/GPPlayerData.java | 40 + .../GriefPrevention/GriefPreventionAPI.java | 48 + .../EtienneDx/RealEstate/ClaimAPI/IClaim.java | 28 + .../RealEstate/ClaimAPI/IClaimAPI.java | 12 + .../RealEstate/ClaimAPI/IPlayerData.java | 9 + src/me/EtienneDx/RealEstate/ClaimEvents.java | 55 + .../RealEstate/ClaimPermissionListener.java | 107 -- src/me/EtienneDx/RealEstate/Config.java | 129 ++- src/me/EtienneDx/RealEstate/Messages.java | 97 +- src/me/EtienneDx/RealEstate/RECommand.java | 46 +- src/me/EtienneDx/RealEstate/REListener.java | 179 +++- src/me/EtienneDx/RealEstate/RealEstate.java | 167 ++- .../EtienneDx/RealEstate/RealEstateSign.java | 37 + .../Transactions/BoughtTransaction.java | 102 +- .../RealEstate/Transactions/ClaimAuction.java | 364 +++++++ .../RealEstate/Transactions/ClaimLease.java | 96 +- .../RealEstate/Transactions/ClaimRent.java | 121 +-- .../RealEstate/Transactions/ClaimSell.java | 73 +- .../Transactions/ClaimTransaction.java | 145 +-- .../RealEstate/Transactions/ExitOffer.java | 47 +- .../RealEstate/Transactions/Transaction.java | 6 +- .../Transactions/TransactionsStore.java | 955 ++++++++++++------ src/me/EtienneDx/RealEstate/Utils.java | 94 +- 37 files changed, 2894 insertions(+), 946 deletions(-) create mode 100644 paper-plugin.yml create mode 100644 src/me/EtienneDx/RealEstate/ClaimAPI/ClaimPermission.java create mode 100644 src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GDClaim.java create mode 100644 src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GDPermissionListener.java create mode 100644 src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GDPlayerData.java create mode 100644 src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GriefDefenderAPI.java create mode 100644 src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/ClaimPermissionListener.java create mode 100644 src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/GPClaim.java create mode 100644 src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/GPPlayerData.java create mode 100644 src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/GriefPreventionAPI.java create mode 100644 src/me/EtienneDx/RealEstate/ClaimAPI/IClaim.java create mode 100644 src/me/EtienneDx/RealEstate/ClaimAPI/IClaimAPI.java create mode 100644 src/me/EtienneDx/RealEstate/ClaimAPI/IPlayerData.java create mode 100644 src/me/EtienneDx/RealEstate/ClaimEvents.java delete mode 100644 src/me/EtienneDx/RealEstate/ClaimPermissionListener.java create mode 100644 src/me/EtienneDx/RealEstate/RealEstateSign.java create mode 100644 src/me/EtienneDx/RealEstate/Transactions/ClaimAuction.java diff --git a/.gitignore b/.gitignore index 5318537..3da09c9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.DS_Store .classpath .project .idea @@ -7,4 +8,6 @@ target/* build.bat /bin/ /target/ -.vscode \ No newline at end of file +.vscode +report.cmd +report.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 53f7155..ec6158b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,37 @@ # Changelog -## 1.4.1 +# Version 1.4.2 (2025-02-13) +### Admin Claim Support Improvements: +* When processing ClaimRent transactions, if a claim is identified as an admin claim, its owner is now set to "SERVER" (using a fixed UUID or identifier) to ensure correct behavior. +* The INSERT statements for ClaimRent now conditionally set the owner to SERVER if the claim is an admin claim. + +### Sign Update Consistency: +* Modified ClaimSell to update its sign immediately after creation. +* Updated ClaimRent to also perform an immediate sign update (by scheduling a one-tick delay) so that rental signs display correct information instantly. +* Updated ClaimLease to also perform an immediate sign update (by scheduling a one-tick delay) so that rental signs display correct information instantly. +* Updated ClaimAuction to also perform an immediate sign update (by scheduling a one-tick delay) so that rental signs display correct information instantly. + +### Database Handling Enhancements: +* Fixed issues with UUID parsing when loading transactions from the database. +* Improved error checking for owner values during data load to prevent invalid UUID strings. + +### Code Quality and Refactoring: +* Cleaned up repeated code between transaction types and centralized common behavior (e.g., sign updating and logging). +** Added missing getter methods for ClaimAuction, ClaimLease, ClaimRent, and ClaimSell to support proper database operations. +* Renamed the old transactions.data file to transactions.yml. (The plugin will automatically reformat if the old file is detected) +** This will be helpful for future upgrades. +### Dependency and Compatibility: +* Ensured compatibility with the latest versions of Vault, EssentialsX, GriefPrevention, and GriefDefender. +** Added paper-plugin.yml to ensure Paper servers load the plugins in the correct order. +** Updated the plugin's plugin.yml and paper-plugin.yml for API version 1.21.4. + +### General Bug Fixes: +* Resolved a bug where the sign for [sell] transactions remained blank. +* Addressed potential null pointer exceptions during claim data loading. +* Improved logging to capture and record transaction events more clearly. + +## 1.4.1 ### Added * Added support for multiple languages files within the jar * Added `pt-br` as a language option @@ -12,7 +42,6 @@ * Fixed #51 regarding a duplicate prefix on `/re info` ## 1.4.0 - ### Added * Readme and changelog files * Error messages to *messages.yml* diff --git a/README.md b/README.md index c73fa11..4d7e442 100644 --- a/README.md +++ b/README.md @@ -10,4 +10,4 @@ Please feel free to report any issue in the [GitHub Issue section](https://githu This plugin is dependent on GriefPrevention version 16.18 and up. -GriefPrevention plugin can be found [here](https://github.com/GriefPrevention/GriefPrevention). \ No newline at end of file +GriefPrevention plugin can be found [here](https://github.com/TechFortress/GriefPrevention). \ No newline at end of file diff --git a/paper-plugin.yml b/paper-plugin.yml new file mode 100644 index 0000000..d1d8fd4 --- /dev/null +++ b/paper-plugin.yml @@ -0,0 +1,106 @@ +name: RealEstate +main: me.EtienneDx.RealEstate.RealEstate +version: ${project.version} +api-version: 1.21.4 +authors: + - EtienneDx + - DmitryRendov + - SkyWalker3200 + - Tadhunt +dependencies: + server: + Vault: + load: BEFORE + required: true + join-classpath: true + Essentials: + load: BEFORE + required: true + join-classpath: true + GriefPrevention: + load: BEFORE + required: false + join-classpath: true + GriefDefender: + load: BEFORE + required: false + join-classpath: true +commands: + re: + description: Access to the claim transaction information. + usage: / + aliases: + - realestate + permission: realestate.info + permission-message: You do not have access to that command! +permissions: + realestate.*: + description: Gives access to all Real Estate permissions + default: op + children: + realestate.info: true + realestate.admin: true + realestate.destroysigns: true + realestate.claim.buy: true + realestate.claim.sell: true + realestate.claim.rent: true + realestate.claim.lease: true + realestate.subclaim.buy: true + realestate.subclaim.sell: true + realestate.subclaim.rent: true + realestate.subclaim.lease: true + realestate.autorenew: true + realestate.claim.*: + description: Allows the player full access over claims + default: op + children: + realestate.claim.buy: true + realestate.claim.sell: true + realestate.claim.rent: true + realestate.claim.lease: true + realestate.subclaim.*: + description: Allows the player full access over subclaims + default: op + children: + realestate.subclaim.buy: true + realestate.subclaim.sell: true + realestate.subclaim.rent: true + realestate.subclaim.lease: true + realestate.admin: + description: Allows the player to sell and lease admin claims, as well as see + plugin informations + default: op + realestate.info: + description: Allow the player to get informations about the claim + default: true + realestate.claim.buy: + description: Allows the player to buy claims (buying, paying rents, paying leases) + default: true + realestate.claim.sell: + description: Allows the player to sell claims + default: true + realestate.claim.rent: + description: Allows the player to rent claims + default: true + realestate.claim.lease: + description: Allows the player to lease claims + default: true + realestate.subclaim.buy: + description: Allows the player to buy subclaims (buying, paying rents) + default: true + realestate.subclaim.sell: + description: Allows the player to sell subclaims + default: false + realestate.subclaim.rent: + description: Allows the player to rent subclaims + default: true + realestate.subclaim.lease: + description: Allows the player to lease subclaims + default: false + realestate.destroysigns: + description: Allows the player to destroy any sign representing a transaction + default: op + realestate.autorenew: + description: Allows the player to enable automatic renew for his rented claims + and subclaims + default: true diff --git a/plugin.yml b/plugin.yml index 6e30645..54a396b 100644 --- a/plugin.yml +++ b/plugin.yml @@ -1,21 +1,30 @@ +--- name: RealEstate main: me.EtienneDx.RealEstate.RealEstate version: ${project.version} -authors: [EtienneDx, DmitryRendov] -depend: [Vault, GriefPrevention] -api-version: "1.18" - +authors: + - EtienneDx + - DmitryRendov + - SkyWalker3200 + - Tadhunt +load: POSTWORLD +depend: + - Vault +softdepend: + - GriefPrevention + - GriefDefender +api-version: 1.21.4 commands: re: description: Access to the claim transaction information. usage: / - aliases: [realestate] + aliases: + - realestate permission: realestate.info permission-message: You do not have access to that command! - permissions: realestate.*: - description: Gives access to all Real Estate permissions + description: Gives access to all Real Estate permissions (including admin!) default: op children: realestate.info: true @@ -25,10 +34,14 @@ permissions: realestate.claim.sell: true realestate.claim.rent: true realestate.claim.lease: true + realestate.claim.auction: true + realestate.claim.bid: true realestate.subclaim.buy: true realestate.subclaim.sell: true realestate.subclaim.rent: true realestate.subclaim.lease: true + realestate.subclaim.auction: true + realestate.subclaim.bid: true realestate.autorenew: true realestate.claim.*: description: Allows the player full access over claims @@ -38,6 +51,8 @@ permissions: realestate.claim.sell: true realestate.claim.rent: true realestate.claim.lease: true + realestate.claim.auction: true + realestate.claim.bid: true realestate.subclaim.*: description: Allows the player full access over subclaims default: op @@ -46,9 +61,28 @@ permissions: realestate.subclaim.sell: true realestate.subclaim.rent: true realestate.subclaim.lease: true + realestate.subclaim.auction: true + realestate.subclaim.bid: true realestate.admin: - description: Allows the player to sell and lease admin claims, as well as see plugin informations + description: Allows the player to sell and lease admin claims, as well as see + plugin informations default: op + children: + realestate.info: true + realestate.destroysigns: true + realestate.claim.buy: true + realestate.claim.sell: true + realestate.claim.rent: true + realestate.claim.lease: true + realestate.claim.auction: true + realestate.claim.bid: true + realestate.subclaim.buy: true + realestate.subclaim.sell: true + realestate.subclaim.rent: true + realestate.subclaim.lease: true + realestate.subclaim.auction: true + realestate.subclaim.bid: true + realestate.autorenew: true realestate.info: description: Allow the player to get informations about the claim default: true @@ -64,6 +98,12 @@ permissions: realestate.claim.lease: description: Allows the player to lease claims default: true + realestate.claim.auction: + description: Allows the player to auction claims + default: true + realestate.claim.bid: + description: Allows the player to bid on claims + default: true realestate.subclaim.buy: description: Allows the player to buy subclaims (buying, paying rents) default: true @@ -76,9 +116,16 @@ permissions: realestate.subclaim.lease: description: Allows the player to lease subclaims default: false + realestate.subclaim.auction: + description: Allows the player to auction subclaims + default: false + realestate.subclaim.bid: + description: Allows the player to bid on subclaims + default: false realestate.destroysigns: description: Allows the player to destroy any sign representing a transaction default: op realestate.autorenew: - description: Allows the player to enable automatic renew for his rented claims and subclaims + description: Allows the player to enable automatic renew for his rented claims + and subclaims default: true diff --git a/pom.xml b/pom.xml index ea4e513..36914a4 100644 --- a/pom.xml +++ b/pom.xml @@ -5,8 +5,9 @@ Me.EtienneDx real-estate 1.4.2 + RealEstate - A spigot plugin for selling, renting and leasing GriefPrevention claims + A spigot plugin for selling, renting, leasing and auctioning GriefPrevention and GriefDefender claims ${project.name} src @@ -17,6 +18,7 @@ . plugin.yml + paper-plugin.yml @@ -33,7 +35,7 @@ 16 - + maven-assembly-plugin 2.4.1 @@ -115,11 +117,18 @@ aikar https://repo.aikar.co/content/groups/aikar/ - ess-repo https://ci.ender.zone/plugin/repository/everything/ + + glaremasters + https://repo.glaremasters.me/repository/bloodshot + + + papermc + https://repo.papermc.io/repository/maven-public/ + @@ -131,7 +140,7 @@ org.spigotmc spigot-api - 1.21.4-R0.1-SNAPSHOT + 1.21.1-R0.1-SNAPSHOT provided @@ -148,7 +157,7 @@ net.ess3 EssentialsX - 2.17.2 + 2.16.1 provided @@ -157,10 +166,22 @@ 16.18.4 provided + + com.griefdefender + api + 2.1.0-20220122.032038-5 + provided + com.github.EtienneDx AnnotationConfig e9eab24 + + io.papermc.paper + paper-api + 1.21.4-R0.1-SNAPSHOT + provided + diff --git a/resources/languages/pt-br.yml b/resources/languages/pt-br.yml index 801417b..c1dc37c 100644 --- a/resources/languages/pt-br.yml +++ b/resources/languages/pt-br.yml @@ -3,6 +3,8 @@ # Use dollar signs ($) for formatting codes, which are documented here: http://minecraft.gamepedia.com/Formatting_codes. # You can use {0}, {1} to include the different values indicated in the comments RealEstate: + lang: + version: 1.0 Keywords: # Keywords used within other messages but with a longer text at # the end just because i need to test some stuff diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/ClaimPermission.java b/src/me/EtienneDx/RealEstate/ClaimAPI/ClaimPermission.java new file mode 100644 index 0000000..b575028 --- /dev/null +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/ClaimPermission.java @@ -0,0 +1,10 @@ +package me.EtienneDx.RealEstate.ClaimAPI; + +public enum ClaimPermission +{ + BUILD, + CONTAINER, + MANAGE, + EDIT, + ACCESS +} diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GDClaim.java b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GDClaim.java new file mode 100644 index 0000000..4e2fcbe --- /dev/null +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GDClaim.java @@ -0,0 +1,174 @@ +package me.EtienneDx.RealEstate.ClaimAPI.GriefDefender; + +import java.util.Iterator; +import java.util.UUID; + +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.TrustType; +import com.griefdefender.api.claim.TrustTypes; + +import org.bukkit.Bukkit; +import org.bukkit.World; + +import me.EtienneDx.RealEstate.ClaimAPI.ClaimPermission; +import me.EtienneDx.RealEstate.ClaimAPI.IClaim; + +public class GDClaim implements IClaim{ + + private Claim claim; + + public GDClaim(Claim claim) { + this.claim = claim; + } + + public Claim getClaim() { + return claim; + } + + @Override + public String getId() { + return claim.getUniqueId().toString(); + } + + @Override + public int getArea() { + return claim.getArea(); + } + + @Override + public World getWorld() { + return Bukkit.getWorld(claim.getWorldUniqueId()); + } + + @Override + public int getX() { + return claim.getLesserBoundaryCorner().getX(); + } + + @Override + public int getY() { + return claim.getLesserBoundaryCorner().getY(); + } + + @Override + public int getZ() { + return claim.getLesserBoundaryCorner().getZ(); + } + + @Override + public boolean isAdminClaim() { + return claim.isAdminClaim(); + } + + @Override + public Iterable getChildren() { + return new Iterable() { + @Override + public Iterator iterator() { + return new Iterator() { + private Iterator it = claim.getChildren(true).iterator(); + + @Override + public boolean hasNext() { + return it.hasNext(); + } + + @Override + public IClaim next() { + return new GDClaim(it.next()); + } + + @Override + public void remove() { + it.remove(); + } + }; + } + }; + } + + @Override + public boolean isWilderness() { + return claim.isWilderness(); + } + + @Override + public boolean isSubClaim() { + return claim.getParent() != null && !claim.getParent().isWilderness(); + } + + @Override + public boolean isParentClaim() { + return claim.getParent() == null || claim.getParent().isWilderness(); + } + + @Override + public IClaim getParent() { + return isParentClaim() ? null : new GDClaim(claim.getParent()); + } + + @Override + public void dropPlayerPermissions(UUID player) { + claim.removeUserTrust(player, TrustTypes.NONE); + } + + @Override + public void addPlayerPermissions(UUID player, ClaimPermission permission) { + TrustType trust = TrustTypes.NONE; + switch (permission) { + case ACCESS: + trust = TrustTypes.ACCESSOR; + break; + case BUILD: + trust = TrustTypes.BUILDER; + break; + case CONTAINER: + trust = TrustTypes.CONTAINER; + break; + case EDIT: + trust = TrustTypes.MANAGER; + break; + case MANAGE: + trust = TrustTypes.MANAGER; + break; + } + + claim.addUserTrust(player, trust); + } + + @Override + public void clearPlayerPermissions() { + claim.removeAllUserTrusts(); + } + + @Override + public void removeManager(UUID player) { + // No equivalent in GD + } + + @Override + public void addManager(UUID player) { + // No equivalent in GD + } + + @Override + public void clearManagers() { + // No equivalent in GD + } + + @Override + public UUID getOwner() { + return claim.getOwnerUniqueId(); + } + + @Override + public String getOwnerName() { + return claim.getOwnerName(); + } + + @Override + public void setInheritPermissions(boolean inherit) { + claim.getData().setInheritParent(inherit); + } + +} diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GDPermissionListener.java b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GDPermissionListener.java new file mode 100644 index 0000000..082be96 --- /dev/null +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GDPermissionListener.java @@ -0,0 +1,135 @@ +package me.EtienneDx.RealEstate.ClaimAPI.GriefDefender; + +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.User; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.api.event.ChangeClaimEvent; +import com.griefdefender.api.event.Event; +import com.griefdefender.api.event.ProcessTrustUserEvent; +import com.griefdefender.api.event.RemoveClaimEvent; +import com.griefdefender.lib.kyori.adventure.text.Component; +import com.griefdefender.lib.kyori.event.EventBus; +import com.griefdefender.lib.kyori.event.EventSubscriber; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.checkerframework.checker.nullness.qual.NonNull; + +import me.EtienneDx.RealEstate.ClaimEvents; +import me.EtienneDx.RealEstate.ClaimAPI.ClaimPermission; + +public class GDPermissionListener +{ + + public GDPermissionListener() { + new ProcessTrustUserEventListener(); + new ChangeClaimEventListener(); + new RemoveClaimEventListener(); + } + + private class ProcessTrustUserEventListener { + + public ProcessTrustUserEventListener() { + final EventBus eventBus = GriefDefender.getEventManager().getBus(); + + eventBus.subscribe(ProcessTrustUserEvent.class, new EventSubscriber() { + + @Override + public void on(@NonNull ProcessTrustUserEvent event) throws Throwable { + final User user = event.getUser(); + if (user == null) { + return; + } + final Player player = Bukkit.getPlayer(user.getUniqueId()); + if (player == null) { + return; + } + ClaimPermission permission = null; + if(event.getTrustType().equals(TrustTypes.ACCESSOR)) { + permission = ClaimPermission.ACCESS; + } + else if(event.getTrustType().equals(TrustTypes.BUILDER)) { + permission = ClaimPermission.BUILD; + } + else if(event.getTrustType().equals(TrustTypes.CONTAINER)) { + permission = ClaimPermission.CONTAINER; + } + else if(event.getTrustType().equals(TrustTypes.MANAGER)) { + permission = ClaimPermission.MANAGE; + } + String denialReason = ClaimEvents.onClaimPermission( + new GDClaim(event.getClaim()), + player, + permission + ); + if (denialReason != null) { + event.setMessage(Component.text(denialReason)); + event.cancelled(true); + } + } + }); + } + } + + private class ChangeClaimEventListener { + + public ChangeClaimEventListener() { + final EventBus eventBus = GriefDefender.getEventManager().getBus(); + + eventBus.subscribe(ChangeClaimEvent.class, new EventSubscriber() { + + @Override + public void on(@NonNull ChangeClaimEvent event) throws Throwable { + final User user = event.getCause().first(User.class).orElse(null); + if (user == null) { + return; + } + final Player player = Bukkit.getPlayer(user.getUniqueId()); + if (player == null) { + return; + } + String denialReason = ClaimEvents.onClaimPermission( + new GDClaim(event.getClaim()), + player, + ClaimPermission.EDIT + ); + if (denialReason != null) { + event.setMessage(Component.text(denialReason)); + event.cancelled(true); + } + } + }); + } + } + + private class RemoveClaimEventListener { + + public RemoveClaimEventListener() { + final EventBus eventBus = GriefDefender.getEventManager().getBus(); + + eventBus.subscribe(RemoveClaimEvent.class, new EventSubscriber() { + + @Override + public void on(@NonNull RemoveClaimEvent event) throws Throwable { + final User user = event.getCause().first(User.class).orElse(null); + if (user == null) { + return; + } + final Player player = Bukkit.getPlayer(user.getUniqueId()); + if (player == null) { + return; + } + String denialReason = ClaimEvents.onClaimPermission( + new GDClaim(event.getClaim()), + player, + ClaimPermission.EDIT + ); + if (denialReason != null) { + event.setMessage(Component.text(denialReason)); + event.cancelled(true); + } + } + }); + } + } +} \ No newline at end of file diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GDPlayerData.java b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GDPlayerData.java new file mode 100644 index 0000000..acb78fd --- /dev/null +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GDPlayerData.java @@ -0,0 +1,40 @@ +package me.EtienneDx.RealEstate.ClaimAPI.GriefDefender; + +import com.griefdefender.api.data.PlayerData; + +import me.EtienneDx.RealEstate.ClaimAPI.IPlayerData; + +public class GDPlayerData implements IPlayerData { + + private PlayerData playerData; + + public GDPlayerData(PlayerData playerData) { + this.playerData = playerData; + } + + @Override + public int getAccruedClaimBlocks() { + return playerData.getAccruedClaimBlocks(); + } + + @Override + public int getBonusClaimBlocks() { + return playerData.getBonusClaimBlocks(); + } + + @Override + public void setAccruedClaimBlocks(int accruedClaimBlocks) { + playerData.setAccruedClaimBlocks(accruedClaimBlocks); + } + + @Override + public void setBonusClaimBlocks(int bonusClaimBlocks) { + playerData.setBonusClaimBlocks(bonusClaimBlocks); + } + + @Override + public int getRemainingClaimBlocks() { + return playerData.getRemainingClaimBlocks(); + } + +} diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GriefDefenderAPI.java b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GriefDefenderAPI.java new file mode 100644 index 0000000..c2cdede --- /dev/null +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GriefDefenderAPI.java @@ -0,0 +1,47 @@ +package me.EtienneDx.RealEstate.ClaimAPI.GriefDefender; + +import java.util.UUID; + +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.claim.ClaimResult; + +import org.bukkit.Bukkit; +import org.bukkit.Location; + +import me.EtienneDx.RealEstate.ClaimAPI.IClaim; +import me.EtienneDx.RealEstate.ClaimAPI.IClaimAPI; +import me.EtienneDx.RealEstate.ClaimAPI.IPlayerData; + +public class GriefDefenderAPI implements IClaimAPI{ + + @Override + public IClaim getClaimAt(Location location) { + return new GDClaim(GriefDefender.getCore().getClaimAt(location)); + } + + @Override + public void saveClaim(IClaim claim) { + // GD auto saves + } + + @Override + public IPlayerData getPlayerData(UUID player) { + return new GDPlayerData(GriefDefender.getCore().getPlayerData(Bukkit.getPlayer(player).getWorld().getUID(), player)); + } + + @Override + public void changeClaimOwner(IClaim claim, UUID newOwner) { + if(claim instanceof GDClaim) { + ClaimResult res = ((GDClaim) claim).getClaim().transferOwner(newOwner); + if(!res.successful()) { + throw new RuntimeException(res.getResultType().toString()); + } + } + } + + @Override + public void registerEvents() { + new GDPermissionListener(); + } + +} diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/ClaimPermissionListener.java b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/ClaimPermissionListener.java new file mode 100644 index 0000000..98b4575 --- /dev/null +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/ClaimPermissionListener.java @@ -0,0 +1,57 @@ +package me.EtienneDx.RealEstate.ClaimAPI.GriefPrevention; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.plugin.PluginManager; + +import me.EtienneDx.RealEstate.ClaimEvents; +import me.EtienneDx.RealEstate.RealEstate; +import me.ryanhamshire.GriefPrevention.events.ClaimDeletedEvent; +import me.ryanhamshire.GriefPrevention.events.ClaimPermissionCheckEvent; + +public class ClaimPermissionListener implements Listener { + void registerEvents() + { + PluginManager pm = RealEstate.instance.getServer().getPluginManager(); + + pm.registerEvents(this, RealEstate.instance); + } + + @EventHandler + public void onClaimPermission(ClaimPermissionCheckEvent event) { + me.EtienneDx.RealEstate.ClaimAPI.ClaimPermission permission = null; + switch (event.getRequiredPermission()) { + case Access: + permission = me.EtienneDx.RealEstate.ClaimAPI.ClaimPermission.ACCESS; + break; + case Build: + permission = me.EtienneDx.RealEstate.ClaimAPI.ClaimPermission.BUILD; + break; + case Edit: + permission = me.EtienneDx.RealEstate.ClaimAPI.ClaimPermission.EDIT; + break; + case Inventory: + permission = me.EtienneDx.RealEstate.ClaimAPI.ClaimPermission.CONTAINER; + break; + case Manage: + permission = me.EtienneDx.RealEstate.ClaimAPI.ClaimPermission.MANAGE; + break; + } + + String denialReason = ClaimEvents.onClaimPermission( + new GPClaim(event.getClaim()), + event.getCheckedPlayer(), + permission + ); + if(denialReason != null) + { + event.setDenialReason(() -> denialReason); + } + } + + // more of a safety measure, normally it shouldn't be needed + @EventHandler + public void onClaimDeleted(ClaimDeletedEvent event) { + ClaimEvents.onClaimDeleted(new GPClaim(event.getClaim())); + } +} diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/GPClaim.java b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/GPClaim.java new file mode 100644 index 0000000..2a4129f --- /dev/null +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/GPClaim.java @@ -0,0 +1,174 @@ +package me.EtienneDx.RealEstate.ClaimAPI.GriefPrevention; + +import java.util.Iterator; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.World; + +import me.EtienneDx.RealEstate.RealEstate; +import me.EtienneDx.RealEstate.ClaimAPI.ClaimPermission; +import me.EtienneDx.RealEstate.ClaimAPI.IClaim; +import me.ryanhamshire.GriefPrevention.Claim; + +public class GPClaim implements IClaim +{ + private Claim claim; + + public GPClaim(Claim claim) + { + this.claim = claim; + } + + public Claim getClaim() + { + return claim; + } + + @Override + public String getId() + { + return claim.getID().toString(); + } + + @Override + public int getArea() { + return claim.getArea(); + } + + @Override + public World getWorld() { + return claim.getLesserBoundaryCorner().getWorld(); + } + + @Override + public int getX() { + return claim.getLesserBoundaryCorner().getBlockX(); + } + + @Override + public int getY() { + return claim.getLesserBoundaryCorner().getBlockY(); + } + + @Override + public int getZ() { + return claim.getLesserBoundaryCorner().getBlockZ(); + } + + @Override + public boolean isAdminClaim() { + return claim.isAdminClaim(); + } + + @Override + public Iterable getChildren() { + return new Iterable() { + @Override + public Iterator iterator() { + return new Iterator() { + private Iterator iterator = claim.children.iterator(); + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public IClaim next() { + return new GPClaim(iterator.next()); + } + + @Override + public void remove() { + iterator.remove(); + } + }; + } + }; + } + + @Override + public boolean isWilderness() { + return false; + } + + @Override + public boolean isSubClaim() { + return claim.parent != null; + } + + @Override + public boolean isParentClaim() { + return claim.parent == null; + } + + @Override + public IClaim getParent() { + return isSubClaim() ? new GPClaim(claim.parent) : null; + } + + @Override + public void dropPlayerPermissions(UUID player) { + claim.dropPermission(player.toString()); + } + + @Override + public void addPlayerPermissions(UUID player, ClaimPermission permission) { + me.ryanhamshire.GriefPrevention.ClaimPermission gpPermission = null; + switch (permission) { + case BUILD: + gpPermission = me.ryanhamshire.GriefPrevention.ClaimPermission.Build; + break; + case CONTAINER: + gpPermission = me.ryanhamshire.GriefPrevention.ClaimPermission.Inventory; + break; + case MANAGE: + gpPermission = me.ryanhamshire.GriefPrevention.ClaimPermission.Manage; + break; + case ACCESS: + gpPermission = me.ryanhamshire.GriefPrevention.ClaimPermission.Access; + break; + case EDIT: + gpPermission = me.ryanhamshire.GriefPrevention.ClaimPermission.Edit; + break; + } + claim.setPermission(player.toString(), gpPermission); + } + + @Override + public void removeManager(UUID player) { + claim.managers.remove(player.toString()); + } + + @Override + public UUID getOwner() { + return claim.ownerID; + } + + @Override + public String getOwnerName() { + return getOwner() != null ? Bukkit.getPlayer(getOwner()).getName() : RealEstate.instance.messages.keywordTheServer; + } + + @Override + public void setInheritPermissions(boolean inherit) { + claim.setSubclaimRestrictions(!inherit); + } + + @Override + public void clearPlayerPermissions() { + claim.clearPermissions(); + } + + @Override + public void addManager(UUID player) { + claim.managers.add(player.toString()); + } + + @Override + public void clearManagers() { + claim.managers.clear(); + } + +} diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/GPPlayerData.java b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/GPPlayerData.java new file mode 100644 index 0000000..a1e0ed1 --- /dev/null +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/GPPlayerData.java @@ -0,0 +1,40 @@ +package me.EtienneDx.RealEstate.ClaimAPI.GriefPrevention; + +import me.EtienneDx.RealEstate.ClaimAPI.IPlayerData; +import me.ryanhamshire.GriefPrevention.PlayerData; + +public class GPPlayerData implements IPlayerData +{ + private PlayerData playerData; + + public GPPlayerData(PlayerData playerData) + { + this.playerData = playerData; + } + + @Override + public int getAccruedClaimBlocks() { + return playerData.getAccruedClaimBlocks(); + } + + @Override + public int getBonusClaimBlocks() { + return playerData.getBonusClaimBlocks(); + } + + @Override + public void setAccruedClaimBlocks(int accruedClaimBlocks) { + playerData.setAccruedClaimBlocks(accruedClaimBlocks); + } + + @Override + public void setBonusClaimBlocks(int bonusClaimBlocks) { + playerData.setBonusClaimBlocks(bonusClaimBlocks); + } + + @Override + public int getRemainingClaimBlocks() { + return playerData.getRemainingClaimBlocks(); + } + +} diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/GriefPreventionAPI.java b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/GriefPreventionAPI.java new file mode 100644 index 0000000..387f011 --- /dev/null +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/GriefPreventionAPI.java @@ -0,0 +1,48 @@ +package me.EtienneDx.RealEstate.ClaimAPI.GriefPrevention; + +import java.util.UUID; + +import org.bukkit.Location; + +import me.EtienneDx.RealEstate.ClaimAPI.IClaim; +import me.EtienneDx.RealEstate.ClaimAPI.IClaimAPI; +import me.EtienneDx.RealEstate.ClaimAPI.IPlayerData; +import me.ryanhamshire.GriefPrevention.GriefPrevention; +import me.ryanhamshire.GriefPrevention.Claim; + +public class GriefPreventionAPI implements IClaimAPI +{ + @Override + public IClaim getClaimAt(Location location) { + Claim gpclaim = GriefPrevention.instance.dataStore.getClaimAt(location, false, null); + + if (gpclaim == null) { + return null; + } + + return new GPClaim(gpclaim); + } + + @Override + public void saveClaim(IClaim claim) { + if(claim instanceof GPClaim) + GriefPrevention.instance.dataStore.saveClaim(((GPClaim) claim).getClaim()); + } + + @Override + public IPlayerData getPlayerData(UUID player) { + return new GPPlayerData(GriefPrevention.instance.dataStore.getPlayerData(player)); + } + + @Override + public void changeClaimOwner(IClaim claim, UUID newOwner) { + if(claim instanceof GPClaim) + GriefPrevention.instance.dataStore.changeClaimOwner(((GPClaim) claim).getClaim(), newOwner); + } + + @Override + public void registerEvents() { + new ClaimPermissionListener().registerEvents(); + } + +} diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/IClaim.java b/src/me/EtienneDx/RealEstate/ClaimAPI/IClaim.java new file mode 100644 index 0000000..6ee3f4e --- /dev/null +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/IClaim.java @@ -0,0 +1,28 @@ +package me.EtienneDx.RealEstate.ClaimAPI; + +import java.util.UUID; +import org.bukkit.World; + +public interface IClaim { + public String getId(); + public int getArea(); + public World getWorld(); + public int getX(); + public int getY(); + public int getZ(); + public boolean isAdminClaim(); + public Iterable getChildren(); + public boolean isWilderness(); + public boolean isSubClaim(); + public boolean isParentClaim(); + public IClaim getParent(); + public void dropPlayerPermissions(UUID player); + public void addPlayerPermissions(UUID player, ClaimPermission permission); + public void clearPlayerPermissions(); + public void removeManager(UUID player); + public void addManager(UUID player); + public void clearManagers(); + public UUID getOwner(); + public String getOwnerName(); + public void setInheritPermissions(boolean inherit); +} diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/IClaimAPI.java b/src/me/EtienneDx/RealEstate/ClaimAPI/IClaimAPI.java new file mode 100644 index 0000000..cb5ce20 --- /dev/null +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/IClaimAPI.java @@ -0,0 +1,12 @@ +package me.EtienneDx.RealEstate.ClaimAPI; + +import java.util.UUID; +import org.bukkit.Location; + +public interface IClaimAPI { + public IClaim getClaimAt(Location location); + public void saveClaim(IClaim claim); + public IPlayerData getPlayerData(UUID player); + public void changeClaimOwner(IClaim claim, UUID newOwner); + public void registerEvents(); +} diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/IPlayerData.java b/src/me/EtienneDx/RealEstate/ClaimAPI/IPlayerData.java new file mode 100644 index 0000000..1ecb5af --- /dev/null +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/IPlayerData.java @@ -0,0 +1,9 @@ +package me.EtienneDx.RealEstate.ClaimAPI; + +public interface IPlayerData { + public int getAccruedClaimBlocks(); + public int getBonusClaimBlocks(); + public void setAccruedClaimBlocks(int accruedClaimBlocks); + public void setBonusClaimBlocks(int bonusClaimBlocks); + public int getRemainingClaimBlocks(); +} diff --git a/src/me/EtienneDx/RealEstate/ClaimEvents.java b/src/me/EtienneDx/RealEstate/ClaimEvents.java new file mode 100644 index 0000000..209626e --- /dev/null +++ b/src/me/EtienneDx/RealEstate/ClaimEvents.java @@ -0,0 +1,55 @@ +package me.EtienneDx.RealEstate; + +import org.bukkit.entity.Player; + +import me.EtienneDx.RealEstate.ClaimAPI.ClaimPermission; +import me.EtienneDx.RealEstate.ClaimAPI.IClaim; +import me.EtienneDx.RealEstate.Transactions.BoughtTransaction; +import me.EtienneDx.RealEstate.Transactions.Transaction; + +public class ClaimEvents { + public static String onClaimPermission(IClaim claim, Player player, ClaimPermission permission) { + Transaction transaction = RealEstate.transactionsStore.getTransaction(claim); + // we only have to remove the owner's access, the rest is handled by GP + if (transaction != null && + (player.getUniqueId().equals(transaction.getOwner()) || + (claim.isAdminClaim() && player.hasPermission("griefprevention.adminclaims"))) + && + transaction instanceof BoughtTransaction && + ((BoughtTransaction) transaction).getBuyer() != null) { + switch (permission) { + case EDIT: + return Messages.getMessage(RealEstate.instance.messages.msgErrorClaimInTransactionCantEdit); + case ACCESS: + return Messages.getMessage(RealEstate.instance.messages.msgErrorClaimInTransactionCantAccess); + case BUILD: + return Messages.getMessage(RealEstate.instance.messages.msgErrorClaimInTransactionCantBuild); + case CONTAINER: + return Messages.getMessage(RealEstate.instance.messages.msgErrorClaimInTransactionCantInventory); + case MANAGE: + return Messages.getMessage(RealEstate.instance.messages.msgErrorClaimInTransactionCantManage); + } + } + + if (permission == ClaimPermission.EDIT || permission == ClaimPermission.MANAGE) { + for (IClaim child : claim.getChildren()) { + Transaction tr = RealEstate.transactionsStore.getTransaction(child); + if (tr != null && + tr instanceof BoughtTransaction && + ((BoughtTransaction) tr).getBuyer() != null) { + return Messages.getMessage(RealEstate.instance.messages.msgErrorSubclaimInTransaction); + } + } + } + return null; + } + + public static void onClaimDeleted(IClaim claim) { + Transaction tr = RealEstate.transactionsStore.getTransaction(claim); + if(tr != null) tr.tryCancelTransaction(null, true); + for (IClaim child : claim.getChildren()) { + tr = RealEstate.transactionsStore.getTransaction(child); + if(tr != null) tr.tryCancelTransaction(null, true); + } + } +} diff --git a/src/me/EtienneDx/RealEstate/ClaimPermissionListener.java b/src/me/EtienneDx/RealEstate/ClaimPermissionListener.java deleted file mode 100644 index 898e437..0000000 --- a/src/me/EtienneDx/RealEstate/ClaimPermissionListener.java +++ /dev/null @@ -1,107 +0,0 @@ -package me.EtienneDx.RealEstate; - -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.plugin.PluginManager; - -import me.EtienneDx.RealEstate.Transactions.BoughtTransaction; -import me.EtienneDx.RealEstate.Transactions.Transaction; -import me.ryanhamshire.GriefPrevention.Claim; -import me.ryanhamshire.GriefPrevention.ClaimPermission; -import me.ryanhamshire.GriefPrevention.events.ClaimDeletedEvent; -import me.ryanhamshire.GriefPrevention.events.ClaimPermissionCheckEvent; - -public class ClaimPermissionListener implements Listener { - void registerEvents() - { - PluginManager pm = RealEstate.instance.getServer().getPluginManager(); - - pm.registerEvents(this, RealEstate.instance); - } - - @EventHandler - public void onClaimPermission(ClaimPermissionCheckEvent event) { - if (event == null || event.getClaim() == null || event.getDenialReason() == null) { - return; - } - - // Retrieve the active transaction for this claim - Transaction transaction = RealEstate.transactionsStore.getTransaction(event.getClaim()); - - // Ensure transaction exists and player is valid - if (transaction == null || event.getCheckedPlayer() == null || event.getCheckedUUID() == null) { - return; - } - - // Debugging: Log transaction type - RealEstate.instance.getLogger().info("Transaction found: " + transaction.getClass().getSimpleName()); - - // Check if the player is the owner or has admin permissions - boolean isOwner = event.getCheckedUUID().equals(transaction.getOwner()); - boolean isAdmin = event.getClaim().isAdminClaim() && event.getCheckedPlayer().hasPermission("griefprevention.adminclaims"); - - if (RealEstate.instance.config.DebugMode) { - RealEstate.instance.getLogger().info("isOwner: " + isOwner); - RealEstate.instance.getLogger().info("isAdmin: " + isAdmin); - RealEstate.instance.getLogger().info("isAdminClaim: " + event.getClaim().isAdminClaim()); - RealEstate.instance.getLogger().info("Player has GP.AdminClaims: " + event.getCheckedPlayer().hasPermission("griefprevention.adminclaims")); - RealEstate.instance.getLogger().info("User Who Triggered: " + event.getCheckedUUID()); - RealEstate.instance.getLogger().info("Reason for Denial: " + event.getDenialReason()); - } - - // Ensure it's a valid "BoughtTransaction" with an actual buyer, but NOT a rental - if ((isOwner || isAdmin) && transaction instanceof BoughtTransaction) { - BoughtTransaction boughtTransaction = (BoughtTransaction) transaction; - if (boughtTransaction.getBuyer() != null) { - switch (event.getRequiredPermission()) { - case Edit: - event.setDenialReason(() -> Messages.getMessage(RealEstate.instance.messages.msgErrorClaimInTransactionCantEdit)); - break; - case Access: - event.setDenialReason(() -> Messages.getMessage(RealEstate.instance.messages.msgErrorClaimInTransactionCantAccess)); - break; - case Build: - event.setDenialReason(() -> Messages.getMessage(RealEstate.instance.messages.msgErrorClaimInTransactionCantBuild)); - break; - case Inventory: - event.setDenialReason(() -> Messages.getMessage(RealEstate.instance.messages.msgErrorClaimInTransactionCantInventory)); - break; - case Manage: - event.setDenialReason(() -> Messages.getMessage(RealEstate.instance.messages.msgErrorClaimInTransactionCantManage)); - break; - default: - break; - } - } - } - - // Check if permission is Edit or Manage and deny access to subclaims in transactions - if (event.getRequiredPermission() == ClaimPermission.Edit || event.getRequiredPermission() == ClaimPermission.Manage) { - for (Claim child : event.getClaim().children) { - if (child == null) continue; // Avoid potential null references - - Transaction childTransaction = RealEstate.transactionsStore.getTransaction(child); - if (childTransaction instanceof BoughtTransaction) { - BoughtTransaction boughtTransaction = (BoughtTransaction) childTransaction; - if (boughtTransaction.getBuyer() != null) { - event.setDenialReason(() -> Messages.getMessage(RealEstate.instance.messages.msgErrorSubclaimInTransaction)); - return; // Stop further checks after setting a denial reason - } - } - } - } - } - - - - // more of a safety measure, normally it shouldn't be needed - @EventHandler - public void onClaimDeleted(ClaimDeletedEvent event) { - Transaction tr = RealEstate.transactionsStore.getTransaction(event.getClaim()); - if(tr != null) tr.tryCancelTransaction(null, true); - for (Claim child : event.getClaim().children) { - tr = RealEstate.transactionsStore.getTransaction(child); - if(tr != null) tr.tryCancelTransaction(null, true); - } - } -} diff --git a/src/me/EtienneDx/RealEstate/Config.java b/src/me/EtienneDx/RealEstate/Config.java index 6d7e716..b5a5076 100644 --- a/src/me/EtienneDx/RealEstate/Config.java +++ b/src/me/EtienneDx/RealEstate/Config.java @@ -11,9 +11,8 @@ import me.EtienneDx.AnnotationConfig.ConfigField; import me.EtienneDx.AnnotationConfig.ConfigFile; -@ConfigFile(header = "RealEstate wiki and newest versions are available at https://github.com/msburgess3200/RealEstate") -public class Config extends AnnotationConfig -{ +@ConfigFile(header = "RealEstate wiki and newest versions are available at http://www.github.com/msburgess3200/RealEstate") +public class Config extends AnnotationConfig { public PluginDescriptionFile pdf; public final String configFilePath = RealEstate.pluginDirPath + "config.yml"; @@ -21,26 +20,29 @@ public class Config extends AnnotationConfig @ConfigField(name="RealEstate.Keywords.ChatPrefix", comment="What is displayed before any chat message") public String chatPrefix = "$f[$6RealEstate$f] "; - + @ConfigField(name="RealEstate.Keywords.SignsHeader", comment = "What is displayed in top of the signs") public String cfgSignsHeader = "$6[RealEstate]"; - //public List cfgSigns; - @ConfigField(name="RealEstate.Keywords.Sell", comment = "List of all possible possible signs headers to sell a claim") + @ConfigField(name="RealEstate.Keywords.Sell", comment = "List of all possible signs headers to sell a claim") public List cfgSellKeywords = Arrays.asList("[sell]", "[sell claim]", "[sc]", "[re]", "[realestate]"); - @ConfigField(name="RealEstate.Keywords.Rent", comment = "List of all possible possible signs headers to rent a claim") + @ConfigField(name="RealEstate.Keywords.Rent", comment = "List of all possible signs headers to rent a claim") public List cfgRentKeywords = Arrays.asList("[rent]", "[rent claim]", "[rc]"); - @ConfigField(name="RealEstate.Keywords.ContainerRent", comment = "List of all possible possible signs headers to rent a claim") + @ConfigField(name="RealEstate.Keywords.ContainerRent", comment = "List of all possible signs headers to rent container access only") public List cfgContainerRentKeywords = Arrays.asList("[container rent]", "[crent]"); - @ConfigField(name="RealEstate.Keywords.Lease", comment = "List of all possible possible signs headers to lease a claim") + @ConfigField(name="RealEstate.Keywords.Lease", comment = "List of all possible signs headers to lease a claim") public List cfgLeaseKeywords = Arrays.asList("[lease]", "[lease claim]", "[lc]"); + @ConfigField(name="RealEstate.Keywords.Auction", comment = "List of all possible signs headers to auction a claim") + public List cfgAuctionKeywords = Arrays.asList("[auction]", "[auction claim]", "[ac]"); - @ConfigField(name="RealEstate.Keywords.Replace.Sell", comment = "What is displayed on signs for preoperties to sell") + @ConfigField(name="RealEstate.Keywords.Replace.Sell", comment = "What is displayed on signs for properties to sell") public String cfgReplaceSell = "FOR SALE"; - @ConfigField(name="RealEstate.Keywords.Replace.Rent", comment = "What is displayed on signs for preoperties to rent") + @ConfigField(name="RealEstate.Keywords.Replace.Rent", comment = "What is displayed on signs for properties to rent") public String cfgReplaceRent = "FOR RENT"; - @ConfigField(name="RealEstate.Keywords.Replace.Lease", comment = "What is displayed on signs for preoperties to lease") + @ConfigField(name="RealEstate.Keywords.Replace.Lease", comment = "What is displayed on signs for properties to lease") public String cfgReplaceLease = "FOR LEASE"; + @ConfigField(name="RealEstate.Keywords.Replace.Auction", comment = "What is displayed on signs for properties to auction") + public String cfgReplaceAuction = "FOR AUCTION"; @ConfigField(name="RealEstate.Keywords.Replace.Ongoing.Rent", comment = "What is displayed on the first line of the sign once someone rents a claim.") public String cfgReplaceOngoingRent = "[Rented]"; @ConfigField(name="RealEstate.Keywords.Replace.ContainerRent", comment = "What is displayed on the third line of the sign when renting container access only.") @@ -52,17 +54,22 @@ public class Config extends AnnotationConfig public boolean cfgEnableRent = true; @ConfigField(name="RealEstate.Rules.Lease", comment = "Is leasing claims enabled?") public boolean cfgEnableLease = true; + @ConfigField(name="RealEstate.Rules.Auction", comment = "Is auctioning claims enabled?") + public boolean cfgEnableAuction = true; + + @ConfigField(name="RealEstate.Rules.CancelAuction", comment = "Can an auctioneer cancel his auction if he already received offers?") + public boolean cfgCancelAuction = false; + @ConfigField(name="RealEstate.Rules.DisableOutbidSelf", comment = "Can an auctioneer outbid himself?") + public boolean cfgDisableOutbidSelf = false; @ConfigField(name="RealEstate.Rules.AutomaticRenew", comment = "Can players renting claims enable automatic renew of their contracts?") public boolean cfgEnableAutoRenew = true; - @ConfigField(name="RealEstate.Rules.RentPeriods", comment = "Can a rent contract last multiple periods?") - public boolean cfgEnableRentPeriod = true; @ConfigField(name="RealEstate.Rules.DestroySigns.Rent", comment = "Should the rent signs get destroyed once the claim is rented?") public boolean cfgDestroyRentSigns = false; @ConfigField(name="RealEstate.Rules.DestroySigns.Lease", comment = "Should the lease signs get destroyed once the claim is leased?") public boolean cfgDestroyLeaseSigns = true; - @ConfigField(name="RealEstate.Rules.TransferClaimBlocks", comment = "Are the claim blocks transfered to the new owner on purchase or should the buyer provide them?") + @ConfigField(name="RealEstate.Rules.TransferClaimBlocks", comment = "Are the claim blocks transferred to the new owner on purchase or should the buyer provide them?") public boolean cfgTransferClaimBlocks = true; @ConfigField(name="RealEstate.Rules.UseCurrencySymbol", comment = "Should the signs display the prices with a currency symbol instead of the full currency name?") @@ -76,77 +83,89 @@ public class Config extends AnnotationConfig public boolean cfgMessageOwner = true; @ConfigField(name="RealEstate.Messaging.MessageBuyer", comment = "Should the buyer get messaged once one of his claim is rented/leased/bought and on end of contracts?") public boolean cfgMessageBuyer = true; - @ConfigField(name="RealEstate.Messaging.BroadcastSell", comment = "Should a message get broadcasted when a player put a claim for rent/lease/sell?") + @ConfigField(name="RealEstate.Messaging.BroadcastSell", comment = "Should a message get broadcasted when a player puts a claim for sale/rent/lease?") public boolean cfgBroadcastSell = true; - @ConfigField(name="RealEstate.Messaging.MailOffline", comment = "Should offline owner/buyers receive mails (using the Essentials plugin) when they're offline?") + @ConfigField(name="RealEstate.Messaging.MailOffline", comment = "Should offline owners/buyers receive mails (using the Essentials plugin) when they're offline?") public boolean cfgMailOffline = true; - @ConfigField(name="RealEstate.Default.PricesPerBlock.Sell", comment = "Chat is the default price per block when selling a claim") + @ConfigField(name="RealEstate.Default.PricesPerBlock.Sell", comment = "The default price per block when selling a claim") public double cfgPriceSellPerBlock = 5.0; - @ConfigField(name="RealEstate.Default.PricesPerBlock.Rent", comment = "Chat is the default price per block when renting a claim") + @ConfigField(name="RealEstate.Default.PricesPerBlock.Rent", comment = "The default price per block when renting a claim") public double cfgPriceRentPerBlock = 2.0; - @ConfigField(name="RealEstate.Default.PricesPerBlock.Lease", comment = "Chat is the default price per block when leasing a claim") + @ConfigField(name="RealEstate.Default.PricesPerBlock.Lease", comment = "The default price per block when leasing a claim") public double cfgPriceLeasePerBlock = 2.0; + @ConfigField(name="RealEstate.Default.PricesPerBlock.Auction", comment = "The default price per block when auctioning a claim") + public double cfgPriceAuctionPerBlock = 1.0; + @ConfigField(name="RealEstate.Default.Prices.AuctionBidStep", comment = "The default bid step when auctioning a claim") + public double cfgPriceAuctionBidStep = 2.0; @ConfigField(name="RealEstate.Default.Duration.Rent", comment = "How long is a rent period by default") public String cfgRentTime = "7D"; @ConfigField(name="RealEstate.Default.Duration.Lease", comment = "How long is a lease period by default") public String cfgLeaseTime = "7D"; + @ConfigField(name="RealEstate.Default.Duration.Auction", comment = "How long is an auction period by default") + public String cfgAuctionTime = "7D"; @ConfigField(name="RealEstate.Default.LeasePaymentsCount", comment = "How many lease periods are required before the buyer gets the claim's ownership by default") public int cfgLeasePayments = 5; - @ConfigField(name="RealEstate.Settings.PageSize", comment = "How many Real Estate offer should be shown by page using the '/re list' command") + @ConfigField(name="RealEstate.Settings.PageSize", comment = "How many Real Estate offers should be shown per page using the '/re list' command") public int cfgPageSize = 8; - @ConfigField(name="RealEstate.Settings.MessagesFiles", comment="Language file to be used. You can see all languages files in the languages directory. If the language file does not exist, it will be created and you'll be able to modify it later on.") - public String languageFile = "en.yml"; + @ConfigField(name="RealEstate.Settings.MessagesFiles", comment="Language file to be used. See the languages directory for available files.") + public String languageFile = "en-us.yml"; - @ConfigField(name="RealEstate.Settings.DebugMode", comment="Enable/Disable Debug Logs") - public boolean DebugMode = true; + @ConfigField(name="RealEstate.Settings.Database.DatabaseType", comment="Database type: MySQL, YML, or SQLite") + public String databaseType = "YML"; - @ConfigField(name="RealEstate.Settings.Database.Type", comment="Database Type: Possible values: Serial, MySQL, SQLite") - public String databaseType = "Serial"; - - @ConfigField(name="RealEstate.Settings.Database.Username", comment="Database Username (MySQL)") - public String Username = ""; - - @ConfigField(name="RealEstate.Settings.Database.Password", comment="Database Password (MySQL)") - public String Password = ""; - - @ConfigField(name="RealEstate.Settings.Database.DatabaseName", comment="Database Name (MySQL)") - public String DatabaseName = ""; + @ConfigField(name="RealEstate.Settings.Database.SQLite.Database", comment="SQLite database name") + public String sqliteDatabase = "RealEstate.db"; + + @ConfigField(name="RealEstate.Settings.Database.MySQL.Host", comment="MySQL database host") + public String mysqlHost = "localhost"; + + @ConfigField(name="RealEstate.Settings.Database.MySQL.Port", comment="MySQL database port") + public int mysqlPort = 3306; + + @ConfigField(name="RealEstate.Settings.Database.MySQL.Database", comment="MySQL database name") + public String mysqlDatabase = "RealEstate"; + + @ConfigField(name="RealEstate.Settings.Database.MySQL.Username", comment="MySQL database username") + public String mysqlUsername = ""; + + @ConfigField(name="RealEstate.Settings.Database.MySQL.Password", comment="MySQL database password") + public String mysqlPassword = ""; - @ConfigField(name="RealEstate.Settings.Database.Port", comment="Database Port (MySQL)") - public int Port = 3306; + @ConfigField(name="RealEstate.Settings.Database.MySQL.Prefix", comment="MySQL database table prefix") + public String mysqlPrefix = "realestate_"; + + @ConfigField(name="RealEstate.Settings.Database.MySQL.UseSSL", comment="MySQL database use SSL") + public boolean mysqlUseSSL = false; + + @ConfigField(name="RealEstate.Settings.Database.MySQL.AutoReconnect", comment="MySQL database auto reconnect") + public boolean mysqlAutoReconnect = true; - public Config() - { + public Config() { this.pdf = RealEstate.instance.getDescription(); } - public String getString(List li) - { - return String.join(";", li); + public String getString(List li) { + return String.join(";", li); } - public List getList(String str) - { - return Arrays.asList(str.split(";")); + public List getList(String str) { + return Arrays.asList(str.split(";")); } - List getConfigList(YamlConfiguration config, String path, List defVal) - { - config.addDefault(path, defVal); - List ret = config.getStringList(path); - ret.replaceAll(String::toLowerCase); - return ret; + List getConfigList(YamlConfiguration config, String path, List defVal) { + config.addDefault(path, defVal); + List ret = config.getStringList(path); + ret.replaceAll(String::toLowerCase); + return ret; } @Override - public void loadConfig() - { - //YamlConfiguration config = YamlConfiguration.loadConfiguration(new File(this.configFilePath)); + public void loadConfig() { this.loadConfig(this.configFilePath); } } diff --git a/src/me/EtienneDx/RealEstate/Messages.java b/src/me/EtienneDx/RealEstate/Messages.java index 8132e30..ee351ed 100644 --- a/src/me/EtienneDx/RealEstate/Messages.java +++ b/src/me/EtienneDx/RealEstate/Messages.java @@ -74,6 +74,9 @@ public class Messages extends AnnotationConfig @ConfigField(name="RealEstate.Errors.RentOnly") public String msgErrorRentOnly = "$cThis command only applies to rented claims!"; + + @ConfigField(name="RealEstate.Errors.AuctionOnly") + public String msgErrorAuctionOnly = "$cThis command only applies to auctioned claims!"; @ConfigField(name="RealEstate.Errors.ValueGreaterThanZero") public String msgErrorValueGreaterThanZero = "$cThe value must be greater than zero!"; @@ -120,6 +123,9 @@ public class Messages extends AnnotationConfig @ConfigField(name="RealEstate.Errors.NegativePrice", comment = "0: price") public String msgErrorNegativePrice = "$cThe price must be greater than zero!"; + @ConfigField(name="RealEstate.Errors.NegativeBidStep", comment = "0: bid step") + public String msgErrorNegativeBidStep = "$cThe bid step must be greater than zero!"; + @ConfigField(name="RealEstate.Errors.NonIntegerPrice", comment = "0: price") public String msgErrorNonIntegerPrice = "$cThe price must be an integer!"; @@ -150,6 +156,18 @@ public class Messages extends AnnotationConfig @ConfigField(name="RealEstate.Errors.CantCancelAlreadyRented", comment = "0: claim type") public String msgErrorCantCancelAlreadyRented = "$cThis {0} is currently being rented, you can't cancel the transaction!"; + @ConfigField(name="RealEstate.Errors.CantCancelAuction") + public String msgErrorCantCancelAuction = "$cThis claim is currently being auctioned, you can't cancel the transaction!"; + + @ConfigField(name="RealEstate.Errors.CouldntReimburseSelf", comment = "0: formatted price") + public String msgErrorCouldntReimburseSelf = "$cCould not reimburse you, refunding {0}!"; + + @ConfigField(name="RealEstate.Errors.CouldntReimburseOther", comment = "0: formatted price") + public String msgErrorCouldntReimburseOther = "$cCould not reimburse {0} to another player, the action has been cancelled!"; + + @ConfigField(name="RealEstate.Errors.ContactAdmin") + public String msgErrorContactAdmin = "$cAn unexpected error occured, please contact an admin to resolve this issue!"; + @ConfigField(name="RealEstate.Errors.AutoRenew.Disabled") public String msgErrorAutoRenewDisabled = "$cAutomatic renew is disabled!"; @@ -192,6 +210,9 @@ public class Messages extends AnnotationConfig @ConfigField(name="RealEstate.Errors.Sign.RentingDisabled") public String msgErrorSignRentingDisabled = "$cRenting is disabled!"; + @ConfigField(name="RealEstate.Errors.Sign.AuctionDisabled") + public String msgErrorSignAuctionDisabled = "$cAuctioning is disabled!"; + @ConfigField(name="RealEstate.Errors.Sign.NoSellPermission", comment = "0: claim type") public String msgErrorSignNoSellPermission = "$cYou don't have permission to sell this {0}!"; @@ -201,6 +222,9 @@ public class Messages extends AnnotationConfig @ConfigField(name="RealEstate.Errors.Sign.NoRentPermission", comment = "0: claim type") public String msgErrorSignNoRentPermission = "$cYou don't have permission to rent this {0}!"; + @ConfigField(name="RealEstate.Errors.Sign.NoAuctionPermission", comment = "0: claim type") + public String msgErrorSignNoAuctionPermission = "$cYou don't have permission to auction this {0}!"; + @ConfigField(name="RealEstate.Errors.Sign.NoAdminSellPermission", comment = "0: claim type") public String msgErrorSignNoAdminSellPermission = "$cYou don't have permission to sell this admin {0}!"; @@ -210,6 +234,9 @@ public class Messages extends AnnotationConfig @ConfigField(name="RealEstate.Errors.Sign.NoAdminRentPermission", comment = "0: claim type") public String msgErrorSignNoAdminRentPermission = "$cYou don't have permission to rent this admin {0}!"; + @ConfigField(name="RealEstate.Errors.Sign.NoAdminAuctionPermission", comment = "0: claim type") + public String msgErrorSignNoAdminAuctionPermission = "$cYou don't have permission to auction this admin {0}!"; + @ConfigField(name="RealEstate.Errors.Sign.NotOwner", comment = "0: claim type") public String msgErrorSignNotOwner = "$cYou can only sell/rent/lease {0} you own!"; @@ -225,6 +252,9 @@ public class Messages extends AnnotationConfig @ConfigField(name="RealEstate.Errors.Claim.DoesNotExist") public String msgErrorClaimDoesNotExist = "$cThis claim does not exist!"; + @ConfigField(name="RealEstate.Errors.Claim.DoesNotExistAuction") + public String msgErrorClaimDoesNotExistAuction = "$cThis auctioned claim does not exist!"; + @ConfigField(name="RealEstate.Errors.Claim.AlreadyOwner", comment = "0: claim type") public String msgErrorClaimAlreadyOwner = "$cYou are already the owner of this {0}!"; @@ -237,6 +267,9 @@ public class Messages extends AnnotationConfig @ConfigField(name="RealEstate.Errors.Claim.NotRentedByOwner", comment = "0: claim type") public String msgErrorClaimNotRentedByOwner = "$cThis {0} is not rented by its owner!"; + @ConfigField(name="RealEstate.Errors.Claim.NotAuctionedByOwner", comment = "0: claim type") + public String msgErrorClaimNotAuctionedByOwner = "$cThis {0} is not auctioned by its owner!"; + @ConfigField(name="RealEstate.Errors.Claim.NoBuyPermission", comment = "0: claim type") public String msgErrorClaimNoBuyPermission = "$cYou don't have permission to buy this {0}!"; @@ -246,18 +279,30 @@ public class Messages extends AnnotationConfig @ConfigField(name="RealEstate.Errors.Claim.NoRentPermission", comment = "0: claim type") public String msgErrorClaimNoRentPermission = "$cYou don't have permission to rent this {0}!"; + @ConfigField(name="RealEstate.Errors.Claim.NoAuctionPermission", comment = "0: claim type") + public String msgErrorClaimNoAuctionPermission = "$cYou don't have permission to auction this {0}!"; + @ConfigField(name="RealEstate.Errors.Claim.AlreadyLeased", comment = "0: claim type") public String msgErrorClaimAlreadyLeased = "$cThis {0} is already leased!"; @ConfigField(name="RealEstate.Errors.Claim.AlreadyRented", comment = "0: claim type") public String msgErrorClaimAlreadyRented = "$cThis {0} is already rented!"; + @ConfigField(name="RealEstate.Errors.Claim.AlreadyHighestBidder", comment = "0: claim type") + public String msgErrorClaimAlreadyHighestBidder = "$cYou are already the highest bidder of this {0}!"; + @ConfigField(name="RealEstate.Errors.Claim.NoInfoPermission") public String msgErrorClaimNoInfoPermission = "$cYou don't have permission to view this real estate informations!"; @ConfigField(name="RealEstate.Errors.Claim.NoClaimBlocks", comment = "0: area; 1: claim blocks remaining; 2: missing claim blocks") public String msgErrorClaimNoClaimBlocks = "$cYou don't have enough claim blocks! You need $a{2}$c more claim blocks to claim this area. The claim requires $a{0}$c claim blocks, you only have $a{1}$c claim blocks left."; + @ConfigField(name="RealEstate.Errors.Auction.CouldntPayOwner") + public String msgErrorAuctionCouldntPayOwner = "$cCouldn't pay the owner of this auction! The auction is being cancelled."; + + @ConfigField(name="RealEstate.Errors.Auction.CouldntReceiveOwner") + public String msgErrorAuctionCouldntReceiveOwner = "$cCouldn't receive the payment of this auction! The auction is being cancelled."; + @ConfigField(name="RealEstate.Info.ExitOffer.None") public String msgInfoExitOfferNone = "$bThere is currently no exit offer for this claim!"; @@ -357,12 +402,6 @@ public class Messages extends AnnotationConfig @ConfigField(name="RealEstate.Info.Claim.Info.Rent.GeneralBuyer", comment = "0: claim type, 1: buyer name, 2: formatted price, 3: time left in current period, 4: duration of a period") public String msgInfoClaimInfoGeneralRentBuyer = "$bThis {0} is currently rented by $a{1}$b for $a{2}$b. The {0} is rented for another $a{3}$b. The rent period is $a{4}"; - @ConfigField(name="RealEstate.Info.Claim.Info.Rent.MaxPeriod", comment = "0: max periods") - public String msgInfoClaimInfoRentMaxPeriod = "$bIt can be rented for a maximum of $a{0}$b periods."; - - @ConfigField(name="RealEstate.Info.Claim.Info.Rent.RemainingPeriods", comment = "0: periods left") - public String msgInfoClaimInfoRentRemainingPeriods = "$bThe contract will end after another $a{0}$b periods."; - @ConfigField(name="RealEstate.Info.Claim.Info.Rent.AutoRenew", comment = "0: enabled / disabled") public String msgInfoClaimInfoRentAutoRenew = "$bAutomatic renew is currently $a{0}$b."; @@ -393,6 +432,30 @@ public class Messages extends AnnotationConfig @ConfigField(name="RealEstate.Info.Claim.Info.Sell.Oneline", comment = "0: claim area, 1: location, 2: formatted price") public String msgInfoClaimInfoSellOneline = "$2{0} $bblocks to $2Sell $bat $2{1} $bfor $a{2}"; + @ConfigField(name="RealEstate.Info.Claim.Info.Auction.Header") + public String msgInfoClaimInfoAuctionHeader = "$9-----= $f[$6RealEstate Auction Info$f]$9 =-----"; + + @ConfigField(name="RealEstate.Info.Claim.Info.Auction.NoBidder", comment = "0: claim type, 1: formatted price") + public String msgInfoClaimInfoAuctionNoBidder = "$bThis {0} is currently being auctioned for $a{1}$b."; + + @ConfigField(name="RealEstate.Info.Claim.Info.Auction.HighestBidder", comment = "0: claim type, 1: bidder name, 2: formatted price") + public String msgInfoClaimInfoAuctionHighestBidder = "$bThis {0} is currently being auctioned. The highest bidder is $a{1}$b for $a{2}$b."; + + @ConfigField(name="RealEstate.Info.Claim.Info.Auction.TimeRemaining", comment = "0: time remaining") + public String msgInfoClaimInfoAuctionTimeRemaining = "$bThe auction will end in $a{0}$b."; + + @ConfigField(name="RealEstate.Info.Claim.Info.Auction.BidStep", comment = "0: Bid Step") + public String msgInfoClaimInfoAuctionBidStep = "$bThe bid step is $a{0}$b."; + + @ConfigField(name="RealEstate.Info.Claim.Info.Auction.Oneline", comment = "0: claim area, 1: location, 2: formatted price, 3: time remaining, 4: bid step") + public String msgInfoClaimInfoAuctionOneline = "$2{0} $bblocks to $2Auction $bat $2{1}$b. Current highest bid is $a{2}$b. The auction will end in $a{3}$b. The bid step is $a{4}"; + + @ConfigField(name="RealEstate.Info.Claim.Info.Auction.Ended", comment = "0: claim type") + public String msgInfoClaimInfoAuctionEnded = "$bThe auction for the {0} has ended."; + + @ConfigField(name="RealEstate.Info.Claim.Info.Auction.Cancelled", comment = "0: claim type") + public String msgInfoClaimInfoAuctionCancelled = "$bThe auction for the {0} has been cancelled. You have been reimbursed."; + @ConfigField(name="RealEstate.Info.Claim.Info.Owner", comment = "0: owner name") public String msgInfoClaimInfoOwner = "$bThe current owner is $a{0}"; @@ -411,6 +474,9 @@ public class Messages extends AnnotationConfig @ConfigField(name="RealEstate.Info.Claim.Created.Rent", comment = "0: claim prefix, 1: claim type, 2: formatted price, 3: duration") public String msgInfoClaimCreatedRent = "$bYou have successfully created {0} {1} rent for $a{2}$b per $a{3}"; + @ConfigField(name="RealEstate.Info.Claim.Created.Auction", comment = "0: claim prefix, 1: claim type, 2: formatted price, 3: formatted bid step, 4: time remaining") + public String msgInfoClaimCreatedAuction = "$bYou have successfully created {0} {1} auction for $a{2}$b. The bid step is $a{3}$b. The auction will end in $a{4}"; + @ConfigField(name="RealEstate.Info.Claim.Created.SellBroadcast", comment = "0: player name, 1: claim prefix, 2: claim type, 3: formatted price") public String msgInfoClaimCreatedSellBroadcast = "$a{0} $bhas created {1} {2} sale for $a{3}"; @@ -420,12 +486,29 @@ public class Messages extends AnnotationConfig @ConfigField(name="RealEstate.Info.Claim.Created.RentBroadcast", comment = "0: player name, 1: claim prefix, 2: claim type, 3: formatted price, 4: duration") public String msgInfoClaimCreatedRentBroadcast = "$a{0} $bhas created {1} {2} rent for $a{3}$b per $a{4}"; + @ConfigField(name="RealEstate.Info.Claim.Created.AuctionBroadcast", comment = "0: player name, 1: claim prefix, 2: claim type, 3: formatted price, 4: formatted bid step, 5: time remaining") + public String msgInfoClaimCreatedAuctionBroadcast = "$a{0} $bhas created {1} {2} auction for $a{3}$b. The bid step is $a{4}$b. The auction will end in $a{5}"; + @ConfigField(name="RealEstate.List.Header", comment = "0: RE Offers|Sell Offers|Rent Offers|Lease Offers; 1: Page number; 2: Page count") public String msgListTransactionsHeader = "$1----= $f[ $6{0} page $2 {1} $6/ $2{2} $f] $1=----"; @ConfigField(name="RealEstate.List.NextPage", comment="0: all|sell|rent|lease; 1: next page number") public String msgListNextPage = "$6To see the next page, type $a/re list {0} {1}"; - + + @ConfigField(name="RealEstate.Sign.Auction.HighestBidder", comment="0: player name, 1: formatted price") + public String msgSignAuctionHighestBidder = "$b{0}: $a{1}"; + + @ConfigField(name="RealEstate.Sign.Auction.NoBider") + public String msgSignAuctionNoBider = "$bNo bidder"; + + @ConfigField(name="RealEstate.Sign.Auction.RemainingTime", comment="0: formatted time") + public String msgSignAuctionRemainingTime = "$b$a{0}"; + + @ConfigField(name="RealEstate.Sign.Auction.Ended") + public String msgSignAuctionEnded = "$bAuction ended"; + + @ConfigField(name="RealEstate.Sign.Auction.Won", comment="next line: winner") + public String msgSignAuctionWon = "$bAuction won by"; public Messages() { diff --git a/src/me/EtienneDx/RealEstate/RECommand.java b/src/me/EtienneDx/RealEstate/RECommand.java index 9263537..5ba0061 100644 --- a/src/me/EtienneDx/RealEstate/RECommand.java +++ b/src/me/EtienneDx/RealEstate/RECommand.java @@ -23,12 +23,12 @@ import co.aikar.commands.annotation.Optional; import co.aikar.commands.annotation.Subcommand; import co.aikar.commands.annotation.Syntax; +import me.EtienneDx.RealEstate.ClaimAPI.IClaim; import me.EtienneDx.RealEstate.Transactions.BoughtTransaction; +import me.EtienneDx.RealEstate.Transactions.ClaimAuction; import me.EtienneDx.RealEstate.Transactions.ClaimRent; import me.EtienneDx.RealEstate.Transactions.ExitOffer; import me.EtienneDx.RealEstate.Transactions.Transaction; -import me.ryanhamshire.GriefPrevention.Claim; -import me.ryanhamshire.GriefPrevention.GriefPrevention; @CommandAlias("re|realestate") public class RECommand extends BaseCommand @@ -39,10 +39,10 @@ public class RECommand extends BaseCommand public static void info(Player player) { if(RealEstate.transactionsStore.anyTransaction( - GriefPrevention.instance.dataStore.getClaimAt(((Player)player).getLocation(), false, null))) + RealEstate.claimAPI.getClaimAt(((Player)player).getLocation()))) { Transaction tr = RealEstate.transactionsStore.getTransaction( - GriefPrevention.instance.dataStore.getClaimAt(((Player)player).getLocation(), false, null)); + RealEstate.claimAPI.getClaimAt(((Player)player).getLocation())); tr.preview((Player)player); } else @@ -153,9 +153,9 @@ else if(type.equalsIgnoreCase("lease")) public static void renewRent(Player player, @Optional String newStatus) { Location loc = player.getLocation(); - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(loc, false, null); + IClaim claim = RealEstate.claimAPI.getClaimAt(loc); ClaimRent cr = (ClaimRent)RealEstate.transactionsStore.getTransaction(claim); - String claimType = claim.parent == null ? + String claimType = claim.isParentClaim() ? RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; if(!RealEstate.instance.config.cfgEnableAutoRenew) { @@ -246,7 +246,7 @@ public void create(Player player, @Conditions("positiveDouble") Double price) { OfflinePlayer otherP = Bukkit.getOfflinePlayer(other); Location loc = player.getLocation(); - String claimType = GriefPrevention.instance.dataStore.getClaimAt(loc, false, null).parent == null ? + String claimType = RealEstate.claimAPI.getClaimAt(loc).isParentClaim() ? RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; String location = "[" + loc.getWorld().getName() + ", X: " + loc.getBlockX() + ", Y: " + loc.getBlockY() + ", Z: " + loc.getBlockZ() + "]"; @@ -270,9 +270,9 @@ else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) public void accept(Player player) { Location loc = player.getLocation(); - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(loc, false, null); + IClaim claim = RealEstate.claimAPI.getClaimAt(loc); BoughtTransaction bt = (BoughtTransaction)RealEstate.transactionsStore.getTransaction(claim); - String claimType = claim.parent == null ? "claim" : "subclaim"; + String claimType = claim.isParentClaim() ? "claim" : "subclaim"; if(bt.exitOffer == null) { Messages.sendMessage(player, RealEstate.instance.messages.msgErrorExitOfferNone); @@ -306,9 +306,9 @@ else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) } } bt.exitOffer = null; - claim.dropPermission(bt.buyer.toString()); - claim.managers.remove(bt.buyer.toString()); - GriefPrevention.instance.dataStore.saveClaim(claim); + claim.dropPlayerPermissions(bt.buyer); + claim.removeManager(bt.buyer); + RealEstate.claimAPI.saveClaim(claim); bt.buyer = null; bt.update();// eventual cancel is contained in here } @@ -320,9 +320,9 @@ else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) public void refuse(Player player) { Location loc = player.getLocation(); - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(loc, false, null); + IClaim claim = RealEstate.claimAPI.getClaimAt(loc); BoughtTransaction bt = (BoughtTransaction)RealEstate.transactionsStore.getTransaction(claim); - String claimType = claim.parent == null ? "claim" : "subclaim"; + String claimType = claim.isParentClaim() ? "claim" : "subclaim"; if(bt.exitOffer == null) { Messages.sendMessage(player, RealEstate.instance.messages.msgErrorExitOfferNone); @@ -361,9 +361,9 @@ else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) public void cancel(Player player) { Location loc = player.getLocation(); - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(loc, false, null); + IClaim claim = RealEstate.claimAPI.getClaimAt(loc); BoughtTransaction bt = (BoughtTransaction)RealEstate.transactionsStore.getTransaction(claim); - String claimType = claim.parent == null ? "claim" : "subclaim"; + String claimType = claim.isParentClaim() ? "claim" : "subclaim"; if(bt.exitOffer.offerBy.equals(player.getUniqueId())) { bt.exitOffer = null; @@ -394,6 +394,18 @@ else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) } } } + + @Subcommand("bid") + @Conditions("claimIsAuctioned") + @CommandPermission("realestate.bid") + @Syntax("") + public static void bid(Player player, @Conditions("positiveDouble") double bid) + { + Location loc = player.getLocation(); + IClaim claim = RealEstate.claimAPI.getClaimAt(loc); + ClaimAuction ca = (ClaimAuction)RealEstate.transactionsStore.getTransaction(claim); + ca.bid(player, bid); + } @Subcommand("cancel") @Conditions("claimHasTransaction") @@ -401,7 +413,7 @@ else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) public static void cancelTransaction(Player player) { Location loc = player.getLocation(); - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(loc, false, null); + IClaim claim = RealEstate.claimAPI.getClaimAt(loc); Transaction t = RealEstate.transactionsStore.getTransaction(claim); t.tryCancelTransaction(player, true); } diff --git a/src/me/EtienneDx/RealEstate/REListener.java b/src/me/EtienneDx/RealEstate/REListener.java index 5cf9c92..61b5906 100644 --- a/src/me/EtienneDx/RealEstate/REListener.java +++ b/src/me/EtienneDx/RealEstate/REListener.java @@ -3,10 +3,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.block.Sign; -import org.bukkit.block.sign.Side; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -17,9 +15,8 @@ import org.bukkit.inventory.EquipmentSlot; import org.bukkit.plugin.PluginManager; +import me.EtienneDx.RealEstate.ClaimAPI.IClaim; import me.EtienneDx.RealEstate.Transactions.Transaction; -import me.ryanhamshire.GriefPrevention.Claim; -import me.ryanhamshire.GriefPrevention.GriefPrevention; public class REListener implements Listener { @@ -28,7 +25,6 @@ void registerEvents() PluginManager pm = RealEstate.instance.getServer().getPluginManager(); pm.registerEvents(this, RealEstate.instance); - //RealEstate.instance.getCommand("re").setExecutor(this); } @EventHandler @@ -37,13 +33,14 @@ public void onSignChange(SignChangeEvent event) if(RealEstate.instance.config.cfgSellKeywords.contains(event.getLine(0).toLowerCase()) || RealEstate.instance.config.cfgLeaseKeywords.contains(event.getLine(0).toLowerCase()) || RealEstate.instance.config.cfgRentKeywords.contains(event.getLine(0).toLowerCase()) || - RealEstate.instance.config.cfgContainerRentKeywords.contains(event.getLine(0).toLowerCase())) + RealEstate.instance.config.cfgContainerRentKeywords.contains(event.getLine(0).toLowerCase()) || + RealEstate.instance.config.cfgAuctionKeywords.contains(event.getLine(0).toLowerCase())) { Player player = event.getPlayer(); Location loc = event.getBlock().getLocation(); - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(loc, false, null); - if(claim == null)// must have something to sell + IClaim claim = RealEstate.claimAPI.getClaimAt(loc); + if(claim == null || claim.isWilderness())// must have something to sell { Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNotInClaim); event.setCancelled(true); @@ -57,14 +54,14 @@ public void onSignChange(SignChangeEvent event) event.getBlock().breakNaturally(); return; } - if(RealEstate.transactionsStore.anyTransaction(claim.parent)) + if(RealEstate.transactionsStore.anyTransaction(claim.getParent())) { Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignParentOngoingTransaction); event.setCancelled(true); event.getBlock().breakNaturally(); return; } - for(Claim c : claim.children) + for(IClaim c : claim.getChildren()) { if(RealEstate.transactionsStore.anyTransaction(c)) { @@ -86,8 +83,8 @@ public void onSignChange(SignChangeEvent event) return; } - String type = claim.parent == null ? "claim" : "subclaim"; - String typeDisplay = claim.parent == null ? + String type = claim.isParentClaim() ? "claim" : "subclaim"; + String typeDisplay = claim.isParentClaim() ? RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; if(!RealEstate.perms.has(player, "realestate." + type + ".sell")) { @@ -135,7 +132,7 @@ public void onSignChange(SignChangeEvent event) return; } } - else if(type.equals("claim") && !player.getUniqueId().equals(claim.ownerID))// only the owner may sell his claim + else if(type.equals("claim") && !player.getUniqueId().equals(claim.getOwner()))// only the owner may sell his claim { Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNotOwner, typeDisplay); event.setCancelled(true); @@ -157,8 +154,8 @@ else if(RealEstate.instance.config.cfgRentKeywords.contains(event.getLine(0).toL event.getBlock().breakNaturally(); return; } - String type = claim.parent == null ? "claim" : "subclaim"; - String typeDisplay = claim.parent == null ? + String type = claim.isParentClaim() ? "claim" : "subclaim"; + String typeDisplay = claim.isParentClaim() ? RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; if(!RealEstate.perms.has(player, "realestate." + type + ".rent")) { @@ -211,32 +208,6 @@ else if(RealEstate.instance.config.cfgRentKeywords.contains(event.getLine(0).toL event.getBlock().breakNaturally(); return; } - int rentPeriods = 1; - if(RealEstate.instance.config.cfgEnableRentPeriod) - { - if(event.getLine(3).isEmpty()) - { - event.setLine(3, "1"); - } - try - { - rentPeriods = Integer.parseInt(event.getLine(3)); - } - catch (NumberFormatException e) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorInvalidNumber, event.getLine(3)); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - if(rentPeriods <= 0) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorNegativeNumber, event.getLine(3)); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - } if(claim.isAdminClaim()) { @@ -248,7 +219,7 @@ else if(RealEstate.instance.config.cfgRentKeywords.contains(event.getLine(0).toL return; } } - else if(type.equals("claim") && !player.getUniqueId().equals(claim.ownerID))// only the owner may sell his claim + else if(type.equals("claim") && !player.getUniqueId().equals(claim.getOwner()))// only the owner may sell his claim { Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNotOwner, typeDisplay); event.setCancelled(true); @@ -258,7 +229,7 @@ else if(type.equals("claim") && !player.getUniqueId().equals(claim.ownerID))// o // all should be good, we can create the rent event.setCancelled(true); - RealEstate.transactionsStore.rent(claim, player, price, event.getBlock().getLocation(), duration, rentPeriods, + RealEstate.transactionsStore.rent(claim, player, price, event.getBlock().getLocation(), duration, RealEstate.instance.config.cfgRentKeywords.contains(event.getLine(0).toLowerCase())); } else if(RealEstate.instance.config.cfgLeaseKeywords.contains(event.getLine(0).toLowerCase()))// we want to rent it @@ -270,8 +241,8 @@ else if(RealEstate.instance.config.cfgLeaseKeywords.contains(event.getLine(0).to event.getBlock().breakNaturally(); return; } - String type = claim.parent == null ? "claim" : "subclaim"; - String typeDisplay = claim.parent == null ? + String type = claim.isParentClaim() ? "claim" : "subclaim"; + String typeDisplay = claim.isParentClaim() ? RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; if(!RealEstate.perms.has(player, "realestate." + type + ".lease")) @@ -353,7 +324,7 @@ else if(RealEstate.instance.config.cfgLeaseKeywords.contains(event.getLine(0).to return; } } - else if(type.equals("claim") && !player.getUniqueId().equals(claim.ownerID))// only the owner may sell his claim + else if(type.equals("claim") && !player.getUniqueId().equals(claim.getOwner()))// only the owner may sell his claim { Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNotOwner, typeDisplay); event.setCancelled(true); @@ -365,6 +336,108 @@ else if(type.equals("claim") && !player.getUniqueId().equals(claim.ownerID))// o event.setCancelled(true); RealEstate.transactionsStore.lease(claim, player, price, event.getBlock().getLocation(), frequency, paymentsCount); } + else if(RealEstate.instance.config.cfgAuctionKeywords.contains(event.getLine(0).toLowerCase())) + { + if(!RealEstate.instance.config.cfgEnableAuction) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignAuctionDisabled); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + String type = claim.isParentClaim() ? "claim" : "subclaim"; + String typeDisplay = claim.isParentClaim() ? + RealEstate.instance.messages.keywordClaim : + RealEstate.instance.messages.keywordSubclaim; + if(!RealEstate.perms.has(player, "realestate." + type + ".auction")) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNoAuctionPermission, typeDisplay); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + + // check for a valid price + double price; + try + { + price = getDouble(event, 1, RealEstate.instance.config.cfgPriceAuctionPerBlock * claim.getArea()); + } + catch (NumberFormatException e) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorInvalidNumber, event.getLine(1)); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + if(price <= 0) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorNegativePrice, event.getLine(1)); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + + // check for a valid bid step + double bidStep; + try + { + bidStep = getDouble(event, 2, RealEstate.instance.config.cfgPriceAuctionBidStep); + } + catch (NumberFormatException e) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorInvalidNumber, event.getLine(2)); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + if(bidStep <= 0) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorNegativeBidStep, event.getLine(2)); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + + // check for a valid duration + if(event.getLine(3).isEmpty()) + { + event.setLine(3, RealEstate.instance.config.cfgLeaseTime); + } + int duration = parseDuration(event.getLine(3)); + if(duration == 0) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorInvalidDuration, event.getLine(3), + "10 weeks", + "3 days", + "1 week 3 days"); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + + if(claim.isAdminClaim()) + { + if(!RealEstate.perms.has(player, "realestate.admin"))// admin may sell admin claims + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNoAdminAuctionPermission, typeDisplay); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + } + else if(type.equals("claim") && !player.getUniqueId().equals(claim.getOwner()))// only the owner may sell his claim + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNotOwner, typeDisplay); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + + // all should be good, we can create the auction + event.setCancelled(true); + RealEstate.transactionsStore.auction(claim, player, price, event.getBlock().getLocation(), duration, bidStep); + } } } @@ -386,7 +459,7 @@ private int parseDuration(String line) private double getDouble(SignChangeEvent event, int line, double defaultValue) throws NumberFormatException { - if(event.getLine(line).isEmpty())// if no price precised, make it the default one + if(event.getLine(line).isEmpty())// if no price specified, make it the default one { event.setLine(line, Double.toString(defaultValue)); } @@ -399,14 +472,12 @@ public void onPlayerInteract(PlayerInteractEvent event) if(event.getAction().equals(Action.RIGHT_CLICK_BLOCK) && event.getHand().equals(EquipmentSlot.HAND) && event.getClickedBlock().getState() instanceof Sign) { - Sign sign = (Sign)event.getClickedBlock().getState(); - RealEstate.instance.log.info(sign.getSide(Side.FRONT).getLine(0)); + RealEstateSign s = new RealEstateSign((Sign) event.getClickedBlock().getState()); // it is a real estate sign - if(ChatColor.stripColor(sign.getSide(Side.FRONT).getLine(0)).equalsIgnoreCase(ChatColor.stripColor( - Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)))) + if(s.isRealEstateSign()) { Player player = event.getPlayer(); - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(event.getClickedBlock().getLocation(), false, null); + IClaim claim = RealEstate.claimAPI.getClaimAt(event.getClickedBlock().getLocation()); if(!RealEstate.transactionsStore.anyTransaction(claim)) { @@ -430,8 +501,8 @@ public void onBreakBlock(BlockBreakEvent event) { if(event.getBlock().getState() instanceof Sign) { - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(event.getBlock().getLocation(), false, null); - if(claim != null) + IClaim claim = RealEstate.claimAPI.getClaimAt(event.getBlock().getLocation()); + if(claim != null && !claim.isWilderness()) { Transaction tr = RealEstate.transactionsStore.getTransaction(claim); if(tr != null && event.getBlock().equals(tr.getHolder())) diff --git a/src/me/EtienneDx/RealEstate/RealEstate.java b/src/me/EtienneDx/RealEstate/RealEstate.java index d7fdcc9..b2c26ea 100644 --- a/src/me/EtienneDx/RealEstate/RealEstate.java +++ b/src/me/EtienneDx/RealEstate/RealEstate.java @@ -25,15 +25,18 @@ import co.aikar.commands.BukkitCommandManager; import co.aikar.commands.ConditionFailedException; +import me.EtienneDx.RealEstate.ClaimAPI.IClaim; +import me.EtienneDx.RealEstate.ClaimAPI.IClaimAPI; +import me.EtienneDx.RealEstate.ClaimAPI.GriefDefender.GriefDefenderAPI; +import me.EtienneDx.RealEstate.ClaimAPI.GriefPrevention.GriefPreventionAPI; import me.EtienneDx.RealEstate.Transactions.BoughtTransaction; +import me.EtienneDx.RealEstate.Transactions.ClaimAuction; import me.EtienneDx.RealEstate.Transactions.ClaimLease; import me.EtienneDx.RealEstate.Transactions.ClaimRent; import me.EtienneDx.RealEstate.Transactions.ClaimSell; import me.EtienneDx.RealEstate.Transactions.ExitOffer; import me.EtienneDx.RealEstate.Transactions.Transaction; import me.EtienneDx.RealEstate.Transactions.TransactionsStore; -import me.ryanhamshire.GriefPrevention.Claim; -import me.ryanhamshire.GriefPrevention.GriefPrevention; import net.milkbowl.vault.economy.Economy; import net.milkbowl.vault.permission.Permission; @@ -53,8 +56,10 @@ public class RealEstate extends JavaPlugin public static RealEstate instance = null; public static TransactionsStore transactionsStore = null; + + public static IClaimAPI claimAPI = null; - //@SuppressWarnings("deprecation") + @SuppressWarnings("deprecation") public void onEnable() { RealEstate.instance = this; @@ -63,33 +68,59 @@ public void onEnable() if (checkVault()) { this.log.info("Vault has been detected and enabled."); - if (setupEconomy()) - { - this.log.info("Vault is using " + econ.getName() + " as the economy plugin."); - } - else - { - this.log.warning("No compatible economy plugin detected [Vault]."); - this.log.warning("Disabling plugin."); - getPluginLoader().disablePlugin(this); - return; - } - if (setupPermissions()) - { - this.log.info("Vault is using " + perms.getName() + " for the permissions."); - } - else - { - this.log.warning("No compatible permissions plugin detected [Vault]."); - this.log.warning("Disabling plugin."); - getPluginLoader().disablePlugin(this); - return; - } } + else + { + this.log.info("Vault has not been detected. RealEstate will not work without Vault."); + this.log.info("Disabling RealEstate..."); + getPluginLoader().disablePlugin(this); + return; + } + if (setupEconomy()) + { + this.log.info("Vault is using " + econ.getName() + " as the economy plugin."); + } + else + { + this.log.warning("No compatible economy plugin detected [Vault]."); + this.log.warning("Disabling RealEstate..."); + getPluginLoader().disablePlugin(this); + return; + } + if (setupPermissions()) + { + this.log.info("Vault is using " + perms.getName() + " for the permissions."); + } + else + { + this.log.warning("No compatible permissions plugin detected [Vault]."); + this.log.warning("Disabling RealEstate..."); + getPluginLoader().disablePlugin(this); + return; + } + + if(setupGriefPreventionAPI()) + { + this.log.info("RealEstate is using GriefPrevention as a claim management plugin."); + } + else if(setupGriefDefenderAPI()) + { + this.log.info("RealEstate is using GriefDefender as a claim management plugin."); + } + /**Insert alternate claim APIs here**/ + else + { + this.log.warning("No compatible Claim API detected. Please install GriefPrevention or GriefDefender."); + this.log.warning("Disabling RealEstate..."); + getPluginLoader().disablePlugin(this); + return; + } + if((ess = (Essentials)getServer().getPluginManager().getPlugin("Essentials")) != null) { this.log.info("Found Essentials, using version " + ess.getDescription().getVersion()); } + checkForOldFiles(); this.config = new Config(); this.config.loadConfig();// loads config or default this.config.saveConfig();// save eventual default @@ -97,31 +128,41 @@ public void onEnable() this.messages = new Messages(); this.messages.loadConfig();// loads customizable messages or defaults this.messages.saveConfig();// save eventual default - this.log.info("Customizable messages loaded."); ConfigurationSerialization.registerClass(ClaimSell.class); ConfigurationSerialization.registerClass(ClaimRent.class); ConfigurationSerialization.registerClass(ClaimLease.class); + ConfigurationSerialization.registerClass(ClaimAuction.class); ConfigurationSerialization.registerClass(ExitOffer.class); RealEstate.transactionsStore = new TransactionsStore(); new REListener().registerEvents(); - new ClaimPermissionListener().registerEvents(); manager = new BukkitCommandManager(this); - + manager.enableUnstableAPI("help"); registerConditions(); manager.registerCommand(new RECommand()); - + copyResourcesIntoPluginDirectory(); } - private void registerConditions() + private void checkForOldFiles() { + File oldConfig = new File("plugins" + File.separator + "RealEstate" + File.separator + "transactions.data"); + if(oldConfig.exists()) + { + this.log.info("Found old transactions.data file, reformatting it..."); + // rename old file + oldConfig.renameTo(new File("plugins" + File.separator + "RealEstate" + File.separator + "transactions.yml")); + } + + } + + private void registerConditions() { manager.getCommandConditions().addCondition("inClaim", (context) -> { if(context.getIssuer().isPlayer() && - GriefPrevention.instance.dataStore.getClaimAt(context.getIssuer().getPlayer().getLocation(), false, null) != null) + claimAPI.getClaimAt(context.getIssuer().getPlayer().getLocation()) != null) { return; } @@ -132,8 +173,8 @@ private void registerConditions() { throw new ConditionFailedException(Messages.getMessage(messages.msgErrorPlayerOnly)); } - Claim c = GriefPrevention.instance.dataStore.getClaimAt(context.getIssuer().getPlayer().getLocation(), false, null); - if(c == null) + IClaim c = claimAPI.getClaimAt(context.getIssuer().getPlayer().getLocation()); + if(c == null || c.isWilderness()) { throw new ConditionFailedException(Messages.getMessage(messages.msgErrorOutOfClaim)); } @@ -148,8 +189,8 @@ private void registerConditions() { throw new ConditionFailedException(Messages.getMessage(messages.msgErrorPlayerOnly)); } - Claim c = GriefPrevention.instance.dataStore.getClaimAt(context.getIssuer().getPlayer().getLocation(), false, null); - if(c == null) + IClaim c = claimAPI.getClaimAt(context.getIssuer().getPlayer().getLocation()); + if(c == null || c.isWilderness()) { throw new ConditionFailedException(Messages.getMessage(messages.msgErrorOutOfClaim)); } @@ -168,8 +209,8 @@ else if(tr instanceof BoughtTransaction && ((BoughtTransaction)tr).getBuyer() != { throw new ConditionFailedException(Messages.getMessage(messages.msgErrorPlayerOnly)); } - Claim c = GriefPrevention.instance.dataStore.getClaimAt(context.getIssuer().getPlayer().getLocation(), false, null); - if(c == null) + IClaim c = claimAPI.getClaimAt(context.getIssuer().getPlayer().getLocation()); + if(c == null || c.isWilderness()) { throw new ConditionFailedException(Messages.getMessage(messages.msgErrorOutOfClaim)); } @@ -184,8 +225,8 @@ else if(tr instanceof BoughtTransaction && ((BoughtTransaction)tr).getBuyer() != { throw new ConditionFailedException(Messages.getMessage(messages.msgErrorPlayerOnly)); } - Claim c = GriefPrevention.instance.dataStore.getClaimAt(context.getIssuer().getPlayer().getLocation(), false, null); - if(c == null) + IClaim c = claimAPI.getClaimAt(context.getIssuer().getPlayer().getLocation()); + if(c == null || c.isWilderness()) { throw new ConditionFailedException(Messages.getMessage(messages.msgErrorOutOfClaim)); } @@ -211,8 +252,8 @@ else if(tr instanceof BoughtTransaction && ((BoughtTransaction)tr).getBuyer() != { throw new ConditionFailedException(Messages.getMessage(messages.msgErrorPlayerOnly)); } - Claim c = GriefPrevention.instance.dataStore.getClaimAt(context.getIssuer().getPlayer().getLocation(), false, null); - if(c == null) + IClaim c = claimAPI.getClaimAt(context.getIssuer().getPlayer().getLocation()); + if(c == null || c.isWilderness()) { throw new ConditionFailedException(Messages.getMessage(messages.msgErrorOutOfClaim)); } @@ -237,6 +278,26 @@ else if(tr instanceof BoughtTransaction && ((BoughtTransaction)tr).getBuyer() != if(value > 0) return; throw new ConditionFailedException(Messages.getMessage(messages.msgErrorValueGreaterThanZero)); }); + manager.getCommandConditions().addCondition("claimIsAuctioned", (context) -> { + if(!context.getIssuer().isPlayer()) + { + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorPlayerOnly)); + } + IClaim c = claimAPI.getClaimAt(context.getIssuer().getPlayer().getLocation()); + if(c == null || c.isWilderness()) + { + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorOutOfClaim)); + } + Transaction tr = transactionsStore.getTransaction(c); + if(tr == null) + { + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorNoOngoingTransaction)); + } + if(!(tr instanceof ClaimAuction)) + { + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorAuctionOnly)); + } + }); } public void addLogEntry(String entry) @@ -266,6 +327,26 @@ private boolean checkVault() return vaultPresent; } + private boolean setupGriefPreventionAPI() + { + if(getServer().getPluginManager().getPlugin("GriefPrevention") != null) + { + claimAPI = new GriefPreventionAPI(); + return true; + } + return false; + } + + private boolean setupGriefDefenderAPI() + { + if(getServer().getPluginManager().getPlugin("GriefDefender") != null) + { + claimAPI = new GriefDefenderAPI(); + return true; + } + return false; + } + private boolean setupEconomy() { RegisteredServiceProvider rsp = getServer().getServicesManager().getRegistration(Economy.class); @@ -285,6 +366,7 @@ private boolean setupPermissions() private void copyResourcesIntoPluginDirectory() { + this.log.info("Checking language files..."); Path pluginPath = Paths.get(RealEstate.pluginDirPath); File pluginDirectory = pluginPath.toFile(); if(!pluginDirectory.exists()) @@ -316,8 +398,6 @@ private void copyResourcesIntoPluginDirectory() { Path path = it.next(); Path targetPath = pluginPath.resolve(path.toString().substring(11)); - this.log.info(path.toString()); - this.log.info(targetPath.toString()); if(!targetPath.toFile().exists()) { Files.createDirectories(targetPath.getParent()); @@ -326,6 +406,7 @@ private void copyResourcesIntoPluginDirectory() if(s.available() > 0) { Files.copy(s, targetPath); + this.log.info("Adding language file: " + targetPath.getFileName()); } } catch(NoSuchFileException e) diff --git a/src/me/EtienneDx/RealEstate/RealEstateSign.java b/src/me/EtienneDx/RealEstate/RealEstateSign.java new file mode 100644 index 0000000..3b06040 --- /dev/null +++ b/src/me/EtienneDx/RealEstate/RealEstateSign.java @@ -0,0 +1,37 @@ +package me.EtienneDx.RealEstate; + +import org.bukkit.block.Sign; +import org.bukkit.block.sign.Side; +import org.bukkit.block.sign.SignSide; +import org.bukkit.ChatColor; + +public class RealEstateSign +{ + private Sign sign; + private SignSide front; + + public RealEstateSign(Sign sign) { + this.sign = sign; + this.front = sign.getSide(Side.FRONT); + } + + public void setLine(int line, String value) { + front.setLine(line, value); + } + + public void update(boolean force) { + sign.update(force); + } + + public boolean isRealEstateSign() { + String header = ChatColor.stripColor(Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); + + SignSide front = sign.getSide(Side.FRONT); + + if(ChatColor.stripColor(front.getLine(0)).equalsIgnoreCase(header)) { + return true; + } + return false; + } + +} \ No newline at end of file diff --git a/src/me/EtienneDx/RealEstate/Transactions/BoughtTransaction.java b/src/me/EtienneDx/RealEstate/Transactions/BoughtTransaction.java index a74683e..725e1b9 100644 --- a/src/me/EtienneDx/RealEstate/Transactions/BoughtTransaction.java +++ b/src/me/EtienneDx/RealEstate/Transactions/BoughtTransaction.java @@ -2,67 +2,49 @@ import java.util.Map; import java.util.UUID; - import org.bukkit.Location; -import org.bukkit.block.Sign; +import org.bukkit.configuration.serialization.ConfigurationSerializable; import org.bukkit.entity.Player; +import me.EtienneDx.RealEstate.ClaimAPI.IClaim; -import me.EtienneDx.RealEstate.RealEstate; -import me.ryanhamshire.GriefPrevention.Claim; - -public abstract class BoughtTransaction extends ClaimTransaction -{ - public UUID buyer = null; - public ExitOffer exitOffer = null; - public boolean destroyedSign = false; - - public BoughtTransaction(Map map) - { - super(map); - if(map.get("buyer") != null) - buyer = UUID.fromString((String)map.get("buyer")); - if(map.get("exitOffer") != null) - exitOffer = (ExitOffer) map.get("exitOffer"); - if(map.get("destroyedSign") != null)// may be the case on upgrading from 0.0.1-SNAPSHOT - destroyedSign = (boolean) map.get("destroyedSign"); - } - - public BoughtTransaction(Claim claim, Player player, double price, Location sign) - { - super(claim, player, price, sign); - } - - @Override - public Map serialize() - { - Map map = super.serialize(); - if(buyer != null) - map.put("buyer", buyer.toString()); - if(exitOffer != null) - map.put("exitOffer", exitOffer); - map.put("destroyedSign", destroyedSign); - - return map; - } - - public void destroySign() - { - if((this instanceof ClaimRent && RealEstate.instance.config.cfgDestroyRentSigns) || - (this instanceof ClaimLease && RealEstate.instance.config.cfgDestroyLeaseSigns)) - { - if(!destroyedSign && getHolder().getState() instanceof Sign) - getHolder().breakNaturally(); - destroyedSign = true; - } - } - - public UUID getBuyer() - { - return buyer; - } - - public void setOwner(UUID newOwner) - { - this.owner = newOwner; - } +public abstract class BoughtTransaction extends ClaimTransaction { + public UUID buyer = null; + public ExitOffer exitOffer = null; + public boolean destroyedSign = false; + + public BoughtTransaction(Map map) { + super(map); + if(map.get("buyer") != null) + buyer = UUID.fromString((String) map.get("buyer")); + if(map.get("exitOffer") != null) + exitOffer = (ExitOffer) map.get("exitOffer"); + if(map.get("destroyedSign") != null) + destroyedSign = (boolean) map.get("destroyedSign"); + } + + public BoughtTransaction(IClaim claim, Player player, double price, Location sign) { + super(claim, player, price, sign); + } + + @Override + public Map serialize() { + Map map = super.serialize(); + if(buyer != null) + map.put("buyer", buyer.toString()); + if(exitOffer != null) + map.put("exitOffer", exitOffer); + map.put("destroyedSign", destroyedSign); + return map; + } + + public void destroySign() { + // Example: destroy sign if configured + if(!destroyedSign && getHolder() != null) + getHolder().breakNaturally(); + destroyedSign = true; + } + + public UUID getBuyer() { + return buyer; + } } diff --git a/src/me/EtienneDx/RealEstate/Transactions/ClaimAuction.java b/src/me/EtienneDx/RealEstate/Transactions/ClaimAuction.java new file mode 100644 index 0000000..5e62a4c --- /dev/null +++ b/src/me/EtienneDx/RealEstate/Transactions/ClaimAuction.java @@ -0,0 +1,364 @@ +package me.EtienneDx.RealEstate.Transactions; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Period; +import java.time.format.DateTimeFormatter; +import java.util.Map; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.OfflinePlayer; +import org.bukkit.block.Sign; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import me.EtienneDx.RealEstate.Messages; +import me.EtienneDx.RealEstate.RealEstate; +import me.EtienneDx.RealEstate.RealEstateSign; +import me.EtienneDx.RealEstate.Utils; +import me.EtienneDx.RealEstate.ClaimAPI.IClaim; +import net.md_5.bungee.api.ChatColor; + +public class ClaimAuction extends ClaimTransaction { + + public UUID buyer = null; + public LocalDateTime endDate = null; + public double bidStep = 1; + + public ClaimAuction(Map map) { + super(map); + if(map.get("buyer") != null) + buyer = UUID.fromString((String)map.get("buyer")); + if(map.get("endDate") != null) + endDate = LocalDateTime.parse((String) map.get("endDate"), DateTimeFormatter.ISO_DATE_TIME); + bidStep = (double) map.get("bidStep"); + } + + public ClaimAuction(IClaim claim, Player player, double price, Location sign, LocalDateTime endDate, double bidStep) { + super(claim, player, price, sign); + this.endDate = endDate; + this.bidStep = bidStep; + } + + @Override + public Map serialize() { + Map map = super.serialize(); + if(buyer != null) + map.put("buyer", buyer.toString()); + if(endDate != null) + map.put("endDate", endDate.format(DateTimeFormatter.ISO_DATE_TIME)); + map.put("bidStep", bidStep); + return map; + } + + @Override + public boolean update() { + int days = Period.between(LocalDate.now(), endDate.toLocalDate()).getDays(); + Duration hours = Duration.between(LocalTime.now(), endDate.toLocalTime()); + if(hours.isNegative() && !hours.isZero()) + { + hours = hours.plusHours(24); + days--; + } + if(days < 0) + { + if(buyer != null) + { + IClaim claim = RealEstate.claimAPI.getClaimAt(sign);// getting by id creates errors for subclaims + OfflinePlayer buyerPlayer = Bukkit.getOfflinePlayer(buyer); + OfflinePlayer ownerPlayer = owner != null ? Bukkit.getOfflinePlayer(owner) : null; + if(claim == null || claim.isWilderness()) + { + if(!Utils.makePayment(buyer, null, price, false, false)) + { + RealEstate.instance.log.warning("Couldn't reimburse " + price + " to " + buyerPlayer.getName() + " for the auction of a deleted claim"); + } + if(buyerPlayer.isOnline()) + { + Messages.sendMessage(buyerPlayer.getPlayer(), RealEstate.instance.messages.msgErrorClaimDoesNotExistAuction); + } + RealEstate.transactionsStore.cancelTransaction(claim); + } + else if(!Utils.makePayment(owner, null, price, false, false)) + { + RealEstate.instance.log.warning("Couldn't pay " + price + " to " + claim.getOwnerName() + " for the auction of a claim"); + if(buyerPlayer.isOnline()) + { + Messages.sendMessage(buyerPlayer.getPlayer(), RealEstate.instance.messages.msgErrorAuctionCouldntPayOwner); + if(!Utils.makePayment(buyer, null, price, false, false)) + { + RealEstate.instance.log.warning("Couldn't reimburse " + price + " to " + buyerPlayer.getName() + " for the cancellation of an auction"); + } + if(buyerPlayer.isOnline()) + { + Messages.sendMessage(buyerPlayer.getPlayer(), RealEstate.instance.messages.msgInfoClaimInfoAuctionCancelled); + } + } + if(owner != null && ownerPlayer != null && ownerPlayer.isOnline()) + { + Messages.sendMessage(ownerPlayer.getPlayer(), RealEstate.instance.messages.msgErrorAuctionCouldntReceiveOwner); + } + RealEstate.transactionsStore.cancelTransaction(claim); + } + else + { + + Utils.transferClaim(claim, buyer, owner); + if(getHolder().getState() instanceof Sign) + { + RealEstateSign s = new RealEstateSign((Sign) getHolder().getState()); + s.setLine(0, ""); + s.setLine(1, Messages.getMessage(RealEstate.instance.messages.msgSignAuctionWon, false)); + s.setLine(2, buyerPlayer.getName()); + s.setLine(3, ""); + s.update(true); + } + return true; + } + } + if(getHolder().getState() instanceof Sign) + { + RealEstateSign s = new RealEstateSign((Sign) getHolder().getState()); + s.setLine(0, ""); + s.setLine(1, Messages.getMessage(RealEstate.instance.messages.msgSignAuctionEnded, false)); + s.setLine(2, ""); + s.setLine(3, ""); + s.update(true); + } + return true; + } + else + { + // update sign + if(sign.getBlock().getState() instanceof Sign) + { + RealEstateSign s = new RealEstateSign((Sign) sign.getBlock().getState()); + s.setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); + s.setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceAuction); + String remaining = Utils.getTime(days, hours, false); + s.setLine(2, Messages.getMessage(RealEstate.instance.messages.msgSignAuctionRemainingTime, false, remaining)); + if(buyer != null) + { + String name = Bukkit.getOfflinePlayer(buyer).getName(); + s.setLine(3, Messages.getMessage(RealEstate.instance.messages.msgSignAuctionHighestBidder, false, name, RealEstate.econ.format(price))); + } + else + { + s.setLine(3, Messages.getMessage(RealEstate.instance.messages.msgSignAuctionNoBider, false)); + } + s.update(true); + } + else + { + RealEstate.transactionsStore.cancelTransaction(this); + } + return false; + } + } + + @Override + public boolean tryCancelTransaction(Player p, boolean force) { + if(buyer == null || RealEstate.instance.config.cfgCancelAuction || force || p.hasPermission("realestate.admin")) + { + if(buyer != null) + { + OfflinePlayer buyerPlayer = Bukkit.getOfflinePlayer(buyer); + if(!Utils.makePayment(buyer, null, price, false, false)) + { + RealEstate.instance.log.warning("Couldn't reimburse " + price + " to " + buyerPlayer.getName() + " for the cancellation of an auction"); + } + if(buyerPlayer.isOnline()) + { + Messages.sendMessage(buyerPlayer.getPlayer(), RealEstate.instance.messages.msgInfoClaimInfoAuctionCancelled); + } + } + RealEstate.transactionsStore.cancelTransaction(this); + return true; + } + else if(p != null) + { + p.sendMessage(Messages.getMessage(RealEstate.instance.messages.msgErrorCantCancelAuction)); + } + return false; + } + + @Override + public void interact(Player player) { + IClaim claim = RealEstate.claimAPI.getClaimAt(sign);// getting by id creates errors for subclaims + if(claim == null || claim.isWilderness()) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimDoesNotExist); + RealEstate.transactionsStore.cancelTransaction(claim); + return; + } + String claimTypeDisplay = claim.isParentClaim() ? + RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; + + if (owner != null && owner.equals(player.getUniqueId())) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimAlreadyOwner, claimTypeDisplay); + return; + } + if(claim.isParentClaim() && owner != null && !owner.equals(claim.getOwner())) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNotAuctionedByOwner, claimTypeDisplay); + RealEstate.transactionsStore.cancelTransaction(claim); + return; + } + bid(player, price + bidStep); + } + + public void bid(Player player, double price) { + IClaim claim = RealEstate.claimAPI.getClaimAt(sign);// getting by id creates errors for subclaims + if(claim == null || claim.isWilderness()) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimDoesNotExist); + RealEstate.transactionsStore.cancelTransaction(claim); + return; + } + String claimType = claim.isParentClaim() ? "claim" : "subclaim"; + String claimTypeDisplay = claim.isParentClaim() ? + RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; + + if(!player.hasPermission("realestate." + claimType + ".bid")) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNoAuctionPermission, claimTypeDisplay); + return; + } + if(RealEstate.instance.config.cfgDisableOutbidSelf && player.getUniqueId().equals(buyer)) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimAlreadyHighestBidder, claimTypeDisplay); + return; + } + if(RealEstate.econ.getBalance(player) < price) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorNoMoneySelf, RealEstate.econ.format(price)); + return; + } + if(!Utils.makePayment(null, player.getUniqueId(), price, false, false)) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorNoMoneySelf, RealEstate.econ.format(price)); + return; + } + if(buyer != null && !Utils.makePayment(buyer, null, this.price, false, false)) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorCouldntReimburseOther, RealEstate.econ.format(this.price)); + + if(!Utils.makePayment(player.getUniqueId(), null, price, false, false)) + { + RealEstate.instance.log.warning("Couldn't reimburse " + player.getName() + " for " + RealEstate.econ.format(price) + ", the money has been lost!"); + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorCouldntReimburseSelf, RealEstate.econ.format(price)); + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorContactAdmin); + } + + return; + } + buyer = player.getUniqueId(); + this.price = price; + update(); + RealEstate.transactionsStore.saveData(); + } + + @Override + public void preview(Player player) { + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); + if(player.hasPermission("realestate.info")) + { + String claimType = claim.isParentClaim() ? "claim" : "subclaim"; + String claimTypeDisplay = claim.isParentClaim() ? + RealEstate.instance.messages.keywordClaim : + RealEstate.instance.messages.keywordSubclaim; + String msg; + msg = Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoAuctionHeader) + "\n"; + if(buyer == null) + { + msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoAuctionNoBidder, + claimTypeDisplay, + RealEstate.econ.format(price)) + "\n"; + } + else + { + OfflinePlayer buyer = Bukkit.getOfflinePlayer(this.buyer); + msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoAuctionHighestBidder, + claimTypeDisplay, + buyer.getName(), + RealEstate.econ.format(price)) + "\n"; + } + + int days = Period.between(LocalDate.now(), endDate.toLocalDate()).getDays(); + Duration hours = Duration.between(LocalTime.now(), endDate.toLocalTime()); + if(hours.isNegative() && !hours.isZero()) + { + hours = hours.plusHours(24); + days--; + } + + if(days < 0) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgInfoClaimInfoAuctionEnded, claimTypeDisplay); + update();// update will end the auction properly + return; + } + + msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoAuctionTimeRemaining, + Utils.getTime(days, hours, true)) + "\n"; + + msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoAuctionBidStep, + RealEstate.econ.format(bidStep)) + "\n"; + + if(claimType.equalsIgnoreCase("claim")) + { + msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoOwner, + claim.getOwnerName()) + "\n"; + } + else + { + msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoMainOwner, + claim.getParent().getOwnerName()) + "\n"; + } + + Messages.sendMessage(player, msg, false); + } + } + + @Override + public void msgInfo(CommandSender cs) + { + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); + String location = "[" + claim.getWorld().getName() + ", " + + "X: " + claim.getX() + ", " + + "Y: " + claim.getY() + ", " + + "Z: " + claim.getZ() + "]"; + + int days = Period.between(LocalDate.now(), endDate.toLocalDate()).getDays(); + Duration hours = Duration.between(LocalTime.now(), endDate.toLocalTime()); + if(hours.isNegative() && !hours.isZero()) + { + hours = hours.plusHours(24); + days--; + } + + Messages.sendMessage(cs, RealEstate.instance.messages.msgInfoClaimInfoAuctionOneline, + claim.getArea() + "", + location, + RealEstate.econ.format(price), + Utils.getTime(days, hours, true), + RealEstate.econ.format(bidStep)); + } + public double getBidStep() { + return this.bidStep; + } + + public UUID getBuyer() { + return this.buyer; + } + + public LocalDateTime getEndDate() { + return this.endDate; + } + +} diff --git a/src/me/EtienneDx/RealEstate/Transactions/ClaimLease.java b/src/me/EtienneDx/RealEstate/Transactions/ClaimLease.java index 8f2f375..f4ae123 100644 --- a/src/me/EtienneDx/RealEstate/Transactions/ClaimLease.java +++ b/src/me/EtienneDx/RealEstate/Transactions/ClaimLease.java @@ -11,7 +11,6 @@ import org.bukkit.Location; import org.bukkit.OfflinePlayer; import org.bukkit.block.Sign; -import org.bukkit.block.sign.Side; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -20,9 +19,9 @@ import me.EtienneDx.RealEstate.Messages; import me.EtienneDx.RealEstate.RealEstate; import me.EtienneDx.RealEstate.Utils; -import me.ryanhamshire.GriefPrevention.Claim; -import me.ryanhamshire.GriefPrevention.ClaimPermission; -import me.ryanhamshire.GriefPrevention.GriefPrevention; +import me.EtienneDx.RealEstate.RealEstateSign; +import me.EtienneDx.RealEstate.ClaimAPI.ClaimPermission; +import me.EtienneDx.RealEstate.ClaimAPI.IClaim; import net.md_5.bungee.api.ChatColor; public class ClaimLease extends BoughtTransaction @@ -40,7 +39,7 @@ public ClaimLease(Map map) paymentsLeft = (int)map.get("paymentsLeft"); } - public ClaimLease(Claim claim, Player player, double price, Location sign, int frequency, int paymentsLeft) + public ClaimLease(IClaim claim, Player player, double price, Location sign, int frequency, int paymentsLeft) { super(claim, player, price, sign); this.frequency = frequency; @@ -66,41 +65,39 @@ public boolean update() { if(sign.getBlock().getState() instanceof Sign) { - Sign s = (Sign)sign.getBlock().getState(); - s.getSide(Side.FRONT).setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader)); - s.getSide(Side.FRONT).setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceLease); - s.getSide(Side.FRONT).setLine(2, owner != null ? Bukkit.getOfflinePlayer(owner).getName() : "SERVER"); - s.getSide(Side.FRONT).setLine(3, paymentsLeft + "x " + price + " " + RealEstate.econ.currencyNamePlural()); - + RealEstateSign s = new RealEstateSign((Sign) sign.getBlock().getState()); + s.setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); + s.setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceLease); if(RealEstate.instance.config.cfgUseCurrencySymbol) { if(RealEstate.instance.config.cfgUseDecimalCurrency == false) { - s.getSide(Side.FRONT).setLine(2, paymentsLeft + "x " + RealEstate.instance.config.cfgCurrencySymbol + " " + (int)Math.round(price)); + s.setLine(2, paymentsLeft + "x " + RealEstate.instance.config.cfgCurrencySymbol + " " + (int)Math.round(price)); } else { - s.getSide(Side.FRONT).setLine(2, paymentsLeft + "x " + RealEstate.instance.config.cfgCurrencySymbol + " " + price); + s.setLine(2, paymentsLeft + "x " + RealEstate.instance.config.cfgCurrencySymbol + " " + price); } } else { if(RealEstate.instance.config.cfgUseDecimalCurrency == false) { - s.getSide(Side.FRONT).setLine(2, paymentsLeft + "x " + (int)Math.round(price) + " " + RealEstate.econ.currencyNamePlural()); + s.setLine(2, paymentsLeft + "x " + (int)Math.round(price) + " " + RealEstate.econ.currencyNamePlural()); } else { - s.getSide(Side.FRONT).setLine(2, paymentsLeft + "x " + price + " " + RealEstate.econ.currencyNamePlural()); + s.setLine(2, paymentsLeft + "x " + price + " " + RealEstate.econ.currencyNamePlural()); } } - s.getSide(Side.FRONT).setLine(3, Utils.getTime(frequency, null, false)); + s.setLine(3, Utils.getTime(frequency, null, false)); s.update(true); } else { return true; } + } else { @@ -126,7 +123,7 @@ private void payLease() OfflinePlayer buyerPlayer = Bukkit.getOfflinePlayer(buyer); OfflinePlayer seller = owner == null ? null : Bukkit.getOfflinePlayer(owner); - String claimType = GriefPrevention.instance.dataStore.getClaimAt(sign, false, null).parent == null ? + String claimType = RealEstate.claimAPI.getClaimAt(sign).isParentClaim() ? RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; String location = "[" + sign.getWorld().getName() + ", X: " + sign.getBlockX() + ", Y: " + sign.getBlockY() + ", Z: " + sign.getBlockZ() + "]"; @@ -157,7 +154,7 @@ else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) if(owner != null) { - if(seller.isOnline() && RealEstate.instance.config.cfgMessageOwner) + if(seller != null && seller.isOnline() && RealEstate.instance.config.cfgMessageOwner) { Messages.sendMessage(seller.getPlayer(), RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentOwner, buyerPlayer.getName(), @@ -196,7 +193,7 @@ else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) RealEstate.econ.format(price))); } - if(seller.isOnline() && RealEstate.instance.config.cfgMessageOwner) + if(seller != null && seller.isOnline() && RealEstate.instance.config.cfgMessageOwner) { Messages.sendMessage(seller.getPlayer(), RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentOwnerFinal, buyerPlayer.getName(), @@ -213,7 +210,7 @@ else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) location, RealEstate.econ.format(price))); } - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(sign, false, null); + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); Utils.transferClaim(claim, buyer, owner); RealEstate.transactionsStore.cancelTransaction(this);// the transaction is finished @@ -234,9 +231,9 @@ private void exitLease() OfflinePlayer buyerPlayer = Bukkit.getOfflinePlayer(buyer); OfflinePlayer seller = owner == null ? null : Bukkit.getOfflinePlayer(owner); - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(sign, false, null); + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); - String claimType = claim.parent == null ? + String claimType = claim.isParentClaim() ? RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; String location = "[" + sign.getWorld().getName() + ", X: " + @@ -258,7 +255,7 @@ else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) location, RealEstate.econ.format(price))); } - if(seller.isOnline() && RealEstate.instance.config.cfgMessageOwner) + if(seller != null && seller.isOnline() && RealEstate.instance.config.cfgMessageOwner) { Messages.sendMessage(seller.getPlayer(), RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentOwnerCancelled, buyerPlayer.getName(), @@ -276,12 +273,12 @@ else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) RealEstate.econ.format(price))); } - claim.managers.remove(buyer.toString()); - claim.dropPermission(buyer.toString()); + claim.removeManager(buyer); + claim.dropPlayerPermissions(buyer); } else { - getHolder().breakNaturally();// the sign should still be there since the lease has never begun + getHolder().breakNaturally();// the sign should still be there since the lease has netver begun } RealEstate.transactionsStore.cancelTransaction(this); } @@ -298,10 +295,10 @@ public boolean tryCancelTransaction(Player p, boolean force) } else { - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(sign, false, null); + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); if(p != null) { Messages.sendMessage(p, RealEstate.instance.messages.msgErrorCantCancelAlreadyLeased, - claim.parent == null ? + claim.isParentClaim() ? RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim ); @@ -319,15 +316,15 @@ public boolean tryCancelTransaction(Player p, boolean force) @Override public void interact(Player player) { - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(sign, false, null);// getting by id creates errors for subclaims - if(claim == null) + IClaim claim = RealEstate.claimAPI.getClaimAt(sign);// getting by id creates errors for subclaims + if(claim == null || claim.isWilderness()) { Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimDoesNotExist); RealEstate.transactionsStore.cancelTransaction(claim); return; } - String claimType = claim.parent == null ? "claim" : "subclaim"; - String claimTypeDisplay = claim.parent == null ? + String claimType = claim.isParentClaim() ? "claim" : "subclaim"; + String claimTypeDisplay = claim.isParentClaim() ? RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; @@ -336,7 +333,7 @@ public void interact(Player player) Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimAlreadyOwner, claimTypeDisplay); return; } - if(claim.parent == null && owner != null && !owner.equals(claim.ownerID)) + if(claim.isParentClaim() && owner != null && !owner.equals(claim.getOwner())) { Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNotLeasedByOwner, claimTypeDisplay); RealEstate.transactionsStore.cancelTransaction(claim); @@ -358,9 +355,9 @@ public void interact(Player player) buyer = player.getUniqueId(); lastPayment = LocalDateTime.now(); paymentsLeft--; - claim.setPermission(buyer.toString(), ClaimPermission.Build); - claim.setPermission(player.getUniqueId().toString(), ClaimPermission.Manage); - GriefPrevention.instance.dataStore.saveClaim(claim); + claim.addPlayerPermissions(buyer, ClaimPermission.BUILD); + claim.addPlayerPermissions(player.getUniqueId(), ClaimPermission.MANAGE); + RealEstate.claimAPI.saveClaim(claim); getHolder().breakNaturally();// leases don't have signs indicating the remaining time update(); RealEstate.transactionsStore.saveData(); @@ -410,11 +407,11 @@ else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) @Override public void preview(Player player) { - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(sign, false, null); + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); if(player.hasPermission("realestate.info")) { - String claimType = claim.parent == null ? "claim" : "subclaim"; - String claimTypeDisplay = claim.parent == null ? + String claimType = claim.isParentClaim() ? "claim" : "subclaim"; + String claimTypeDisplay = claim.isParentClaim() ? RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; String msg; @@ -435,7 +432,7 @@ public void preview(Player player) else { msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoMainOwner, - claim.parent.getOwnerName()) + "\n"; + claim.getParent().getOwnerName()) + "\n"; msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoNote) + "\n"; } } @@ -466,7 +463,7 @@ public void preview(Player player) else { msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoMainOwner, - claim.parent.getOwnerName()) + "\n"; + claim.getParent().getOwnerName()) + "\n"; } } Messages.sendMessage(player, msg, false); @@ -480,11 +477,11 @@ public void preview(Player player) @Override public void msgInfo(CommandSender cs) { - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(sign, false, null); - String location = "[" + claim.getLesserBoundaryCorner().getWorld().getName() + ", " + - "X: " + claim.getLesserBoundaryCorner().getBlockX() + ", " + - "Y: " + claim.getLesserBoundaryCorner().getBlockY() + ", " + - "Z: " + claim.getLesserBoundaryCorner().getBlockZ() + "]"; + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); + String location = "[" + claim.getWorld().getName() + ", " + + "X: " + claim.getX() + ", " + + "Y: " + claim.getY() + ", " + + "Z: " + claim.getZ() + "]"; Messages.sendMessage(cs, RealEstate.instance.messages.msgInfoClaimInfoLeaseOneline, claim.getArea() + "", @@ -492,5 +489,12 @@ public void msgInfo(CommandSender cs) RealEstate.econ.format(price), paymentsLeft + ""); } + public int getFrequency() { + return this.frequency; + } + + public int getPaymentsLeft() { + return this.paymentsLeft; + } } diff --git a/src/me/EtienneDx/RealEstate/Transactions/ClaimRent.java b/src/me/EtienneDx/RealEstate/Transactions/ClaimRent.java index cb395d0..e940b3d 100644 --- a/src/me/EtienneDx/RealEstate/Transactions/ClaimRent.java +++ b/src/me/EtienneDx/RealEstate/Transactions/ClaimRent.java @@ -11,7 +11,6 @@ import org.bukkit.Location; import org.bukkit.OfflinePlayer; import org.bukkit.block.Sign; -import org.bukkit.block.sign.Side; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -20,9 +19,9 @@ import me.EtienneDx.RealEstate.Messages; import me.EtienneDx.RealEstate.RealEstate; import me.EtienneDx.RealEstate.Utils; -import me.ryanhamshire.GriefPrevention.Claim; -import me.ryanhamshire.GriefPrevention.ClaimPermission; -import me.ryanhamshire.GriefPrevention.GriefPrevention; +import me.EtienneDx.RealEstate.RealEstateSign; +import me.EtienneDx.RealEstate.ClaimAPI.ClaimPermission; +import me.EtienneDx.RealEstate.ClaimAPI.IClaim; import net.md_5.bungee.api.ChatColor; public class ClaimRent extends BoughtTransaction @@ -31,8 +30,6 @@ public class ClaimRent extends BoughtTransaction int duration; public boolean autoRenew = false; public boolean buildTrust = true; - public int periodCount = 0; - public int maxPeriod; public ClaimRent(Map map) { @@ -41,8 +38,6 @@ public ClaimRent(Map map) startDate = LocalDateTime.parse((String) map.get("startDate"), DateTimeFormatter.ISO_DATE_TIME); duration = (int)map.get("duration"); autoRenew = (boolean) map.get("autoRenew"); - periodCount = (int) map.get("periodCount"); - maxPeriod = (int) map.get("maxPeriod"); try { buildTrust = (boolean) map.get("buildTrust"); } @@ -51,11 +46,10 @@ public ClaimRent(Map map) } } - public ClaimRent(Claim claim, Player player, double price, Location sign, int duration, int rentPeriods, boolean buildTrust) + public ClaimRent(IClaim claim, Player player, double price, Location sign, int duration, boolean buildTrust) { super(claim, player, price, sign); this.duration = duration; - this.maxPeriod = RealEstate.instance.config.cfgEnableRentPeriod ? rentPeriods : 1; this.buildTrust = buildTrust; } @@ -67,8 +61,6 @@ public Map serialize() { map.put("startDate", startDate.format(DateTimeFormatter.ISO_DATE_TIME)); map.put("duration", duration); map.put("autoRenew", autoRenew); - map.put("periodCount", periodCount); - map.put("maxPeriod", maxPeriod); map.put("buildTrust", buildTrust); return map; @@ -81,11 +73,9 @@ public boolean update() { if(sign.getBlock().getState() instanceof Sign) { - Sign s = (Sign) sign.getBlock().getState(); - s.getSide(Side.FRONT).setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); - s.getSide(Side.FRONT).setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceRent); - s.getSide(Side.FRONT).setLine(2, owner != null ? Bukkit.getOfflinePlayer(owner).getName() : "SERVER"); - + RealEstateSign s = new RealEstateSign((Sign) sign.getBlock().getState()); + s.setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); + s.setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceRent); String price_line = ""; if(RealEstate.instance.config.cfgUseCurrencySymbol) { @@ -110,13 +100,13 @@ public boolean update() price_line = price + " " + RealEstate.econ.currencyNamePlural(); } } - String period = (maxPeriod > 1 ? maxPeriod + "x " : "") + Utils.getTime(duration, null, false); + String period = Utils.getTime(duration, null, false); if(this.buildTrust) { - s.getSide(Side.FRONT).setLine(2, price_line); - s.getSide(Side.FRONT).setLine(3, period); + s.setLine(2, price_line); + s.setLine(3, period); } else { - s.getSide(Side.FRONT).setLine(2, RealEstate.instance.config.cfgContainerRentLine); - s.getSide(Side.FRONT).setLine(3, price_line + " - " + period); + s.setLine(2, RealEstate.instance.config.cfgContainerRentLine); + s.setLine(3, price_line + " - " + period); } s.update(true); } @@ -141,15 +131,15 @@ public boolean update() } else if(sign.getBlock().getState() instanceof Sign) { - Sign s = (Sign) sign.getBlock().getState(); - s.getSide(Side.FRONT).setLine(0, ChatColor.GOLD + RealEstate.instance.config.cfgReplaceOngoingRent); - s.getSide(Side.FRONT).setLine(1, Utils.getSignString(Bukkit.getOfflinePlayer(buyer).getName())); - s.getSide(Side.FRONT).setLine(2, "Time remaining : "); + RealEstateSign s = new RealEstateSign((Sign) sign.getBlock().getState()); + s.setLine(0, ChatColor.GOLD + RealEstate.instance.config.cfgReplaceOngoingRent); //Changed the header to "[Rented]" so that it won't waste space on the next line and allow the name of the player to show underneath. + s.setLine(1, Utils.getSignString(Bukkit.getOfflinePlayer(buyer).getName()));//remove "Rented by" + s.setLine(2, "Time remaining : "); int daysLeft = duration - days - 1;// we need to remove the current day Duration timeRemaining = Duration.ofHours(24).minus(hours); - s.getSide(Side.FRONT).setLine(3, Utils.getTime(daysLeft, timeRemaining, false)); + s.setLine(3, Utils.getTime(daysLeft, timeRemaining, false)); s.update(true); } } @@ -159,16 +149,16 @@ else if(sign.getBlock().getState() instanceof Sign) private void unRent(boolean msgBuyer) { - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(sign, false, null); - claim.dropPermission(buyer.toString()); - claim.managers.remove(buyer.toString()); - claim.setSubclaimRestrictions(false); - GriefPrevention.instance.dataStore.saveClaim(claim); + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); + claim.dropPlayerPermissions(buyer); + claim.removeManager(buyer); + claim.setInheritPermissions(true); + RealEstate.claimAPI.saveClaim(claim); if(msgBuyer && Bukkit.getOfflinePlayer(buyer).isOnline() && RealEstate.instance.config.cfgMessageBuyer) { String location = "[" + sign.getWorld().getName() + ", X: " + sign.getBlockX() + ", Y: " + sign.getBlockY() + ", Z: " + sign.getBlockZ() + "]"; - String claimType = claim.parent == null ? + String claimType = claim.isParentClaim() ? RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; @@ -188,15 +178,14 @@ private void payRent() OfflinePlayer buyerPlayer = Bukkit.getOfflinePlayer(this.buyer); OfflinePlayer seller = owner == null ? null : Bukkit.getOfflinePlayer(owner); - String claimType = GriefPrevention.instance.dataStore.getClaimAt(sign, false, null).parent == null ? + String claimType = RealEstate.claimAPI.getClaimAt(sign).isParentClaim() ? RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; String location = "[" + sign.getWorld().getName() + ", X: " + sign.getBlockX() + ", Y: " + sign.getBlockY() + ", Z: " + sign.getBlockZ() + "]"; - if((autoRenew || periodCount + 1 < maxPeriod) && Utils.makePayment(owner, this.buyer, price, false, false)) + if(autoRenew && Utils.makePayment(owner, this.buyer, price, false, false)) { - periodCount = (periodCount + 1) % maxPeriod; startDate = LocalDateTime.now(); if(buyerPlayer.isOnline() && RealEstate.instance.config.cfgMessageBuyer) { @@ -270,7 +259,7 @@ public boolean tryCancelTransaction(Player p, boolean force) { if(buyer != null) { - if(p.hasPermission("realestate.admin") && force == true) + if(p.hasPermission("realestate.admin") || force == true) { this.unRent(true); RealEstate.transactionsStore.cancelTransaction(this); @@ -278,10 +267,10 @@ public boolean tryCancelTransaction(Player p, boolean force) } else { - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(sign, false, null); if(p != null) { + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); Messages.sendMessage(p, RealEstate.instance.messages.msgErrorCantCancelAlreadyRented, - claim.parent == null ? + claim.isParentClaim() ? RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim ); @@ -299,15 +288,15 @@ public boolean tryCancelTransaction(Player p, boolean force) @Override public void interact(Player player) { - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(sign, false, null);// getting by id creates errors for subclaims - if(claim == null) + IClaim claim = RealEstate.claimAPI.getClaimAt(sign);// getting by id creates errors for subclaims + if(claim == null || claim.isWilderness()) { Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimDoesNotExist); RealEstate.transactionsStore.cancelTransaction(claim); return; } - String claimType = claim.parent == null ? "claim" : "subclaim"; - String claimTypeDisplay = claim.parent == null ? + String claimType = claim.isParentClaim() ? "claim" : "subclaim"; + String claimTypeDisplay = claim.isParentClaim() ? RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; if (owner != null && owner.equals(player.getUniqueId())) @@ -315,7 +304,7 @@ public void interact(Player player) Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimAlreadyOwner, claimTypeDisplay); return; } - if(claim.parent == null && owner != null && !owner.equals(claim.ownerID)) + if(claim.isParentClaim() && owner != null && !owner.equals(claim.getOwner())) { Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNotRentedByOwner, claimTypeDisplay); RealEstate.transactionsStore.cancelTransaction(claim); @@ -332,17 +321,16 @@ public void interact(Player player) return; } - if(Utils.makePayment(owner, player.getUniqueId(), price, false, true))// if payment succeed + if(Utils.makePayment(owner, player.getUniqueId(), price, false, true)) // if payment succeed { buyer = player.getUniqueId(); startDate = LocalDateTime.now(); autoRenew = false; - periodCount = 0; - claim.setPermission(buyer.toString(), buildTrust ? ClaimPermission.Build : ClaimPermission.Inventory); - claim.setPermission(player.getUniqueId().toString(), ClaimPermission.Manage); - claim.managers.add(player.getUniqueId().toString()); - claim.setSubclaimRestrictions(true); - GriefPrevention.instance.dataStore.saveClaim(claim); + claim.addPlayerPermissions(buyer, buildTrust ? ClaimPermission.BUILD : ClaimPermission.CONTAINER); + claim.addPlayerPermissions(player.getUniqueId(), ClaimPermission.MANAGE); + claim.addManager(player.getUniqueId()); + claim.setInheritPermissions(false); + RealEstate.claimAPI.saveClaim(claim); update(); RealEstate.transactionsStore.saveData(); @@ -393,11 +381,11 @@ else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) @Override public void preview(Player player) { - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(sign, false, null); + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); if(player.hasPermission("realestate.info")) { - String claimType = claim.parent == null ? "claim" : "subclaim"; - String claimTypeDisplay = claim.parent == null ? + String claimType = claim.isParentClaim() ? "claim" : "subclaim"; + String claimTypeDisplay = claim.isParentClaim() ? RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; String msg; @@ -408,11 +396,6 @@ public void preview(Player player) claimTypeDisplay, RealEstate.econ.format(price), Utils.getTime(duration, null, true)) + "\n"; - if(maxPeriod > 1) - { - msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoRentMaxPeriod, - maxPeriod + "") + "\n"; - } if(claimType.equalsIgnoreCase("claim")) { @@ -422,7 +405,7 @@ public void preview(Player player) else { msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoMainOwner, - claim.parent.getOwnerName()) + "\n"; + claim.getParent().getOwnerName()) + "\n"; msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoNote) + "\n"; } } @@ -445,12 +428,6 @@ public void preview(Player player) Utils.getTime(daysLeft, timeRemaining, true), Utils.getTime(duration, null, true)) + "\n"; - if(maxPeriod > 1 && maxPeriod - periodCount > 0) - { - msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoRentRemainingPeriods, - (maxPeriod - periodCount) + "") + "\n"; - } - if((owner != null && owner.equals(player.getUniqueId()) || buyer.equals(player.getUniqueId())) && RealEstate.instance.config.cfgEnableAutoRenew) { msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoRentAutoRenew, @@ -466,7 +443,7 @@ public void preview(Player player) else { msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoMainOwner, - claim.parent.getOwnerName()) + "\n"; + claim.getParent().getOwnerName()) + "\n"; } } Messages.sendMessage(player, msg, false); @@ -480,11 +457,11 @@ public void preview(Player player) @Override public void msgInfo(CommandSender cs) { - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(sign, false, null); - String location = "[" + claim.getLesserBoundaryCorner().getWorld().getName() + ", " + - "X: " + claim.getLesserBoundaryCorner().getBlockX() + ", " + - "Y: " + claim.getLesserBoundaryCorner().getBlockY() + ", " + - "Z: " + claim.getLesserBoundaryCorner().getBlockZ() + "]"; + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); + String location = "[" + claim.getWorld().getName() + ", " + + "X: " + claim.getX() + ", " + + "Y: " + claim.getY() + ", " + + "Z: " + claim.getZ() + "]"; Messages.sendMessage(cs, RealEstate.instance.messages.msgInfoClaimInfoRentOneline, claim.getArea() + "", diff --git a/src/me/EtienneDx/RealEstate/Transactions/ClaimSell.java b/src/me/EtienneDx/RealEstate/Transactions/ClaimSell.java index 0ca4478..2f6a2d3 100644 --- a/src/me/EtienneDx/RealEstate/Transactions/ClaimSell.java +++ b/src/me/EtienneDx/RealEstate/Transactions/ClaimSell.java @@ -7,23 +7,21 @@ import me.EtienneDx.RealEstate.Messages; import me.EtienneDx.RealEstate.RealEstate; import me.EtienneDx.RealEstate.Utils; -import me.ryanhamshire.GriefPrevention.Claim; -import me.ryanhamshire.GriefPrevention.GriefPrevention; +import me.EtienneDx.RealEstate.RealEstateSign; +import me.EtienneDx.RealEstate.ClaimAPI.IClaim; import net.md_5.bungee.api.ChatColor; import java.util.Map; -import java.util.UUID; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.OfflinePlayer; import org.bukkit.block.Sign; -import org.bukkit.block.sign.Side; import org.bukkit.command.CommandSender; public class ClaimSell extends ClaimTransaction { - public ClaimSell(Claim claim, Player player, double price, Location sign) + public ClaimSell(IClaim claim, Player player, double price, Location sign) { super(claim, player, price, sign); } @@ -38,37 +36,30 @@ public boolean update() { if(sign.getBlock().getState() instanceof Sign) { - Sign s = (Sign) sign.getBlock().getState(); - s.getSide(Side.FRONT).setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); - s.getSide(Side.FRONT).setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceSell); - s.getSide(Side.FRONT).setLine(2, owner != null ? Utils.getSignString(Bukkit.getOfflinePlayer(owner).getName()) : "SERVER"); - //s.setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); - //s.setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceSell); - //s.setLine(2, owner != null ? Utils.getSignString(Bukkit.getOfflinePlayer(owner).getName()) : "SERVER"); + RealEstateSign s = new RealEstateSign((Sign) sign.getBlock().getState()); + s.setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); + s.setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceSell); + s.setLine(2, owner != null ? Utils.getSignString(Bukkit.getOfflinePlayer(owner).getName()) : "SERVER"); if(RealEstate.instance.config.cfgUseCurrencySymbol) { if(RealEstate.instance.config.cfgUseDecimalCurrency == false) { - s.getSide(Side.FRONT).setLine(3, RealEstate.instance.config.cfgCurrencySymbol + " " + (int)Math.round(price)); - //s.setLine(3, RealEstate.instance.config.cfgCurrencySymbol + " " + (int)Math.round(price)); + s.setLine(3, RealEstate.instance.config.cfgCurrencySymbol + " " + (int)Math.round(price)); } else { - s.getSide(Side.FRONT).setLine(3, RealEstate.instance.config.cfgCurrencySymbol + " " + price); - //s.setLine(3, RealEstate.instance.config.cfgCurrencySymbol + " " + price); + s.setLine(3, RealEstate.instance.config.cfgCurrencySymbol + " " + price); } } else { if(RealEstate.instance.config.cfgUseDecimalCurrency == false) { - s.getSide(Side.FRONT).setLine(3, (int)Math.round(price) + " " + RealEstate.econ.currencyNamePlural()); - //s.setLine(3, (int)Math.round(price) + " " + RealEstate.econ.currencyNamePlural()); + s.setLine(3, (int)Math.round(price) + " " + RealEstate.econ.currencyNamePlural()); } else { - s.getSide(Side.FRONT).setLine(3, price + " " + RealEstate.econ.currencyNamePlural()); - //s.setLine(3, price + " " + RealEstate.econ.currencyNamePlural()); + s.setLine(3, price + " " + RealEstate.econ.currencyNamePlural()); } } s.update(true); @@ -91,15 +82,15 @@ public boolean tryCancelTransaction(Player p, boolean force) @Override public void interact(Player player) { - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(sign, false, null);// getting by id creates errors for subclaims - if(claim == null) + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); + if(claim == null || claim.isWilderness()) { Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimDoesNotExist); RealEstate.transactionsStore.cancelTransaction(claim); return; } - String claimType = claim.parent == null ? "claim" : "subclaim"; - String claimTypeDisplay = claim.parent == null ? + String claimType = claim.isParentClaim() ? "claim" : "subclaim"; + String claimTypeDisplay = claim.isParentClaim() ? RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; if (player.getUniqueId().equals(owner)) @@ -107,7 +98,7 @@ public void interact(Player player) Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimAlreadyOwner, claimTypeDisplay); return; } - if(claim.parent == null && owner != null && !owner.equals(claim.ownerID)) + if(claim.isParentClaim() && owner != null && !owner.equals(claim.getOwner())) { Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNotSoldByOwner, claimTypeDisplay); RealEstate.transactionsStore.cancelTransaction(claim); @@ -120,9 +111,9 @@ public void interact(Player player) } // for real claims, you may need to have enough claim blocks in reserve to purchase it (if transferClaimBlocks is false) if(claimType.equalsIgnoreCase("claim") && !RealEstate.instance.config.cfgTransferClaimBlocks && - GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()).getRemainingClaimBlocks() < claim.getArea()) + RealEstate.claimAPI.getPlayerData(player.getUniqueId()).getRemainingClaimBlocks() < claim.getArea()) { - int remaining = GriefPrevention.instance.dataStore.getPlayerData(player.getUniqueId()).getRemainingClaimBlocks(); + int remaining = RealEstate.claimAPI.getPlayerData(player.getUniqueId()).getRemainingClaimBlocks(); int area = claim.getArea(); Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNoClaimBlocks, area + "", @@ -136,7 +127,7 @@ public void interact(Player player) { Utils.transferClaim(claim, player.getUniqueId(), owner); // normally, this is always the case, so it's not necessary, but until I proven my point, here - if(claim.parent != null || claim.ownerID.equals(player.getUniqueId())) + if(claim.isSubClaim() || claim.getOwner().equals(player.getUniqueId())) { String location = "[" + player.getLocation().getWorld() + ", " + "X: " + player.getLocation().getBlockX() + ", " + @@ -190,11 +181,11 @@ else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) @Override public void preview(Player player) { - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(sign, false, null); + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); if(player.hasPermission("realestate.info")) { - String claimType = claim.parent == null ? "claim" : "subclaim"; - String claimTypeDisplay = claim.parent == null ? + String claimType = claim.isParentClaim() ? "claim" : "subclaim"; + String claimTypeDisplay = claim.isParentClaim() ? RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; String msg = Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoSellHeader) + "\n"; @@ -211,7 +202,7 @@ public void preview(Player player) else { msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoMainOwner, - claim.parent.getOwnerName()) + "\n"; + claim.getParent().getOwnerName()) + "\n"; msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoNote) + "\n"; } Messages.sendMessage(player, msg, false); @@ -222,24 +213,18 @@ public void preview(Player player) } } - @Override - public void setOwner(UUID newOwner) - { - this.owner = newOwner; - } - @Override public void msgInfo(CommandSender cs) { - Claim claim = GriefPrevention.instance.dataStore.getClaimAt(sign, false, null); - if(claim == null) { + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); + if(claim == null || claim.isWilderness()) { tryCancelTransaction(null, true); return; } - String location = "[" + claim.getLesserBoundaryCorner().getWorld().getName() + ", " + - "X: " + claim.getLesserBoundaryCorner().getBlockX() + ", " + - "Y: " + claim.getLesserBoundaryCorner().getBlockY() + ", " + - "Z: " + claim.getLesserBoundaryCorner().getBlockZ() + "]"; + String location = "[" + claim.getWorld().getName() + ", " + + "X: " + claim.getX() + ", " + + "Y: " + claim.getY() + ", " + + "Z: " + claim.getZ() + "]"; Messages.sendMessage(cs, RealEstate.instance.messages.msgInfoClaimInfoSellOneline, claim.getArea() + "", diff --git a/src/me/EtienneDx/RealEstate/Transactions/ClaimTransaction.java b/src/me/EtienneDx/RealEstate/Transactions/ClaimTransaction.java index 4fc3622..3ab2330 100644 --- a/src/me/EtienneDx/RealEstate/Transactions/ClaimTransaction.java +++ b/src/me/EtienneDx/RealEstate/Transactions/ClaimTransaction.java @@ -3,75 +3,92 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; - import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.block.Sign; import org.bukkit.configuration.serialization.ConfigurationSerializable; import org.bukkit.entity.Player; +import me.EtienneDx.RealEstate.ClaimAPI.IClaim; -import me.ryanhamshire.GriefPrevention.Claim; - -public abstract class ClaimTransaction implements ConfigurationSerializable, Transaction -{ - public long claimId; - public UUID owner = null; - public double price; - public Location sign = null; - - public ClaimTransaction(Claim claim, Player player, double price, Location sign) - { - this.claimId = claim.getID().longValue(); - this.owner = player != null ? player.getUniqueId() : null; - this.price = price; - this.sign = sign; - } - - public ClaimTransaction(Map map) - { - this.claimId = Long.valueOf(String.valueOf(map.get("claimId"))); - if(map.get("owner") != null) - this.owner = UUID.fromString((String) map.get("owner")); - this.price = (double) map.get("price"); - if(map.get("signLocation") != null) - this.sign = (Location) map.get("signLocation"); - } - - public ClaimTransaction() - { - - } - - @Override - public Map serialize() - { - Map map = new HashMap<>(); - - map.put("claimId", this.claimId); - if(owner != null) - map.put("owner", owner.toString()); - map.put("price", this.price); - if(sign != null) - map.put("signLocation", sign); - - return map; - } - - @Override - public Block getHolder() - { - return sign.getBlock().getState() instanceof Sign ? sign.getBlock() : null; - } +public abstract class ClaimTransaction implements ConfigurationSerializable, Transaction { + public String claimId; + public UUID owner = null; + public double price; + public Location sign = null; + + public ClaimTransaction(IClaim claim, Player player, double price, Location sign) { + this.claimId = claim.getId(); + this.owner = player != null ? player.getUniqueId() : null; + this.price = price; + this.sign = sign; + } + + public ClaimTransaction(Map map) { + this.claimId = String.valueOf(map.get("claimId")); + if (map.get("owner") != null) { + String ownerStr = (String) map.get("owner"); + // If the owner string is "SERVER", set owner to null (i.e. admin claim) + if ("SERVER".equalsIgnoreCase(ownerStr)) { + this.owner = null; + } else { + this.owner = UUID.fromString(ownerStr); + } + } + this.price = (double) map.get("price"); + if(map.get("signLocation") != null) + this.sign = (Location) map.get("signLocation"); + } - @Override - public UUID getOwner() - { - return owner; - } - - @Override - public boolean tryCancelTransaction(Player p) - { - return this.tryCancelTransaction(p, false); - } + + public ClaimTransaction() { } + + @Override + public Map serialize() { + Map map = new HashMap<>(); + map.put("claimId", this.claimId); + if(owner != null) + map.put("owner", owner.toString()); + map.put("price", this.price); + if(sign != null) + map.put("signLocation", sign); + return map; + } + + @Override + public Block getHolder() { + return sign.getBlock().getState() instanceof Sign ? sign.getBlock() : null; + } + + @Override + public UUID getOwner() { + return owner; + } + + @Override + public void setOwner(UUID newOwner) { + this.owner = newOwner; + } + + @Override + public boolean tryCancelTransaction(Player p) { + return this.tryCancelTransaction(p, false); + } + + // --- Added getter methods --- + public IClaim getClaim() { + // Use your API to retrieve the claim from the sign. + return me.EtienneDx.RealEstate.RealEstate.claimAPI.getClaimAt(sign); + } + + public UUID getOwnerUUID() { + return owner; + } + + public double getPrice() { + return price; + } + + public Location getSign() { + return sign; + } } diff --git a/src/me/EtienneDx/RealEstate/Transactions/ExitOffer.java b/src/me/EtienneDx/RealEstate/Transactions/ExitOffer.java index 5bcd65b..076f991 100644 --- a/src/me/EtienneDx/RealEstate/Transactions/ExitOffer.java +++ b/src/me/EtienneDx/RealEstate/Transactions/ExitOffer.java @@ -3,32 +3,27 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; - import org.bukkit.configuration.serialization.ConfigurationSerializable; -public class ExitOffer implements ConfigurationSerializable -{ - public UUID offerBy; - public double price; - - public ExitOffer(UUID offerBy, double price) - { - this.offerBy = offerBy; - this.price = price; - } - - public ExitOffer(Map map) - { - offerBy = UUID.fromString((String)map.get("offerBy")); - price = (double)map.get("price"); - } - - @Override - public Map serialize() - { - HashMap map = new HashMap<>(); - map.put("offerBy", offerBy.toString()); - map.put("price", price); - return map; - } +public class ExitOffer implements ConfigurationSerializable { + public UUID offerBy; + public double price; + + public ExitOffer(UUID offerBy, double price) { + this.offerBy = offerBy; + this.price = price; + } + + public ExitOffer(Map map) { + offerBy = UUID.fromString((String) map.get("offerBy")); + price = (double) map.get("price"); + } + + @Override + public Map serialize() { + HashMap map = new HashMap<>(); + map.put("offerBy", offerBy.toString()); + map.put("price", price); + return map; + } } diff --git a/src/me/EtienneDx/RealEstate/Transactions/Transaction.java b/src/me/EtienneDx/RealEstate/Transactions/Transaction.java index 2620d5c..04e9086 100644 --- a/src/me/EtienneDx/RealEstate/Transactions/Transaction.java +++ b/src/me/EtienneDx/RealEstate/Transactions/Transaction.java @@ -1,13 +1,11 @@ package me.EtienneDx.RealEstate.Transactions; import java.util.UUID; - import org.bukkit.block.Block; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -public interface Transaction -{ +public interface Transaction { public Block getHolder(); public UUID getOwner(); public void setOwner(UUID newOwner); @@ -17,4 +15,4 @@ public interface Transaction public boolean tryCancelTransaction(Player p); public boolean tryCancelTransaction(Player p, boolean force); public void msgInfo(CommandSender cs); -} \ No newline at end of file +} diff --git a/src/me/EtienneDx/RealEstate/Transactions/TransactionsStore.java b/src/me/EtienneDx/RealEstate/Transactions/TransactionsStore.java index a14d928..8be7853 100644 --- a/src/me/EtienneDx/RealEstate/Transactions/TransactionsStore.java +++ b/src/me/EtienneDx/RealEstate/Transactions/TransactionsStore.java @@ -4,14 +4,23 @@ import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Files; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.time.LocalDateTime; import java.util.Date; import java.util.HashMap; import java.util.Iterator; +import java.util.UUID; +import java.util.logging.Level; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; @@ -21,307 +30,685 @@ import me.EtienneDx.RealEstate.Messages; import me.EtienneDx.RealEstate.RealEstate; import me.EtienneDx.RealEstate.Utils; -import me.ryanhamshire.GriefPrevention.Claim; -import me.ryanhamshire.GriefPrevention.GriefPrevention; +import me.EtienneDx.RealEstate.ClaimAPI.IClaim; + +public class TransactionsStore { -public class TransactionsStore -{ public final String dataFilePath = RealEstate.pluginDirPath + "transactions.yml"; - DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); - Date date = new Date(); + // Make these public so that other classes can reference them if needed. + public DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); + public Date date = new Date(); public HashMap claimSell; public HashMap claimRent; public HashMap claimLease; + public HashMap claimAuction; - public TransactionsStore() - { - loadData(); - new BukkitRunnable() - { - - @Override - public void run() - { - Iterator ite = claimRent.values().iterator(); - while(ite.hasNext()) - { - if(ite.next().update()) - ite.remove(); - } + public enum StorageType { FILE, MYSQL, SQLITE } + private StorageType storageType; + private Connection dbConnection = null; - Iterator it = claimLease.values().iterator(); - while(it.hasNext()) - { - if(it.next().update()) - it.remove(); - } - saveData(); - } - }.runTaskTimer(RealEstate.instance, 1200L, 1200L);// run every 60 seconds + public TransactionsStore() { + String storageStr = RealEstate.instance.config.databaseType.toLowerCase(); + switch(storageStr) { + case "mysql": + storageType = StorageType.MYSQL; + break; + case "sqlite": + storageType = StorageType.SQLITE; + break; + case "yml": + storageType = StorageType.FILE; + break; + default: + RealEstate.instance.log.log(Level.SEVERE, "Invalid storage type in config.yml. Using default file storage!"); + storageType = StorageType.FILE; + break; + } + + claimSell = new HashMap<>(); + claimRent = new HashMap<>(); + claimLease = new HashMap<>(); + claimAuction = new HashMap<>(); + + if(storageType == StorageType.FILE) { + loadDataFromFile(); + } else { + setupDatabase(); + loadDataFromDatabase(); + } + + new BukkitRunnable() { + @Override + public void run() { + Iterator it1 = claimRent.values().iterator(); + while(it1.hasNext()) { + if(it1.next().update()) + it1.remove(); + } + Iterator it2 = claimLease.values().iterator(); + while(it2.hasNext()) { + if(it2.next().update()) + it2.remove(); + } + Iterator it3 = claimAuction.values().iterator(); + while(it3.hasNext()) { + if(it3.next().update()) + it3.remove(); + } + saveData(); + } + }.runTaskTimer(RealEstate.instance, 1200L, 1200L); } - public void loadData() - { - claimSell = new HashMap<>(); - claimRent = new HashMap<>(); - claimLease = new HashMap<>(); - - File file = new File(this.dataFilePath); - - if(file.exists()) - { - FileConfiguration config = YamlConfiguration.loadConfiguration(file); - try { - RealEstate.instance.addLogEntry(new String(Files.readAllBytes(FileSystems.getDefault().getPath(this.dataFilePath)))); - } catch (IOException e) { - e.printStackTrace(); - } - ConfigurationSection sell = config.getConfigurationSection("Sell"); - ConfigurationSection rent = config.getConfigurationSection("Rent"); - ConfigurationSection lease = config.getConfigurationSection("Lease"); - if(sell != null) - { - RealEstate.instance.addLogEntry(sell.toString()); - RealEstate.instance.addLogEntry(sell.getKeys(false).size() + ""); - for(String key : sell.getKeys(false)) - { - ClaimSell cs = (ClaimSell)sell.get(key); - claimSell.put(key, cs); - } - } - if(rent != null) - { - for(String key : rent.getKeys(false)) - { - ClaimRent cr = (ClaimRent)rent.get(key); - claimRent.put(key, cr); - } - } - if(lease != null) - { - for(String key : lease.getKeys(false)) - { - ClaimLease cl = (ClaimLease)lease.get(key); - claimLease.put(key, cl); - } - } - } + /* FILE-BASED METHODS */ + public void loadDataFromFile() { + File file = new File(this.dataFilePath); + if(file.exists()) { + FileConfiguration config = YamlConfiguration.loadConfiguration(file); + try { + RealEstate.instance.addLogEntry(new String(Files.readAllBytes(FileSystems.getDefault().getPath(this.dataFilePath)))); + } catch (IOException e) { + e.printStackTrace(); + } + ConfigurationSection sell = config.getConfigurationSection("Sell"); + ConfigurationSection rent = config.getConfigurationSection("Rent"); + ConfigurationSection lease = config.getConfigurationSection("Lease"); + ConfigurationSection auction = config.getConfigurationSection("Auction"); + if(sell != null) { + for(String key : sell.getKeys(false)) { + ClaimSell cs = (ClaimSell) sell.get(key); + claimSell.put(key, cs); + } + } + if(rent != null) { + for(String key : rent.getKeys(false)) { + ClaimRent cr = (ClaimRent) rent.get(key); + claimRent.put(key, cr); + } + } + if(lease != null) { + for(String key : lease.getKeys(false)) { + ClaimLease cl = (ClaimLease) lease.get(key); + claimLease.put(key, cl); + } + } + if(auction != null) { + for(String key : auction.getKeys(false)) { + ClaimAuction ca = (ClaimAuction) auction.get(key); + claimAuction.put(key, ca); + } + } + } } - public void saveData() - { - YamlConfiguration config = new YamlConfiguration(); + public void saveDataToFile() { + YamlConfiguration config = new YamlConfiguration(); for (ClaimSell cs : claimSell.values()) - config.set("Sell." + cs.claimId, cs); + config.set("Sell." + cs.getClaim().getId(), cs); for (ClaimRent cr : claimRent.values()) - config.set("Rent." + cr.claimId, cr); + config.set("Rent." + cr.getClaim().getId(), cr); for (ClaimLease cl : claimLease.values()) - config.set("Lease." + cl.claimId, cl); - try - { - config.save(new File(this.dataFilePath)); - } - catch (IOException e) - { - RealEstate.instance.log.info("Unable to write to the data file at \"" + this.dataFilePath + "\""); - } + config.set("Lease." + cl.getClaim().getId(), cl); + for (ClaimAuction ca : claimAuction.values()) + config.set("Auction." + ca.getClaim().getId(), ca); + try { + config.save(new File(this.dataFilePath)); + } catch (IOException e) { + RealEstate.instance.log.info("Unable to write to the data file at \"" + this.dataFilePath + "\""); + } } - - public boolean anyTransaction(Claim claim) - { - return claim != null && - (claimSell.containsKey(claim.getID().toString()) || - claimRent.containsKey(claim.getID().toString()) || - claimLease.containsKey(claim.getID().toString())); - } - - public Transaction getTransaction(Claim claim) - { - if(claimSell.containsKey(claim.getID().toString())) - return claimSell.get(claim.getID().toString()); - if(claimRent.containsKey(claim.getID().toString())) - return claimRent.get(claim.getID().toString()); - if(claimLease.containsKey(claim.getID().toString())) - return claimLease.get(claim.getID().toString()); - return null; - } - - public void cancelTransaction(Claim claim) - { - if(anyTransaction(claim)) - { - Transaction tr = getTransaction(claim); - cancelTransaction(tr); - } - saveData(); - } - - public void cancelTransaction(Transaction tr) - { - if(tr.getHolder() != null) - tr.getHolder().breakNaturally(); - if(tr instanceof ClaimSell) - { - claimSell.remove(String.valueOf(((ClaimSell) tr).claimId)); - } - if(tr instanceof ClaimRent) - { - claimRent.remove(String.valueOf(((ClaimRent) tr).claimId)); - } - if(tr instanceof ClaimLease) - { - claimLease.remove(String.valueOf(((ClaimLease) tr).claimId)); - } - saveData(); - } - - public boolean canCancelTransaction(Transaction tr) - { - return tr instanceof ClaimSell || (tr instanceof ClaimRent && ((ClaimRent)tr).buyer == null) || - (tr instanceof ClaimLease && ((ClaimLease)tr).buyer == null); - } - - public void sell(Claim claim, Player player, double price, Location sign) - { - ClaimSell cs = new ClaimSell(claim, claim.isAdminClaim() ? null : player, price, sign); - claimSell.put(claim.getID().toString(), cs); - cs.update(); - saveData(); - - RealEstate.instance.addLogEntry("[" + this.dateFormat.format(this.date) + "] " + (player == null ? "The Server" : player.getName()) + - " has made " + (claim.isAdminClaim() ? "an admin" : "a") + " " + (claim.parent == null ? "claim" : "subclaim") + " for sale at " + - "[" + claim.getGreaterBoundaryCorner().getWorld() + ", " + - "X: " + claim.getGreaterBoundaryCorner().getBlockX() + ", " + - "Y: " + claim.getGreaterBoundaryCorner().getBlockY() + ", " + - "Z: " + claim.getGreaterBoundaryCorner().getBlockZ() + "] " + - "Price: " + price + " " + RealEstate.econ.currencyNamePlural()); - - String claimPrefix = claim.isAdminClaim() ? RealEstate.instance.messages.keywordAdminClaimPrefix : - RealEstate.instance.messages.keywordClaimPrefix; - String claimTypeDisplay = claim.parent == null ? RealEstate.instance.messages.keywordClaim : - RealEstate.instance.messages.keywordSubclaim; - - if(player != null) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgInfoClaimCreatedSell, - claimPrefix, - claimTypeDisplay, - RealEstate.econ.format(price)); - } - if(RealEstate.instance.config.cfgBroadcastSell) - { - for(Player p : Bukkit.getServer().getOnlinePlayers()) - { - if(p != player) - { - Messages.sendMessage(p, RealEstate.instance.messages.msgInfoClaimCreatedSellBroadcast, - player == null ? RealEstate.instance.messages.keywordTheServer : player.getDisplayName(), - claimPrefix, - claimTypeDisplay, - RealEstate.econ.format(price)); - } - } - } - } + + /* DATABASE METHODS using explicit columns */ + private void setupDatabase() { + try { + if(storageType == StorageType.MYSQL) { + String host = RealEstate.instance.config.mysqlHost; + int port = RealEstate.instance.config.mysqlPort; + String database = RealEstate.instance.config.mysqlDatabase; + String username = RealEstate.instance.config.mysqlUsername; + String password = RealEstate.instance.config.mysqlPassword; + String url = "jdbc:mysql://" + host + ":" + port + "/" + database + + "?allowPublicKeyRetrieval=true" + + "&autoReconnect=" + RealEstate.instance.config.mysqlAutoReconnect + + "&useSSL=" + RealEstate.instance.config.mysqlUseSSL; + dbConnection = DriverManager.getConnection(url, username, password); + } else if(storageType == StorageType.SQLITE) { + String sqliteFile = RealEstate.instance.config.sqliteDatabase; + String url = "jdbc:sqlite:" + RealEstate.pluginDirPath + sqliteFile; + dbConnection = DriverManager.getConnection(url); + } + + String prefix = RealEstate.instance.config.mysqlPrefix; + // ClaimSell table: + String createClaimSell = "CREATE TABLE IF NOT EXISTS " + prefix + "ClaimSell (" + + " claimId VARCHAR(255) NOT NULL PRIMARY KEY, " + + " owner CHAR(36) NOT NULL, " + + " price DECIMAL(10,2), " + + " signWorld VARCHAR(255), " + + " signX DOUBLE, " + + " signY DOUBLE, " + + " signZ DOUBLE, " + + " signPitch DOUBLE, " + + " signYaw DOUBLE" + + ");"; + try(PreparedStatement ps = dbConnection.prepareStatement(createClaimSell)) { + ps.executeUpdate(); + } + // ClaimRent table: + String createClaimRent = "CREATE TABLE IF NOT EXISTS " + prefix + "ClaimRent (" + + " claimId VARCHAR(255) NOT NULL PRIMARY KEY, " + + " owner CHAR(36) NOT NULL, " + + " duration INT, " + + " buildTrust BOOLEAN, " + + " price DECIMAL(10,2), " + + " autoRenew BOOLEAN, " + + " startDate DATETIME, " + + " buyer CHAR(36), " + + " signWorld VARCHAR(255), " + + " signX DOUBLE, " + + " signY DOUBLE, " + + " signZ DOUBLE, " + + " signPitch DOUBLE, " + + " signYaw DOUBLE" + + ");"; + try(PreparedStatement ps = dbConnection.prepareStatement(createClaimRent)) { + ps.executeUpdate(); + } + // ClaimLease table: + String createClaimLease = "CREATE TABLE IF NOT EXISTS " + prefix + "ClaimLease (" + + " claimId VARCHAR(255) NOT NULL PRIMARY KEY, " + + " owner CHAR(36) NOT NULL, " + + " price DECIMAL(10,2), " + + " paymentsLeft INT, " + + " frequency INT, " + + " signWorld VARCHAR(255), " + + " signX DOUBLE, " + + " signY DOUBLE, " + + " signZ DOUBLE, " + + " signPitch DOUBLE, " + + " signYaw DOUBLE," + + " buyer CHAR(36)" + + ");"; + try(PreparedStatement ps = dbConnection.prepareStatement(createClaimLease)) { + ps.executeUpdate(); + } + // ClaimAuction table: + String createClaimAuction = "CREATE TABLE IF NOT EXISTS " + prefix + "ClaimAuction (" + + " claimId VARCHAR(255) NOT NULL PRIMARY KEY, " + + " owner CHAR(36), " + + " bidStep DECIMAL(10,2), " + + " endDate DATETIME, " + + " price DECIMAL(10,2), " + + " signWorld VARCHAR(255), " + + " signX DOUBLE, " + + " signY DOUBLE, " + + " signZ DOUBLE, " + + " signPitch DOUBLE, " + + " signYaw DOUBLE," + + " buyer CHAR(36)" + + ");"; + try(PreparedStatement ps = dbConnection.prepareStatement(createClaimAuction)) { + ps.executeUpdate(); + } + } catch (SQLException e) { + e.printStackTrace(); + } + } + + private void loadDataFromDatabase() { + String prefix = RealEstate.instance.config.mysqlPrefix; + try { + // --- Load ClaimSell --- + String querySell = "SELECT * FROM " + prefix + "ClaimSell;"; + try (PreparedStatement ps = dbConnection.prepareStatement(querySell); + ResultSet rs = ps.executeQuery()) { + while(rs.next()){ + String claimId = rs.getString("claimId"); + double price = rs.getDouble("price"); + String signWorld = rs.getString("signWorld"); + double signX = rs.getDouble("signX"); + double signY = rs.getDouble("signY"); + double signZ = rs.getDouble("signZ"); + double signPitch = rs.getDouble("signPitch"); + double signYaw = rs.getDouble("signYaw"); + World world = Bukkit.getWorld(signWorld); + if(world == null) continue; + Location sign = new Location(world, signX, signY, signZ, (float) signYaw, (float) signPitch); + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); + if(claim == null) continue; + // Determine owner: if the stored value is "SERVER" or empty, set ownerPlayer to null (i.e. admin claim) + String ownerStr = rs.getString("owner"); + Player ownerPlayer = null; + if(ownerStr != null && !ownerStr.isEmpty() && !ownerStr.equalsIgnoreCase("SERVER")){ + ownerPlayer = Bukkit.getOfflinePlayer(UUID.fromString(ownerStr)).getPlayer(); + } + ClaimSell cs = new ClaimSell(claim, ownerPlayer, price, sign); + claimSell.put(claimId, cs); + } + } + // --- Load ClaimRent --- + String queryRent = "SELECT * FROM " + prefix + "ClaimRent;"; + try (PreparedStatement ps = dbConnection.prepareStatement(queryRent); + ResultSet rs = ps.executeQuery()) { + while(rs.next()){ + String claimId = rs.getString("claimId"); + int duration = rs.getInt("duration"); + boolean buildTrust = rs.getBoolean("buildTrust"); + double price = rs.getDouble("price"); + boolean autoRenew = rs.getBoolean("autoRenew"); + java.sql.Timestamp ts = rs.getTimestamp("startDate"); + Date startDateDate = (ts != null) ? new Date(ts.getTime()) : null; + // Convert Date to LocalDateTime using the system default zone + LocalDateTime startDate = (startDateDate != null) + ? LocalDateTime.ofInstant(startDateDate.toInstant(), java.time.ZoneId.systemDefault()) + : null; + String signWorld = rs.getString("signWorld"); + double signX = rs.getDouble("signX"); + double signY = rs.getDouble("signY"); + double signZ = rs.getDouble("signZ"); + double signPitch = rs.getDouble("signPitch"); + double signYaw = rs.getDouble("signYaw"); + World world = Bukkit.getWorld(signWorld); + if(world == null) continue; + Location sign = new Location(world, signX, signY, signZ, (float) signYaw, (float) signPitch); + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); + if(claim == null) continue; + + // Determine owner: if the stored value is "SERVER" or empty, set ownerPlayer to null (i.e. admin claim) + String ownerStr = rs.getString("owner"); + Player ownerPlayer = null; + if(ownerStr != null && !ownerStr.isEmpty() && !ownerStr.equalsIgnoreCase("SERVER")){ + ownerPlayer = Bukkit.getOfflinePlayer(UUID.fromString(ownerStr)).getPlayer(); + } + + ClaimRent cr = new ClaimRent(claim, ownerPlayer, price, sign, duration, buildTrust); + cr.autoRenew = autoRenew; + cr.startDate = startDate; + + String buyerStr = rs.getString("buyer"); + if(buyerStr != null && !buyerStr.isEmpty()) { + cr.buyer = UUID.fromString(buyerStr); + } + claimRent.put(claimId, cr); + } - public void rent(Claim claim, Player player, double price, Location sign, int duration, int rentPeriods, boolean buildTrust) - { - ClaimRent cr = new ClaimRent(claim, claim.isAdminClaim() ? null : player, price, sign, duration, rentPeriods, buildTrust); - claimRent.put(claim.getID().toString(), cr); - cr.update(); - saveData(); - - RealEstate.instance.addLogEntry("[" + this.dateFormat.format(this.date) + "] " + (player == null ? "The Server" : player.getName()) + - " has made " + (claim.isAdminClaim() ? "an admin" : "a") + " " + (claim.parent == null ? "claim" : "subclaim") + " for" + (buildTrust ? "" : " container") + " rent at " + - "[" + claim.getLesserBoundaryCorner().getWorld() + ", " + - "X: " + claim.getLesserBoundaryCorner().getBlockX() + ", " + - "Y: " + claim.getLesserBoundaryCorner().getBlockY() + ", " + - "Z: " + claim.getLesserBoundaryCorner().getBlockZ() + "] " + - "Price: " + price + " " + RealEstate.econ.currencyNamePlural()); - - String claimPrefix = claim.isAdminClaim() ? RealEstate.instance.messages.keywordAdminClaimPrefix : - RealEstate.instance.messages.keywordClaimPrefix; - String claimTypeDisplay = claim.parent == null ? RealEstate.instance.messages.keywordClaim : - RealEstate.instance.messages.keywordSubclaim; - - if(player != null) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgInfoClaimCreatedRent, - claimPrefix, - claimTypeDisplay, - RealEstate.econ.format(price), - Utils.getTime(duration, null, false)); - } - if(RealEstate.instance.config.cfgBroadcastSell) - { - for(Player p : Bukkit.getServer().getOnlinePlayers()) - { - if(p != player) - { - Messages.sendMessage(p, RealEstate.instance.messages.msgInfoClaimCreatedRentBroadcast, - player == null ? RealEstate.instance.messages.keywordTheServer : player.getDisplayName(), - claimPrefix, - claimTypeDisplay, - RealEstate.econ.format(price), - Utils.getTime(duration, null, false)); - } - } - } - } + } + // --- Load ClaimLease --- + String queryLease = "SELECT * FROM " + prefix + "ClaimLease;"; + try (PreparedStatement ps = dbConnection.prepareStatement(queryLease); + ResultSet rs = ps.executeQuery()) { + while(rs.next()){ + String claimId = rs.getString("claimId"); + String ownerStr = rs.getString("owner"); + double price = rs.getDouble("price"); + int paymentsLeft = rs.getInt("paymentsLeft"); + int frequency = rs.getInt("frequency"); + String signWorld = rs.getString("signWorld"); + double signX = rs.getDouble("signX"); + double signY = rs.getDouble("signY"); + double signZ = rs.getDouble("signZ"); + double signPitch = rs.getDouble("signPitch"); + double signYaw = rs.getDouble("signYaw"); + World world = Bukkit.getWorld(signWorld); + if(world == null) continue; + Location sign = new Location(world, signX, signY, signZ, (float) signYaw, (float) signPitch); + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); + if(claim == null) continue; + Player ownerPlayer = Bukkit.getOfflinePlayer(UUID.fromString(ownerStr)).getPlayer(); + ClaimLease cl = new ClaimLease(claim, ownerPlayer, price, sign, frequency, paymentsLeft); + String buyerStr = rs.getString("buyer"); + if(buyerStr != null && !buyerStr.isEmpty()) { + cl.buyer = UUID.fromString(buyerStr); + } + claimLease.put(claimId, cl); + } + } + // --- Load ClaimAuction --- + String queryAuction = "SELECT * FROM " + prefix + "ClaimAuction;"; + try (PreparedStatement ps = dbConnection.prepareStatement(queryAuction); + ResultSet rs = ps.executeQuery()) { + while(rs.next()){ + String claimId = rs.getString("claimId"); + String ownerStr = rs.getString("owner"); + double bidStep = rs.getDouble("bidStep"); + java.sql.Timestamp ts = rs.getTimestamp("endDate"); + Date endDateDate = (ts != null) ? new Date(ts.getTime()) : null; + String signWorld = rs.getString("signWorld"); + double signX = rs.getDouble("signX"); + double signY = rs.getDouble("signY"); + double signZ = rs.getDouble("signZ"); + double signPitch = rs.getDouble("signPitch"); + double signYaw = rs.getDouble("signYaw"); + double price = rs.getDouble("price"); + World world = Bukkit.getWorld(signWorld); + if(world == null) continue; + Location sign = new Location(world, signX, signY, signZ, (float) signYaw, (float) signPitch); + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); + if(claim == null) continue; + Player ownerPlayer = (ownerStr != null && !ownerStr.isEmpty()) ? Bukkit.getOfflinePlayer(UUID.fromString(ownerStr)).getPlayer() : null; + // ClaimAuction constructor uses LocalDateTime for endDate. + LocalDateTime endDate = (endDateDate != null) ? LocalDateTime.ofInstant(endDateDate.toInstant(), java.time.ZoneId.systemDefault()) : null; + ClaimAuction ca = new ClaimAuction(claim, ownerPlayer, price, sign, endDate, bidStep); + String buyerStr = rs.getString("buyer"); + if(buyerStr != null && !buyerStr.isEmpty()) { + ca.buyer = UUID.fromString(buyerStr); + } + claimAuction.put(claimId, ca); + } + } + } catch (SQLException e) { + e.printStackTrace(); + } + } + + private void saveDataToDatabase() { + String prefix = RealEstate.instance.config.mysqlPrefix; + try { + // --- Save ClaimSell --- + String deleteSell = "DELETE FROM " + prefix + "ClaimSell;"; + try (PreparedStatement ps = dbConnection.prepareStatement(deleteSell)) { + ps.executeUpdate(); + } + String insertSell = "INSERT INTO " + prefix + "ClaimSell (claimId, owner, price, signWorld, signX, signY, signZ, signPitch, signYaw) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);"; + try (PreparedStatement ps = dbConnection.prepareStatement(insertSell)) { + for (ClaimSell cs : claimSell.values()) { + ps.setString(1, cs.getClaim().getId()); + ps.setString(2, cs.getClaim().isAdminClaim() ? "SERVER" : (cs.getOwnerUUID() != null ? cs.getOwnerUUID().toString() : "")); + ps.setDouble(3, cs.getPrice()); + ps.setString(4, cs.getSign().getWorld().getName()); + ps.setDouble(5, cs.getSign().getX()); + ps.setDouble(6, cs.getSign().getY()); + ps.setDouble(7, cs.getSign().getZ()); + ps.setDouble(8, cs.getSign().getPitch()); + ps.setDouble(9, cs.getSign().getYaw()); + ps.addBatch(); + } + ps.executeBatch(); + } + // --- Save ClaimRent --- + String deleteRent = "DELETE FROM " + prefix + "ClaimRent;"; + try (PreparedStatement ps = dbConnection.prepareStatement(deleteRent)) { + ps.executeUpdate(); + } + String insertRent = "INSERT INTO " + prefix + "ClaimRent (claimId, owner, duration, buildTrust, price, autoRenew, startDate, buyer, signWorld, signX, signY, signZ, signPitch, signYaw) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"; + try (PreparedStatement ps = dbConnection.prepareStatement(insertRent)) { + for (ClaimRent cr : claimRent.values()) { + ps.setString(1, cr.getClaim().getId()); + // If the claim is an admin claim, set owner to "SERVER" + ps.setString(2, cr.getClaim().isAdminClaim() ? "SERVER" : (cr.getOwnerUUID() != null ? cr.getOwnerUUID().toString() : "")); + ps.setInt(3, cr.duration); + ps.setBoolean(4, cr.buildTrust); + ps.setDouble(5, cr.getPrice()); + ps.setBoolean(6, cr.autoRenew); + if(cr.startDate != null) + ps.setTimestamp(7, new java.sql.Timestamp(java.sql.Timestamp.valueOf(cr.startDate).getTime())); + else + ps.setTimestamp(7, null); + ps.setString(8, cr.buyer != null ? cr.buyer.toString() : null); + ps.setString(9, cr.getSign().getWorld().getName()); + ps.setDouble(10, cr.getSign().getX()); + ps.setDouble(11, cr.getSign().getY()); + ps.setDouble(12, cr.getSign().getZ()); + ps.setDouble(13, cr.getSign().getPitch()); + ps.setDouble(14, cr.getSign().getYaw()); + ps.addBatch(); + } + ps.executeBatch(); + } + // --- Save ClaimLease --- + String deleteLease = "DELETE FROM " + prefix + "ClaimLease;"; + try (PreparedStatement ps = dbConnection.prepareStatement(deleteLease)) { + ps.executeUpdate(); + } + String insertLease = "INSERT INTO " + prefix + "ClaimLease (claimId, owner, price, paymentsLeft, frequency, signWorld, signX, signY, signZ, signPitch, signYaw) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"; + try (PreparedStatement ps = dbConnection.prepareStatement(insertLease)) { + for (ClaimLease cl : claimLease.values()) { + ps.setString(1, cl.getClaim().getId()); + ps.setString(2, cl.getClaim().isAdminClaim() ? "SERVER" : (cl.getOwnerUUID() != null ? cl.getOwnerUUID().toString() : "")); + ps.setDouble(3, cl.getPrice()); + ps.setInt(4, cl.getPaymentsLeft()); + ps.setInt(5, cl.getFrequency()); + ps.setString(6, cl.getSign().getWorld().getName()); + ps.setDouble(7, cl.getSign().getX()); + ps.setDouble(8, cl.getSign().getY()); + ps.setDouble(9, cl.getSign().getZ()); + ps.setDouble(10, cl.getSign().getPitch()); + ps.setDouble(11, cl.getSign().getYaw()); + ps.addBatch(); + } + ps.executeBatch(); + } + // --- Save ClaimAuction --- + String deleteAuction = "DELETE FROM " + prefix + "ClaimAuction;"; + try (PreparedStatement ps = dbConnection.prepareStatement(deleteAuction)) { + ps.executeUpdate(); + } + String insertAuction = "INSERT INTO " + prefix + "ClaimAuction (claimId, owner, bidStep, endDate, price, signWorld, signX, signY, signZ, signPitch, signYaw) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"; + try (PreparedStatement ps = dbConnection.prepareStatement(insertAuction)) { + for (ClaimAuction ca : claimAuction.values()) { + ps.setString(1, ca.getClaim().getId()); + ps.setString(2, ca.getClaim().isAdminClaim() ? "SERVER" : (ca.getOwnerUUID() != null ? ca.getOwnerUUID().toString() : "")); + ps.setDouble(3, ca.getBidStep()); + if(ca.getEndDate() != null) + ps.setTimestamp(4, new java.sql.Timestamp(java.sql.Timestamp.valueOf(ca.getEndDate()).getTime())); + else + ps.setTimestamp(4, null); + ps.setDouble(5, ca.getPrice()); + ps.setString(6, ca.getSign().getWorld().getName()); + ps.setDouble(7, ca.getSign().getX()); + ps.setDouble(8, ca.getSign().getY()); + ps.setDouble(9, ca.getSign().getZ()); + ps.setDouble(10, ca.getSign().getPitch()); + ps.setDouble(11, ca.getSign().getYaw()); + ps.addBatch(); + } + ps.executeBatch(); + } + + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public void saveData() { + if(storageType == StorageType.FILE) + saveDataToFile(); + else + saveDataToDatabase(); + } + + public boolean anyTransaction(IClaim claim) { + return claim != null && + !claim.isWilderness() && + (claimSell.containsKey(claim.getId()) || + claimRent.containsKey(claim.getId()) || + claimLease.containsKey(claim.getId()) || + claimAuction.containsKey(claim.getId())); + } + + public Transaction getTransaction(IClaim claim) { + if(claimSell.containsKey(claim.getId())) + return claimSell.get(claim.getId()); + if(claimRent.containsKey(claim.getId())) + return claimRent.get(claim.getId()); + if(claimLease.containsKey(claim.getId())) + return claimLease.get(claim.getId()); + if(claimAuction.containsKey(claim.getId())) + return claimAuction.get(claim.getId()); + return null; + } + + public void cancelTransaction(IClaim claim) { + if(anyTransaction(claim)) { + Transaction tr = getTransaction(claim); + cancelTransaction(tr); + } + saveData(); + } + + public void cancelTransaction(Transaction tr) { + if(tr.getHolder() != null) + tr.getHolder().breakNaturally(); + if(tr instanceof ClaimSell) + claimSell.remove(((ClaimSell) tr).getClaim().getId()); + if(tr instanceof ClaimRent) + claimRent.remove(((ClaimRent) tr).getClaim().getId()); + if(tr instanceof ClaimLease) + claimLease.remove(((ClaimLease) tr).getClaim().getId()); + if(tr instanceof ClaimAuction) + claimAuction.remove(((ClaimAuction) tr).getClaim().getId()); + saveData(); + } + + public boolean canCancelTransaction(Transaction tr) { + // For auction, rent, lease we check if buyer is null (or if cancellation is forced) + return tr instanceof ClaimSell || + (tr instanceof ClaimAuction && (((ClaimAuction)tr).getBuyer() == null || RealEstate.instance.config.cfgCancelAuction)) || + (tr instanceof ClaimRent && ((ClaimRent)tr).getBuyer() == null) || + (tr instanceof ClaimLease && ((ClaimLease)tr).getBuyer() == null); + } + + // Transaction creation methods: + public void sell(IClaim claim, Player player, double price, Location sign) { + ClaimSell cs = new ClaimSell(claim, claim.isAdminClaim() ? null : player, price, sign); + claimSell.put(claim.getId(), cs); + // Delay the update by one tick so that the sign's state is ready + Bukkit.getScheduler().runTaskLater(RealEstate.instance, () -> cs.update(), 1L); + saveData(); + RealEstate.instance.addLogEntry("[" + this.dateFormat.format(this.date) + "] " + + (player == null ? "The Server" : player.getName()) + + " has made " + (claim.isAdminClaim() ? "an admin" : "a") + + " " + (claim.isParentClaim() ? "claim" : "subclaim") + " for sale at " + + "[" + claim.getWorld() + ", X: " + claim.getX() + ", Y: " + claim.getY() + ", Z: " + claim.getZ() + "] " + + "Price: " + price + " " + RealEstate.econ.currencyNamePlural()); + String claimPrefix = claim.isAdminClaim() ? RealEstate.instance.messages.keywordAdminClaimPrefix : + RealEstate.instance.messages.keywordClaimPrefix; + String claimTypeDisplay = claim.isParentClaim() ? RealEstate.instance.messages.keywordClaim : + RealEstate.instance.messages.keywordSubclaim; + if (player != null) { + Messages.sendMessage(player, RealEstate.instance.messages.msgInfoClaimCreatedSell, + claimPrefix, claimTypeDisplay, RealEstate.econ.format(price)); + } + if (RealEstate.instance.config.cfgBroadcastSell) { + for (Player p : Bukkit.getServer().getOnlinePlayers()) { + if (p != player) { + Messages.sendMessage(p, RealEstate.instance.messages.msgInfoClaimCreatedSellBroadcast, + player == null ? RealEstate.instance.messages.keywordTheServer : player.getDisplayName(), + claimPrefix, claimTypeDisplay, RealEstate.econ.format(price)); + } + } + } + } - public void lease(Claim claim, Player player, double price, Location sign, int frequency, int paymentsCount) - { - ClaimLease cl = new ClaimLease(claim, claim.isAdminClaim() ? null : player, price, sign, frequency, paymentsCount); - claimLease.put(claim.getID().toString(), cl); - cl.update(); - saveData(); - - RealEstate.instance.addLogEntry("[" + this.dateFormat.format(this.date) + "] " + (player == null ? "The Server" : player.getName()) + - " has made " + (claim.isAdminClaim() ? "an admin" : "a") + " " + (claim.parent == null ? "claim" : "subclaim") + " for lease at " + - "[" + claim.getLesserBoundaryCorner().getWorld() + ", " + - "X: " + claim.getLesserBoundaryCorner().getBlockX() + ", " + - "Y: " + claim.getLesserBoundaryCorner().getBlockY() + ", " + - "Z: " + claim.getLesserBoundaryCorner().getBlockZ() + "] " + - "Payments Count : " + paymentsCount + " " + - "Price: " + price + " " + RealEstate.econ.currencyNamePlural()); - - String claimPrefix = claim.isAdminClaim() ? RealEstate.instance.messages.keywordAdminClaimPrefix : - RealEstate.instance.messages.keywordClaimPrefix; - String claimTypeDisplay = claim.parent == null ? RealEstate.instance.messages.keywordClaim : - RealEstate.instance.messages.keywordSubclaim; - - if(player != null) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgInfoClaimCreatedLease, - claimPrefix, - claimTypeDisplay, - RealEstate.econ.format(price), - paymentsCount + "", - Utils.getTime(frequency, null, false)); - } - if(RealEstate.instance.config.cfgBroadcastSell) - { - for(Player p : Bukkit.getServer().getOnlinePlayers()) - { - if(p != player) - { - Messages.sendMessage(p, RealEstate.instance.messages.msgInfoClaimCreatedLeaseBroadcast, - player == null ? RealEstate.instance.messages.keywordTheServer : player.getDisplayName(), - claimPrefix, - claimTypeDisplay, - RealEstate.econ.format(price), - paymentsCount + "", - Utils.getTime(frequency, null, false)); - } - } - } - } - public Transaction getTransaction(Player player) - { - if(player == null) return null; - Claim c = GriefPrevention.instance.dataStore.getClaimAt(player.getLocation(), false, null); - return getTransaction(c); - } + + public void rent(IClaim claim, Player player, double price, Location sign, int duration, boolean buildTrust) { + ClaimRent cr = new ClaimRent(claim, claim.isAdminClaim() ? null : player, price, sign, duration, buildTrust); + claimRent.put(claim.getId(), cr); + // Immediately update the sign (using a slight delay if needed) + Bukkit.getScheduler().runTaskLater(RealEstate.instance, () -> cr.update(), 1L); + saveData(); + RealEstate.instance.addLogEntry("[" + this.dateFormat.format(this.date) + "] " + + (player == null ? "The Server" : player.getName()) + + " has made " + (claim.isAdminClaim() ? "an admin" : "a") + + " " + (claim.isParentClaim() ? "claim" : "subclaim") + " for rent at " + + "[" + claim.getWorld() + ", X: " + claim.getX() + ", Y: " + claim.getY() + ", Z: " + claim.getZ() + "] " + + "Price: " + price + " " + RealEstate.econ.currencyNamePlural()); + String claimPrefix = claim.isAdminClaim() ? RealEstate.instance.messages.keywordAdminClaimPrefix : + RealEstate.instance.messages.keywordClaimPrefix; + String claimTypeDisplay = claim.isParentClaim() ? RealEstate.instance.messages.keywordClaim : + RealEstate.instance.messages.keywordSubclaim; + if(player != null) { + Messages.sendMessage(player, RealEstate.instance.messages.msgInfoClaimCreatedRent, + claimPrefix, claimTypeDisplay, RealEstate.econ.format(price), + Utils.getTime(duration, null, false)); + } + if(RealEstate.instance.config.cfgBroadcastSell) { + for(Player p : Bukkit.getServer().getOnlinePlayers()) { + if(p != player) { + Messages.sendMessage(p, RealEstate.instance.messages.msgInfoClaimCreatedRentBroadcast, + player == null ? RealEstate.instance.messages.keywordTheServer : player.getDisplayName(), + claimPrefix, claimTypeDisplay, RealEstate.econ.format(price), + Utils.getTime(duration, null, false)); + } + } + } + } + + public void lease(IClaim claim, Player player, double price, Location sign, int frequency, int paymentsCount) { + ClaimLease cl = new ClaimLease(claim, claim.isAdminClaim() ? null : player, price, sign, frequency, paymentsCount); + claimLease.put(claim.getId(), cl); + // Immediately update the sign (using a slight delay if needed) + Bukkit.getScheduler().runTaskLater(RealEstate.instance, () -> cl.update(), 1L); + saveData(); + RealEstate.instance.addLogEntry("[" + this.dateFormat.format(this.date) + "] " + + (player == null ? "The Server" : player.getName()) + + " has made " + (claim.isAdminClaim() ? "an admin" : "a") + + " " + (claim.isParentClaim() ? "claim" : "subclaim") + " for lease at " + + "[" + claim.getWorld() + ", X: " + claim.getX() + ", Y: " + claim.getY() + ", Z: " + claim.getZ() + "] " + + "Payments Count: " + paymentsCount + " Price: " + price + " " + RealEstate.econ.currencyNamePlural()); + String claimPrefix = claim.isAdminClaim() ? RealEstate.instance.messages.keywordAdminClaimPrefix : + RealEstate.instance.messages.keywordClaimPrefix; + String claimTypeDisplay = claim.isParentClaim() ? RealEstate.instance.messages.keywordClaim : + RealEstate.instance.messages.keywordSubclaim; + if(player != null) { + Messages.sendMessage(player, RealEstate.instance.messages.msgInfoClaimCreatedLease, + claimPrefix, claimTypeDisplay, RealEstate.econ.format(price), + String.valueOf(paymentsCount), Utils.getTime(frequency, null, false)); + } + if(RealEstate.instance.config.cfgBroadcastSell) { + for(Player p : Bukkit.getServer().getOnlinePlayers()) { + if(p != player) { + Messages.sendMessage(p, RealEstate.instance.messages.msgInfoClaimCreatedLeaseBroadcast, + player == null ? RealEstate.instance.messages.keywordTheServer : player.getDisplayName(), + claimPrefix, claimTypeDisplay, RealEstate.econ.format(price), + String.valueOf(paymentsCount), Utils.getTime(frequency, null, false)); + } + } + } + } + + public void auction(IClaim claim, Player player, double price, Location sign, int duration, double bidStep) { + LocalDateTime endDate = LocalDateTime.now().plusDays(duration); + // Use the Auction constructor with a LocalDateTime endDate. + ClaimAuction ca = new ClaimAuction(claim, claim.isAdminClaim() ? null : player, price, sign, endDate, bidStep); + claimAuction.put(claim.getId(), ca); + // Immediately update the sign (using a slight delay if needed) + Bukkit.getScheduler().runTaskLater(RealEstate.instance, () -> ca.update(), 1L); + saveData(); + RealEstate.instance.addLogEntry("[" + this.dateFormat.format(this.date) + "] " + + (player == null ? "The Server" : player.getName()) + + " has made " + (claim.isAdminClaim() ? "an admin" : "a") + + " " + (claim.isParentClaim() ? "claim" : "subclaim") + " for auction at " + + "[" + claim.getWorld() + ", X: " + claim.getX() + ", Y: " + claim.getY() + ", Z: " + claim.getZ() + "] " + + "Price: " + price + " " + RealEstate.econ.currencyNamePlural() + + " Bid Step: " + bidStep + " " + RealEstate.econ.currencyNamePlural() + + " End Date: " + endDate.toString()); + String claimPrefix = claim.isAdminClaim() ? RealEstate.instance.messages.keywordAdminClaimPrefix : + RealEstate.instance.messages.keywordClaimPrefix; + String claimTypeDisplay = claim.isParentClaim() ? RealEstate.instance.messages.keywordClaim : + RealEstate.instance.messages.keywordSubclaim; + if(player != null) { + Messages.sendMessage(player, RealEstate.instance.messages.msgInfoClaimCreatedAuction, + claimPrefix, claimTypeDisplay, RealEstate.econ.format(price), + RealEstate.econ.format(bidStep), Utils.getTime(duration, null, false)); + } + if(RealEstate.instance.config.cfgBroadcastSell) { + for(Player p : Bukkit.getServer().getOnlinePlayers()) { + if(p != player) { + Messages.sendMessage(p, RealEstate.instance.messages.msgInfoClaimCreatedAuctionBroadcast, + player == null ? RealEstate.instance.messages.keywordTheServer : player.getDisplayName(), + claimPrefix, claimTypeDisplay, RealEstate.econ.format(price), + RealEstate.econ.format(bidStep), Utils.getTime(duration, null, false)); + } + } + } + } + + public Transaction getTransaction(Player player) { + if(player == null) return null; + IClaim c = RealEstate.claimAPI.getClaimAt(player.getLocation()); + return getTransaction(c); + } } diff --git a/src/me/EtienneDx/RealEstate/Utils.java b/src/me/EtienneDx/RealEstate/Utils.java index 3758c8e..339b329 100644 --- a/src/me/EtienneDx/RealEstate/Utils.java +++ b/src/me/EtienneDx/RealEstate/Utils.java @@ -6,57 +6,61 @@ import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; -import me.ryanhamshire.GriefPrevention.Claim; -import me.ryanhamshire.GriefPrevention.ClaimPermission; -import me.ryanhamshire.GriefPrevention.GriefPrevention; -import me.ryanhamshire.GriefPrevention.PlayerData; +import me.EtienneDx.RealEstate.ClaimAPI.ClaimPermission; +import me.EtienneDx.RealEstate.ClaimAPI.IClaim; +import me.EtienneDx.RealEstate.ClaimAPI.IPlayerData; import net.milkbowl.vault.economy.EconomyResponse; public class Utils { - public static boolean makePayment(UUID receiver, UUID giver, double amount, boolean msgSeller, boolean msgBuyer) + public static boolean makePayment(UUID receiver, UUID giver, double amount, boolean msgReceiver, boolean msgGiver) { // seller might be null if it is the server - OfflinePlayer s = receiver != null ? Bukkit.getOfflinePlayer(receiver) : null, b = Bukkit.getOfflinePlayer(giver); - if(!RealEstate.econ.has(b, amount)) + OfflinePlayer giveTo = receiver != null ? Bukkit.getOfflinePlayer(receiver) : null; + OfflinePlayer takeFrom = giver != null ? Bukkit.getOfflinePlayer(giver) : null; + if(takeFrom != null && !RealEstate.econ.has(takeFrom, amount)) { - if(b.isOnline() && msgBuyer) + if(takeFrom.isOnline() && msgGiver) { - Messages.sendMessage(b.getPlayer(), RealEstate.instance.messages.msgErrorNoMoneySelf); + Messages.sendMessage(takeFrom.getPlayer(), RealEstate.instance.messages.msgErrorNoMoneySelf); } - if(s != null && s.isOnline() && msgSeller) + if(giveTo != null && giveTo.isOnline() && msgReceiver) { - Messages.sendMessage(s.getPlayer(), RealEstate.instance.messages.msgErrorNoMoneyOther, b.getName()); + Messages.sendMessage(giveTo.getPlayer(), RealEstate.instance.messages.msgErrorNoMoneyOther, takeFrom.getName()); } return false; } - EconomyResponse resp = RealEstate.econ.withdrawPlayer(b, amount); - if(!resp.transactionSuccess()) - { - if(b.isOnline() && msgBuyer) - { - Messages.sendMessage(b.getPlayer(), RealEstate.instance.messages.msgErrorNoWithdrawSelf); - } - if(s != null && s.isOnline() && msgSeller) - { - Messages.sendMessage(b.getPlayer(), RealEstate.instance.messages.msgErrorNoWithdrawOther); - } - return false; - } - if(s != null) + if(takeFrom != null) + { + EconomyResponse resp = RealEstate.econ.withdrawPlayer(takeFrom, amount); + if(!resp.transactionSuccess()) + { + if(takeFrom.isOnline() && msgGiver) + { + Messages.sendMessage(takeFrom.getPlayer(), RealEstate.instance.messages.msgErrorNoWithdrawSelf); + } + if(giveTo != null && giveTo.isOnline() && msgReceiver) + { + Messages.sendMessage(giveTo.getPlayer(), RealEstate.instance.messages.msgErrorNoWithdrawOther); + } + return false; + } + } + if(giveTo != null) { - resp = RealEstate.econ.depositPlayer(s, amount); + EconomyResponse resp = RealEstate.econ.depositPlayer(giveTo, amount); if(!resp.transactionSuccess()) { - if(b.isOnline() && msgBuyer) + if(takeFrom != null && takeFrom.isOnline() && msgGiver) { - Messages.sendMessage(b.getPlayer(), RealEstate.instance.messages.msgErrorNoDepositOther, s.getName()); + Messages.sendMessage(giveTo.getPlayer(), RealEstate.instance.messages.msgErrorNoDepositOther, giveTo.getName()); } - if(s != null && s.isOnline() && msgSeller) + if(takeFrom != null && giveTo != null && giveTo.isOnline() && msgReceiver) { - Messages.sendMessage(b.getPlayer(), RealEstate.instance.messages.msgErrorNoDepositSelf, b.getName()); + Messages.sendMessage(takeFrom.getPlayer(), RealEstate.instance.messages.msgErrorNoDepositSelf, takeFrom.getName()); } - RealEstate.econ.depositPlayer(b, amount); + // refund + RealEstate.econ.depositPlayer(takeFrom, amount); return false; } } @@ -87,18 +91,18 @@ public static String getTime(int days, Duration hours, boolean details) return time; } - public static void transferClaim(Claim claim, UUID buyer, UUID seller) + public static void transferClaim(IClaim claim, UUID buyer, UUID seller) { // blocks transfer : // if transfert is true, the seller will lose the blocks he had // and the buyer will get them // (that means the buyer will keep the same amount of remaining blocks after the transaction) - if(claim.parent == null && RealEstate.instance.config.cfgTransferClaimBlocks) + if(claim.isParentClaim() && RealEstate.instance.config.cfgTransferClaimBlocks) { - PlayerData buyerData = GriefPrevention.instance.dataStore.getPlayerData(buyer); + IPlayerData buyerData = RealEstate.claimAPI.getPlayerData(buyer); if(seller != null) { - PlayerData sellerData = GriefPrevention.instance.dataStore.getPlayerData(seller); + IPlayerData sellerData = RealEstate.claimAPI.getPlayerData(seller); // the seller has to provide the blocks sellerData.setBonusClaimBlocks(sellerData.getBonusClaimBlocks() - claim.getArea()); @@ -114,21 +118,23 @@ public static void transferClaim(Claim claim, UUID buyer, UUID seller) } // start to change owner - if(claim.parent == null) - for(Claim child : claim.children) + if(claim.isParentClaim()) + { + for(IClaim child : claim.getChildren()) { - child.clearPermissions(); - child.managers.clear(); + child.clearPlayerPermissions(); + child.clearManagers(); } - claim.clearPermissions(); + } + claim.clearPlayerPermissions(); try { - if(claim.parent == null) - GriefPrevention.instance.dataStore.changeClaimOwner(claim, buyer); + if(claim.isParentClaim()) + RealEstate.claimAPI.changeClaimOwner(claim, buyer); else { - claim.setPermission(buyer.toString(), ClaimPermission.Build); + claim.addPlayerPermissions(buyer, ClaimPermission.BUILD); } } catch (Exception e)// error occurs when trying to change subclaim owner @@ -136,7 +142,7 @@ public static void transferClaim(Claim claim, UUID buyer, UUID seller) e.printStackTrace(); return; } - GriefPrevention.instance.dataStore.saveClaim(claim); + RealEstate.claimAPI.saveClaim(claim); } From 224702e4a2595f9487864acd611b7a4c5e02213e Mon Sep 17 00:00:00 2001 From: Michael Burgess Date: Fri, 14 Feb 2025 21:25:19 -0500 Subject: [PATCH 04/14] Version 1.4.3 Please see changelog --- CHANGELOG.md | 38 +++++ paper-plugin.yml | 12 ++ plugin.yml | 3 + pom.xml | 28 +++- resources/languages/es.yml | 54 ++++++ resources/languages/ru.yml | 54 ++++++ .../ClaimAPI/WorldGuard/WGClaim.java | 154 ++++++++++++++++++ .../ClaimAPI/WorldGuard/WGPlayerData.java | 34 ++++ .../ClaimAPI/WorldGuard/WorldGuardAPI.java | 61 +++++++ src/me/EtienneDx/RealEstate/RealEstate.java | 34 ++-- .../Transactions/BoughtTransaction.java | 1 - 11 files changed, 456 insertions(+), 17 deletions(-) create mode 100644 resources/languages/es.yml create mode 100644 resources/languages/ru.yml create mode 100644 src/me/EtienneDx/RealEstate/ClaimAPI/WorldGuard/WGClaim.java create mode 100644 src/me/EtienneDx/RealEstate/ClaimAPI/WorldGuard/WGPlayerData.java create mode 100644 src/me/EtienneDx/RealEstate/ClaimAPI/WorldGuard/WorldGuardAPI.java diff --git a/CHANGELOG.md b/CHANGELOG.md index ec6158b..c68495d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,43 @@ # Changelog +# Version 1.4.3 (2025-02-14) + +## New Features + +- **Admin Claim Detection:** + - Improved the WorldGuard integration so that claims with no owner (i.e. an empty owner field) are now treated as admin claims. + - In such cases, the claim’s owner is set to a constant `SERVER_UUID` (and displayed as "SERVER") for consistency in sign updates and transaction validations. + +- **Instant Sign Update for Rent/Lease:** + - Modified the ClaimRent and ClaimLease update logic to update signs instantly—bringing their behavior in line with ClaimSell—so that players see the correct information immediately after placement. + +## Bug Fixes + +- **Owner Verification Issue:** + - Fixed an issue where players were incorrectly receiving the “You can only sell/rent/lease claim you own!” message on sign interaction. + - The plugin now correctly determines claim ownership (including admin claims) using the updated WGClaim methods. + +- **Database Migration for Admin Claims:** + - Updated the SQL insert logic so that if a claim is detected as an admin claim, the owner field is set to the SERVER identifier. + - This change ensures consistent behavior when loading data from the database. + +- **WorldGuard Integration Problems:** + - Resolved compatibility issues with WorldGuard and WorldEdit by updating our WGClaim implementation and adjusting our import statements. + - The plugin now properly retrieves regions and their flags using the latest WorldGuard API. + +## Improvements + +- **Code Refactoring:** + - Cleaned up various sections of the code for better readability and maintainability. + - Improved error handling and logging to make troubleshooting easier. + +- **Build & Dependency Updates:** + - Updated the `pom.xml` to ensure proper integration with the latest versions of WorldGuard, WorldEdit, Vault, and other dependencies. + +- **Documentation & Messaging:** + - Revised in-game messages and log outputs to provide clearer feedback for both players and administrators. + + # Version 1.4.2 (2025-02-13) ### Admin Claim Support Improvements: * When processing ClaimRent transactions, if a claim is identified as an admin claim, its owner is now set to "SERVER" (using a fixed UUID or identifier) to ensure correct behavior. diff --git a/paper-plugin.yml b/paper-plugin.yml index d1d8fd4..47d2d24 100644 --- a/paper-plugin.yml +++ b/paper-plugin.yml @@ -25,6 +25,18 @@ dependencies: load: BEFORE required: false join-classpath: true + WorldGuard: + load: BEFORE + required: false + join-classpath: true + WorldEdit: + load: BEFORE + required: false + join-classpath: true + Towny: + load: BEFORE + required: false + join-classpath: true commands: re: description: Access to the claim transaction information. diff --git a/plugin.yml b/plugin.yml index 54a396b..bea5ac1 100644 --- a/plugin.yml +++ b/plugin.yml @@ -13,6 +13,9 @@ depend: softdepend: - GriefPrevention - GriefDefender + - WorldGuard + - WorldEdit + - Towny api-version: 1.21.4 commands: re: diff --git a/pom.xml b/pom.xml index 36914a4..2f85ab1 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 Me.EtienneDx real-estate - 1.4.2 + 1.4.3 RealEstate A spigot plugin for selling, renting, leasing and auctioning GriefPrevention and GriefDefender claims @@ -129,6 +129,14 @@ papermc https://repo.papermc.io/repository/maven-public/ + + sk89q-repo + https://maven.enginehub.org/repo/ + + + glaremasters repo + https://repo.glaremasters.me/repository/towny/ + @@ -183,5 +191,23 @@ 1.21.4-R0.1-SNAPSHOT provided + + com.sk89q.worldguard + worldguard-bukkit + 7.0.5 + provided + + + com.sk89q.worldedit + worldedit-bukkit + 7.2.0 + provided + + + com.palmergames.bukkit.towny + towny + 0.101.1.0 + provided + diff --git a/resources/languages/es.yml b/resources/languages/es.yml new file mode 100644 index 0000000..2202ed1 --- /dev/null +++ b/resources/languages/es.yml @@ -0,0 +1,54 @@ +# Usa un editor YAML como NotepadPlusPlus para editar este archivo. +# Después de editar, haz una copia de seguridad de tus cambios antes de recargar el servidor en caso de que hayas cometido un error de sintaxis. +# Usa signos de dólar ($) para los códigos de formato, que están documentados aquí: http://minecraft.gamepedia.com/Formatting_codes. +# Puedes usar {0}, {1} para incluir los diferentes valores indicados en los comentarios. +RealEstate: + Keywords: + # Palabras clave utilizadas dentro de otros mensajes pero con un texto más largo al + # final solo porque necesito probar algunas cosas. + Enabled: habilitado + Disabled: deshabilitado + Claim: reclamación + Subclaim: subreclamación + AdminClaimPrefix: un administrador + ClaimPrefix: un + TheServer: El servidor + NoTransactionFound: $c¡No se encontró ninguna transacción! + NoTransactionFoundHere: $c¡No se encontró ninguna transacción en tu ubicación! + PageMustBePositive: $cLa página debe ser una opción positiva. + PageNotExists: $c¡Esta página no existe! + # 0: habilitado/deshabilitado; 1: + # tipo de reclamación + RenewRentNow: $bLa renovación automática ahora está $a{0} $bpara esta {1} + # 0: habilitado/deshabilitado; + # 1: tipo de reclamación + RenewRentCurrently: $bLa renovación automática actualmente está $a{0} $bpara esta {1} + Errors: + OutOfClaim: $c¡Debes estar dentro de una reclamación para usar este comando! + PlayerOnlyCmd: $c¡Solo los jugadores pueden ejecutar este comando! + NoOngoingTransaction: $c¡Esta reclamación no tiene transacciones en curso! + NotRentNorLease: $c¡Esta reclamación no está en alquiler ni arrendamiento! + AlreadyBought: $c¡Esta reclamación ya tiene un comprador! + NotPartOfTransaction: $c¡No eres parte de esta transacción! + RentOnly: $c¡Este comando solo se aplica a reclamaciones alquiladas! + AuctionOnly: $c¡Este comando solo se aplica a reclamaciones subastadas! + ValueGreaterThanZero: $c¡El valor debe ser mayor que cero! + InvalidOption: $c¡Opción inválida proporcionada! + List: + # 0: Ofertas de RE|Ofertas de Venta|Ofertas de Alquiler|Ofertas de Arrendamiento; 1: Número de página; 2: + # Número total de páginas + Header: $1----= $f[ $6{0} página $2 {1} $6/ $2{2} $f] $1=---- + # 0: todo|venta|alquiler|arrendamiento; 1: + # número de la próxima página + NextPage: $6Para ver la próxima página, escribe $a/re list {0} {1} + Sign: + Auction: + # 0: nombre del jugador, 1: + # precio formateado + HighestBidder: '$b{0}: $a{1}' + NoBider: $bSin postor + # 0: tiempo restante formateado + RemainingTime: $b$a{0} + Ended: $bSubasta finalizada + # siguiente línea: ganador + Won: $bSubasta ganada por diff --git a/resources/languages/ru.yml b/resources/languages/ru.yml new file mode 100644 index 0000000..b0251c7 --- /dev/null +++ b/resources/languages/ru.yml @@ -0,0 +1,54 @@ +# Используйте редактор YAML, например NotepadPlusPlus, для редактирования этого файла. +# После редактирования сделайте резервную копию изменений перед перезапуском сервера на случай ошибки синтаксиса. +# Используйте знаки доллара ($) для кодов форматирования, которые задокументированы здесь: http://minecraft.gamepedia.com/Formatting_codes. +# Вы можете использовать {0}, {1}, чтобы включить различные значения, указанные в комментариях. +RealEstate: + Keywords: + # Ключевые слова, используемые в других сообщениях, но с более длинным текстом в конце, + # просто потому что мне нужно протестировать некоторые вещи. + Enabled: включено + Disabled: отключено + Claim: владение + Subclaim: субвладение + AdminClaimPrefix: администраторское + ClaimPrefix: обычное + TheServer: Сервер + NoTransactionFound: $cНе найдено ни одной транзакции! + NoTransactionFoundHere: $cНа вашем местоположении не найдено ни одной транзакции! + PageMustBePositive: $cСтраница должна быть положительным числом. + PageNotExists: $cТакой страницы не существует! + # 0: включено/отключено; 1: + # тип владения + RenewRentNow: $bАвтоматическое обновление теперь $a{0} $bдля этого {1} + # 0: включено/отключено; + # 1: тип владения + RenewRentCurrently: $bАвтоматическое обновление в настоящее время $a{0} $bдля этого {1} + Errors: + OutOfClaim: $cВы должны находиться внутри владения, чтобы использовать эту команду! + PlayerOnlyCmd: $cЭта команда доступна только для игроков! + NoOngoingTransaction: $cЭто владение не участвует ни в одной транзакции! + NotRentNorLease: $cЭто владение не сдается в аренду и не передается в лизинг! + AlreadyBought: $cЭто владение уже куплено! + NotPartOfTransaction: $cВы не участвуете в этой транзакции! + RentOnly: $cЭта команда применяется только к арендуемым владениям! + AuctionOnly: $cЭта команда применяется только к аукционным владениям! + ValueGreaterThanZero: $cЗначение должно быть больше нуля! + InvalidOption: $cУказан неверный параметр! + List: + # 0: Оферты RE|Продажа|Аренда|Лизинг; 1: Номер страницы; 2: + # Общее количество страниц + Header: $1----= $f[ $6{0} страница $2 {1} $6/ $2{2} $f] $1=---- + # 0: всё|продажа|аренда|лизинг; 1: + # номер следующей страницы + NextPage: $6Чтобы увидеть следующую страницу, введите $a/re list {0} {1} + Sign: + Auction: + # 0: имя игрока, 1: + # форматированная цена + HighestBidder: '$b{0}: $a{1}' + NoBider: $bНет ставок + # 0: оставшееся время в формате + RemainingTime: $b$a{0} + Ended: $bАукцион завершен + # следующая строка: победитель + Won: $bАукцион выиграл diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/WorldGuard/WGClaim.java b/src/me/EtienneDx/RealEstate/ClaimAPI/WorldGuard/WGClaim.java new file mode 100644 index 0000000..376788b --- /dev/null +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/WorldGuard/WGClaim.java @@ -0,0 +1,154 @@ +package me.EtienneDx.RealEstate.ClaimAPI.WorldGuard; + +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import org.bukkit.Bukkit; +import org.bukkit.World; +import java.util.Collections; +import java.util.UUID; +import me.EtienneDx.RealEstate.ClaimAPI.IClaim; +import me.EtienneDx.RealEstate.RealEstate; +import me.EtienneDx.RealEstate.ClaimAPI.ClaimPermission; + +public class WGClaim implements IClaim { + + // A constant used to represent the "server" owner for admin claims. + public static final UUID SERVER_UUID = new UUID(0L, 0L); + + private final ProtectedRegion region; + private final World world; + + public WGClaim(ProtectedRegion region, World world) { + this.region = region; + this.world = world; + } + + @Override + public String getId() { + return region.getId(); + } + + @Override + public int getArea() { + // Convert the region’s minimum and maximum BlockVector3 into dimensions. + BlockVector3 min = region.getMinimumPoint(); + BlockVector3 max = region.getMaximumPoint(); + int width = max.getX() - min.getX(); + int length = max.getZ() - min.getZ(); + return width * length; + } + + @Override + public World getWorld() { + return world; + } + + @Override + public int getX() { + return (int) region.getMinimumPoint().getX(); + } + + @Override + public int getY() { + return (int) region.getMinimumPoint().getY(); + } + + @Override + public int getZ() { + return (int) region.getMinimumPoint().getZ(); + } + + @Override + public boolean isAdminClaim() { + // If the region has no owners, treat it as an admin claim. + return region.getOwners().getUniqueIds().isEmpty(); + } + + @Override + public Iterable getChildren() { + // WorldGuard does not support child claims; return an empty list. + return Collections.emptyList(); + } + + @Override + public boolean isWilderness() { + // For our purposes, assume WorldGuard regions are never "wilderness". + return false; + } + + @Override + public boolean isSubClaim() { + // Not supported. + return false; + } + + @Override + public boolean isParentClaim() { + // Every region here is treated as a parent claim. + return true; + } + + @Override + public IClaim getParent() { + // Not supported. + return null; + } + + @Override + public void dropPlayerPermissions(UUID player) { + // Not implemented. + } + + @Override + public void addPlayerPermissions(UUID player, ClaimPermission permission) { + // Not implemented. + } + + @Override + public void clearPlayerPermissions() { + // Not implemented. + } + + @Override + public void removeManager(UUID player) { + // Not implemented. + } + + @Override + public void addManager(UUID player) { + // Not implemented. + } + + @Override + public void clearManagers() { + // Not implemented. + } + + @Override + public UUID getOwner() { + // If no owners, then this is an admin claim. Return the constant SERVER_UUID. + if (isAdminClaim()) { + return SERVER_UUID; + } + // Otherwise, return the first UUID in the owner set. + return region.getOwners().getUniqueIds().iterator().next(); + } + + @Override + public String getOwnerName() { + // If it's an admin claim, return a fixed name. + if (isAdminClaim()) { + return "SERVER"; + } + // Otherwise, look up the owner by UUID. + UUID owner = getOwner(); + return (owner != null && Bukkit.getOfflinePlayer(owner) != null) + ? Bukkit.getOfflinePlayer(owner).getName() : "Unknown"; + } + + @Override + public void setInheritPermissions(boolean inherit) { + // Not supported. + } +} diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/WorldGuard/WGPlayerData.java b/src/me/EtienneDx/RealEstate/ClaimAPI/WorldGuard/WGPlayerData.java new file mode 100644 index 0000000..02deb8d --- /dev/null +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/WorldGuard/WGPlayerData.java @@ -0,0 +1,34 @@ +package me.EtienneDx.RealEstate.ClaimAPI.WorldGuard; + +import me.EtienneDx.RealEstate.ClaimAPI.IPlayerData; + +public class WGPlayerData implements IPlayerData { + + // Since WorldGuard does not supply claim block data, + // we return zero or do nothing in our dummy implementation. + + @Override + public int getAccruedClaimBlocks() { + return 0; + } + + @Override + public int getBonusClaimBlocks() { + return 0; + } + + @Override + public void setAccruedClaimBlocks(int accruedClaimBlocks) { + // No-op. + } + + @Override + public void setBonusClaimBlocks(int bonusClaimBlocks) { + // No-op. + } + + @Override + public int getRemainingClaimBlocks() { + return 0; + } +} diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/WorldGuard/WorldGuardAPI.java b/src/me/EtienneDx/RealEstate/ClaimAPI/WorldGuard/WorldGuardAPI.java new file mode 100644 index 0000000..f34d8a6 --- /dev/null +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/WorldGuard/WorldGuardAPI.java @@ -0,0 +1,61 @@ +package me.EtienneDx.RealEstate.ClaimAPI.WorldGuard; + +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.protection.regions.RegionContainer; +import com.sk89q.worldguard.protection.regions.RegionQuery; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import org.bukkit.Location; +import org.bukkit.World; +import me.EtienneDx.RealEstate.ClaimAPI.IClaim; +import me.EtienneDx.RealEstate.ClaimAPI.IClaimAPI; +import me.EtienneDx.RealEstate.ClaimAPI.IPlayerData; + +public class WorldGuardAPI implements IClaimAPI { + + private final RegionContainer container; + + public WorldGuardAPI() { + // Obtain the RegionContainer via the WorldGuard platform API. + container = WorldGuard.getInstance().getPlatform().getRegionContainer(); + } + + @Override + public IClaim getClaimAt(Location bukkitLocation) { + // Convert the Bukkit location to a WorldEdit location using BukkitAdapter. + com.sk89q.worldedit.util.Location weLocation = BukkitAdapter.adapt(bukkitLocation); + RegionQuery query = container.createQuery(); + // Use size() instead of isEmpty() for ApplicableRegionSet + ApplicableRegionSet regions = query.getApplicableRegions(weLocation); + if (regions.size() > 0) { + // For this example, we take the first region. + ProtectedRegion region = regions.iterator().next(); + // Wrap the region in our WGClaim implementation. + return new WGClaim(region, bukkitLocation.getWorld()); + } + return null; + } + + @Override + public void saveClaim(IClaim claim) { + // WorldGuard auto-saves; no action needed. + } + + @Override + public IPlayerData getPlayerData(java.util.UUID player) { + // Not supported in this implementation; return null or a dummy implementation. + return null; + } + + @Override + public void changeClaimOwner(IClaim claim, java.util.UUID newOwner) { + // Changing a claim's owner is not supported by WorldGuard. + throw new UnsupportedOperationException("Changing claim owner is not supported with WorldGuard."); + } + + @Override + public void registerEvents() { + // No events to register for WorldGuard integration in this example. + } +} diff --git a/src/me/EtienneDx/RealEstate/RealEstate.java b/src/me/EtienneDx/RealEstate/RealEstate.java index b2c26ea..ba2429b 100644 --- a/src/me/EtienneDx/RealEstate/RealEstate.java +++ b/src/me/EtienneDx/RealEstate/RealEstate.java @@ -99,21 +99,17 @@ public void onEnable() return; } - if(setupGriefPreventionAPI()) - { - this.log.info("RealEstate is using GriefPrevention as a claim management plugin."); - } - else if(setupGriefDefenderAPI()) - { - this.log.info("RealEstate is using GriefDefender as a claim management plugin."); - } - /**Insert alternate claim APIs here**/ - else - { - this.log.warning("No compatible Claim API detected. Please install GriefPrevention or GriefDefender."); - this.log.warning("Disabling RealEstate..."); - getPluginLoader().disablePlugin(this); - return; + if(setupGriefPreventionAPI()) { + this.log.info("RealEstate is using GriefPrevention as a claim management plugin."); + } else if(setupGriefDefenderAPI()) { + this.log.info("RealEstate is using GriefDefender as a claim management plugin."); + } else if(setupWorldGuardAPI()) { + this.log.info("RealEstate is using WorldGuard as a claim management plugin."); + } else { + this.log.severe("No compatible Claim API detected. Please install GriefPrevention, GriefDefender, or WorldGuard."); + this.log.severe("Disabling RealEstate..."); + getPluginLoader().disablePlugin(this); + return; } if((ess = (Essentials)getServer().getPluginManager().getPlugin("Essentials")) != null) @@ -363,6 +359,14 @@ private boolean setupPermissions() perms = (Permission)rsp.getProvider(); return perms != null; } + + private boolean setupWorldGuardAPI() { + if(getServer().getPluginManager().getPlugin("WorldGuard") != null) { + claimAPI = new me.EtienneDx.RealEstate.ClaimAPI.WorldGuard.WorldGuardAPI(); + return true; + } + return false; + } private void copyResourcesIntoPluginDirectory() { diff --git a/src/me/EtienneDx/RealEstate/Transactions/BoughtTransaction.java b/src/me/EtienneDx/RealEstate/Transactions/BoughtTransaction.java index 725e1b9..d2618ad 100644 --- a/src/me/EtienneDx/RealEstate/Transactions/BoughtTransaction.java +++ b/src/me/EtienneDx/RealEstate/Transactions/BoughtTransaction.java @@ -3,7 +3,6 @@ import java.util.Map; import java.util.UUID; import org.bukkit.Location; -import org.bukkit.configuration.serialization.ConfigurationSerializable; import org.bukkit.entity.Player; import me.EtienneDx.RealEstate.ClaimAPI.IClaim; From b9072c64f916e46238aebc2237bcd38d50881bf5 Mon Sep 17 00:00:00 2001 From: Michael Burgess Date: Fri, 14 Feb 2025 21:39:21 -0500 Subject: [PATCH 05/14] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4d7e442..4e83aba 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,12 @@ RealEstate is a spigot plugin built on top of GriefPrevention to provide players with an ability to sell and rent claims to other players. -The plugin is documented in the [GitHub wiki](https://github.com/EtienneDx/RealEstate/wiki). +The plugin is documented [here](https://www.michael-burgess.xyz/minecraft-plugins/realestate/). -Please feel free to report any issue in the [GitHub Issue section](https://github.com/EtienneDx/RealEstate/issues). +Please feel free to report any issue in the [GitHub Issue section](https://github.com/msburgess3200/RealEstate/issues). ## GriefPrevention This plugin is dependent on GriefPrevention version 16.18 and up. -GriefPrevention plugin can be found [here](https://github.com/TechFortress/GriefPrevention). \ No newline at end of file +GriefPrevention plugin can be found [here](https://github.com/GriefPrevention/GriefPrevention). \ No newline at end of file From 2d6becea5a717db947129989d69a1d6f44e43b28 Mon Sep 17 00:00:00 2001 From: Michael Burgess Date: Sun, 16 Feb 2025 16:54:07 -0500 Subject: [PATCH 06/14] Update pt-br.yml --- resources/languages/pt-br.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/resources/languages/pt-br.yml b/resources/languages/pt-br.yml index c1dc37c..801417b 100644 --- a/resources/languages/pt-br.yml +++ b/resources/languages/pt-br.yml @@ -3,8 +3,6 @@ # Use dollar signs ($) for formatting codes, which are documented here: http://minecraft.gamepedia.com/Formatting_codes. # You can use {0}, {1} to include the different values indicated in the comments RealEstate: - lang: - version: 1.0 Keywords: # Keywords used within other messages but with a longer text at # the end just because i need to test some stuff From ff2e2598ef76b145fe6023fa064dd3c7432f4089 Mon Sep 17 00:00:00 2001 From: Michael Burgess Date: Sun, 16 Feb 2025 17:03:33 -0500 Subject: [PATCH 07/14] Create en-us.yml Include a static file to reduce plugin load time. --- resources/languages/en-us.yml | 421 ++++++++++++++++++++++++++++++++++ 1 file changed, 421 insertions(+) create mode 100644 resources/languages/en-us.yml diff --git a/resources/languages/en-us.yml b/resources/languages/en-us.yml new file mode 100644 index 0000000..9f20d9d --- /dev/null +++ b/resources/languages/en-us.yml @@ -0,0 +1,421 @@ +# Use a YAML editor like NotepadPlusPlus to edit this file. +# After editing, back up your changes before reloading the server in case you made a syntax error. +# Use dollar signs ($) for formatting codes, which are documented here: http://minecraft.gamepedia.com/Formatting_codes. +# You can use {0}, {1} to include the different values indicated in the comments +RealEstate: + Keywords: + # Keywords used within other messages but with a longer text at + # the end just because i need to test some stuff + Enabled: enabled + Disabled: disabled + Claim: claim + Subclaim: subclaim + AdminClaimPrefix: an admin + ClaimPrefix: a + TheServer: The server + NoTransactionFound: $cNo transaction found! + NoTransactionFoundHere: $cNo transaction found at your location! + PageMustBePositive: $cPage must be a positive option + PageNotExists: $cThis page does not exist! + # 0: enabled/disabled; 1: + # type of claim + RenewRentNow: $bAutomatic renew is now $a{0} $bfor this {1} + # 0: enabled/disabled; + # 1: type of claim + RenewRentCurrently: $bAutomatic renew is currently $a{0} $bfor this {1} + Errors: + OutOfClaim: $cYou must stand inside of a claim to use this command! + PlayerOnlyCmd: $cOnly Players can perform this command! + NoOngoingTransaction: $cThis claim has no ongoing transactions! + NotRentNorLease: $cThis claim is neither to rent or to lease! + AlreadyBought: $cThis claim already has a buyer! + NotPartOfTransaction: $cYou are not part of this transaction! + RentOnly: $cThis command only applies to rented claims! + AuctionOnly: $cThis command only applies to auctioned claims! + ValueGreaterThanZero: $cThe value must be greater than zero! + InvalidOption: $cInvalid option provided! + ClaimInTransaction: + CantOwner: $cThis claim is currently involved in a transaction, you can't modify + it! + CantEdit: $cThis claim is currently involved in a transaction, you can't edit + it! + CantAccess: $cThis claim is currently involved in a transaction, you can't access + it! + CantBuild: $cThis claim is currently involved in a transaction, you can't build + on it! + CantInventory: $cThis claim is currently involved in a transaction, you can't + access its containers! + CantManage: $cThis claim is currently involved in a transaction, you can't manage + it! + Subclaim: $cA subclaim is currently involved in a transaction, you can't edit + or manage the parent claim! + Command: + # 0: command usage + Usage: '$cUsage: {0}' + BuyerOnly: $cOnly the buyer can perform this command! + Unexpected: $cAn unexpected error has occured! + # 0: number + InvalidNumber: $c{0} is not a valid number! + # 0: number + NegativeNumber: $c{0} is a negative number! + # 0: price + NegativePrice: $cThe price must be greater than zero! + # 0: bid step + NegativeBidStep: $cThe bid step must be greater than zero! + # 0: price + NonIntegerPrice: $cThe price must be an integer! + # 0: duration, 1: + # example of duration format, 2: example, 3: + # example + InvalidDuration: $c{0} is not a valid duration! Durations must be in the format + $a{1}$c or $a{2}$c or $a{3}$c! + NoMoneySelf: $cYou don't have enough money to make this transaction! + # 0: Other player + NoMoneyOther: $c{0} doesn't have enough money to make this transaction! + NoWithdrawSelf: $cCould not withdraw the money! + # 0: Other player + NoWithdrawOther: $cCould not withdraw the money from {0}! + # 0: Other player + NoDepositSelf: $cCould not deposit the money to you, refunding {0}! + # 0: Other player + NoDepositOther: $cCould not deposit the money to {0}, refunding you! + # 0: claim type + CantCancelAlreadyLeased: $cThis {0} is currently being leased, you can't cancel + the transaction! + # 0: claim type + CantCancelAlreadyRented: $cThis {0} is currently being rented, you can't cancel + the transaction! + CantCancelAuction: $cThis claim is currently being auctioned, you can't cancel + the transaction! + # 0: formatted price + CouldntReimburseSelf: $cCould not reimburse you, refunding {0}! + # 0: formatted price + CouldntReimburseOther: $cCould not reimburse {0} to another player, the action + has been cancelled! + ContactAdmin: $cAn unexpected error occured, please contact an admin to resolve + this issue! + AutoRenew: + Disabled: $cAutomatic renew is disabled! + ExitOffer: + AlreadyExists: $cThere is already an exit proposition for this transaction! + NoBuyer: $cNo one is engaged by this transaction yet! + None: $cThere is currently no exit offer for this claim! + CantAcceptSelf: $cYou can't accept your own exit offer! + CantRefuseSelf: $cYou can't refuse your own exit offer! + CantCancelOther: $cOnly the player who created this exit proposition may cancel + it! + Sign: + NotInClaim: $cThe sign you placed is not inside a claim! + OngoingTransaction: $cThis claim already has an ongoing transaction! + ParentOngoingTransaction: $cThis claim's parent already has an ongoing transaction! + SubclaimOngoingTransaction: $cThis claim has subclaims with ongoing transactions! + SellingDisabled: $cSelling is disabled! + LeasingDisabled: $cLeasing is disabled! + RentingDisabled: $cRenting is disabled! + AuctionDisabled: $cAuctioning is disabled! + # 0: claim type + NoSellPermission: $cYou don't have permission to sell this {0}! + # 0: claim type + NoLeasePermission: $cYou don't have permission to lease this {0}! + # 0: claim type + NoRentPermission: $cYou don't have permission to rent this {0}! + # 0: claim type + NoAuctionPermission: $cYou don't have permission to auction this {0}! + # 0: claim type + NoAdminSellPermission: $cYou don't have permission to sell this admin {0}! + # 0: claim type + NoAdminLeasePermission: $cYou don't have permission to lease this admin {0}! + # 0: claim type + NoAdminRentPermission: $cYou don't have permission to rent this admin {0}! + # 0: claim type + NoAdminAuctionPermission: $cYou don't have permission to auction this admin + {0}! + # 0: claim type + NotOwner: $cYou can only sell/rent/lease {0} you own! + NotAuthor: $cOnly the author of the sell/rent/lease sign is allowed to destroy + it! + NotAdmin: $cOnly an admin is allowed to destroy this sign! + NoTransaction: $cThis claim is no longer for rent, sell or lease, sorry... + Claim: + DoesNotExist: $cThis claim does not exist! + DoesNotExistAuction: $cThis auctioned claim does not exist! + # 0: claim type + AlreadyOwner: $cYou are already the owner of this {0}! + # 0: claim type + NotSoldByOwner: $cThis {0} is not sold by its owner! + # 0: claim type + NotLeasedByOwner: $cThis {0} is not leased by its owner! + # 0: claim type + NotRentedByOwner: $cThis {0} is not rented by its owner! + # 0: claim type + NotAuctionedByOwner: $cThis {0} is not auctioned by its owner! + # 0: claim type + NoBuyPermission: $cYou don't have permission to buy this {0}! + # 0: claim type + NoLeasePermission: $cYou don't have permission to lease this {0}! + # 0: claim type + NoRentPermission: $cYou don't have permission to rent this {0}! + # 0: claim type + NoAuctionPermission: $cYou don't have permission to auction this {0}! + # 0: claim type + AlreadyLeased: $cThis {0} is already leased! + # 0: claim type + AlreadyRented: $cThis {0} is already rented! + # 0: claim type + AlreadyHighestBidder: $cYou are already the highest bidder of this {0}! + NoInfoPermission: $cYou don't have permission to view this real estate informations! + # 0: area; 1: + # claim blocks remaining; 2: missing claim blocks + NoClaimBlocks: $cYou don't have enough claim blocks! You need $a{2}$c more claim + blocks to claim this area. The claim requires $a{0}$c claim blocks, you only + have $a{1}$c claim blocks left. + Auction: + CouldntPayOwner: $cCouldn't pay the owner of this auction! The auction is being + cancelled. + CouldntReceiveOwner: $cCouldn't receive the payment of this auction! The auction + is being cancelled. + Info: + ExitOffer: + None: $bThere is currently no exit offer for this claim! + # 0: formatted price + MadeByStatus: $bYou offered to exit the contract for $a{0}$b, but your offer + hasn't been accepted or denied yet... + # 0: player who made the + # offer; 1: formatted price + MadeToStatus: $a{0} $boffered to exit the contract for $a{1} + # 0: cancel command + Cancel: $bTo cancel your offer, use $d{0} + # 0: accept command + Accept: $bTo accept this offer, use $d{0} + # 0: reject command + Reject: $bTo reject this offer, use $d{0} + # 0: formatted price + CreatedBySelf: $bThe offer has been successfully created for $a{0} + # 0: player name, 1: + # claim type, 2: formatted price, 3: + # claim location + CreatedByOther: $a{0} $bhas created an offer to exit the transaction for the + {1} at $a{3} $bfor $a{2} + # 0: claim type, 1:formatted + # price + AcceptedBySelf: $bThe {0} is no longer rented or leased, you have been charged + $a{1} + # 0: player name, 1: + # claim type, 2: formatted price, 3: + # claim location + AcceptedByOther: $a{0} $bhas accepted the offer to exit the transaction for + the {1} at $a{3} $bfor $a{2}. It is no longer leased or rented. + RejectedBySelf: $bThe exit offer has been refused. + # 0: player name, 1: + # claim type, 2: claim location + RejectedByOther: $a{0} $bhas refused the offer to exit the transaction for the + {1} at $a{2} + CancelledBySelf: $bThe exit offer has been cancelled. + # 0: player name, 1: + # claim type, 2: claim location + CancelledByOther: $a{0} $bhas cancelled the offer to exit the transaction for + the {1} at $a{2} + Claim: + # 0: buyer name, 1: + # claim type, 2: formatted price, 3: + # claim location + OwnerSold: $a{0} $bhas bought the {1} at $a{3} $bfor $a{2} + # 0: buyer name, 1: + # claim type, 2: formatted price, 3: + # claim location, 4: payments left + OwnerLeaseStarted: $a{0} $bhas leased the {1} at $a{3} $bfor $a{2} with $a{4} + $bpayments left + # 0: buyer name, 1: + # claim type, 2: formatted price, 3: + # claim location + OwnerRented: $a{0} $bhas rented the {1} at $a{3} $bfor $a{2} + # 0: claim type, 1: + # formatted price + BuyerBought: $bYou have bought the {0} for $a{1} + # 0: claim type, 1: + # formatted price, 2: payments left + BuyerLeaseStarted: $bYou have leased the {0} for $a{1} with $a{2} $bpayments + left + # 0: claim type, 1: + # formatted price + BuyerRented: $bYou have rented the {0} for $a{1} + Info: + Lease: + Header: $9-----= $f[$6RealEstate Lease Info$f]$9 =----- + # 0: claim type, 1: + # payments left, 2: formatted price, 3: + # frequency + GeneralNoBuyer: $bThis {0} is for lease for $a{1} $bpayments of $a{2} each. + Payments are due every $a{3} + # 0: claim type, 1: + # buyer name, 2: formatted price, 3: + # payments left, 4: next payment due, 5: + # frequency + GeneralBuyer: $bThis {0} is currently leased by $a{1}$b for $a{2}$b. There + is $a{3} $bpayments left. Next payment is in $a{4}$b. Payments are due + every $a{5} + # 0: claim area, 1: + # location, 2: payments left, 3: + # period, 4: formatted price + Oneline: $2{0} $bblocks to $2Lease $bat $2{1} $bfor $a{2} periods of $a{3}$b, + each period costs $a{4} + # 0: claim type, 1: + # location, 2: formatted price, 3: + # payments left + PaymentBuyer: $bPaid lease for the {0} at $a{1} $bfor $a{2}$b. There are + $a{3} $bpayments left. + # 0: player name, 1: + # claim type, 2: location, 3: + # formatted price, 4: payments left + PaymentOwner: $a{0} $bpaid lease for the {1} at $a{2} $bfor $a{3}$b. There + are $a{4} $bpayments left. + # 0: claim type, + # 1: location, 2: + # formatted price + PaymentBuyerFinal: $bPaid final lease for the {0} at $a{1} $bfor $a{2}$b. + The {0} is now your property. + # 0: player name, + # 1: claim type, 2: + # location, 3: formatted price + PaymentOwnerFinal: $a{0} $bpaid final lease for the {1} at $a{2} $bfor $a{3}$b. + The {1} is now $a{0}$b's property. + # 0: claim + # type, 1: location, 2: + # formatted price + PaymentBuyerCancelled: $bCouldn't pay the lease for the {0} at $a{1} $bfor + $a{2}$b. The lease has been cancelled. + # 0: player + # name, 1: claim type, 2: + # location, 3: formatted price + PaymentOwnerCancelled: $a{0} $bcouldn't pay the lease for the {1} at $a{2} + $bfor $a{3}$b. The lease has been cancelled. + Rent: + Header: $9-----= $f[$6RealEstate Rent Info$f]$9 =----- + # 0: claim type, 1: + # formatted price, 2: duration + GeneralNoBuyer: $bThis {0} is for rent for $a{1}$b per $a{2}$b. + # 0: claim type, 1: + # buyer name, 2: formatted price, 3: + # time left in current period, 4: duration + # of a period + GeneralBuyer: $bThis {0} is currently rented by $a{1}$b for $a{2}$b. The + {0} is rented for another $a{3}$b. The rent period is $a{4} + # 0: enabled / disabled + AutoRenew: $bAutomatic renew is currently $a{0}$b. + # 0: claim area, 1: + # location, 2: formatted price, 3: + # duration + Oneline: $2{0} $bblocks to $2Rent $bat $2{1} $bfor $a{2}$b per $a{3} + # 0: claim type, 1: + # location, 2: formatted price + PaymentBuyer: $bPaid rent for the {0} at $a{1} $bfor $a{2}$b. + # 0: player name, 1: + # claim type, 2: location, 3: + # formatted price + PaymentOwner: $a{0} $bpaid rent for the {1} at $a{2} $bfor $a{3}$b. + # 0: claim + # type, 1: location, 2: + # formatted price + PaymentBuyerCancelled: $bCouldn't pay the rent for the {0} at $a{1} $bfor + $a{2}$b. The rent has been cancelled. + # 0: player + # name, 1: claim type, 2: + # location, 3: formatted price + PaymentOwnerCancelled: $a{0} $bcouldn't pay the rent for the {1} at $a{2} + $bfor $a{3}$b. The rent has been cancelled. + # 0: claim type, 1: + # location + RentCancelled: $bThe rent for the {0} at $a{1} $bis now over, your access + has been revoked. + Sell: + Header: $9-----= $f[$6RealEstate Sale Info$f]$9 =----- + # 0: claim type, 1: + # formatted price + General: $bThis {0} is for sale for $a{1} + # 0: claim area, 1: + # location, 2: formatted price + Oneline: $2{0} $bblocks to $2Sell $bat $2{1} $bfor $a{2} + Auction: + Header: $9-----= $f[$6RealEstate Auction Info$f]$9 =----- + # 0: claim type, 1: + # formatted price + NoBidder: $bThis {0} is currently being auctioned for $a{1}$b. + # 0: claim type, 1: + # bidder name, 2: formatted price + HighestBidder: $bThis {0} is currently being auctioned. The highest bidder + is $a{1}$b for $a{2}$b. + # 0: time remaining + TimeRemaining: $bThe auction will end in $a{0}$b. + # 0: Bid Step + BidStep: $bThe bid step is $a{0}$b. + # 0: claim area, 1: + # location, 2: formatted price, 3: + # time remaining, 4: bid step + Oneline: $2{0} $bblocks to $2Auction $bat $2{1}$b. Current highest bid is + $a{2}$b. The auction will end in $a{3}$b. The bid step is $a{4} + # 0: claim type + Ended: $bThe auction for the {0} has ended. + # 0: claim type + Cancelled: $bThe auction for the {0} has been cancelled. You have been reimbursed. + # 0: owner name + Owner: $bThe current owner is $a{0} + # 0: owner name + MainOwner: $bThe main claim's owner is $a{0} + Note: '$dNote: You will only get access to this subclaim.' + Created: + # 0: claim prefix, 1: + # claim type, 2: formatted price + Sell: $bYou have successfully created {0} {1} sale for $a{2} + # 0: claim prefix, 1: + # claim type, 2: formatted price, 3: + # payments count, 4: frequency + Lease: $bYou have successfully created {0} {1} lease for $a{3}$b payments + of $a{2}$b each. Payments are due every $a{4} + # 0: claim prefix, 1: + # claim type, 2: formatted price, 3: + # duration + Rent: $bYou have successfully created {0} {1} rent for $a{2}$b per $a{3} + # 0: claim prefix, 1: + # claim type, 2: formatted price, 3: + # formatted bid step, 4: time remaining + Auction: $bYou have successfully created {0} {1} auction for $a{2}$b. The + bid step is $a{3}$b. The auction will end in $a{4} + # 0: player name, 1: + # claim prefix, 2: claim type, 3: + # formatted price + SellBroadcast: $a{0} $bhas created {1} {2} sale for $a{3} + # 0: player name, 1: + # claim prefix, 2: claim type, 3: + # formatted price, 4: payments count, 5: + # frequency + LeaseBroadcast: $a{0} $bhas created {1} {2} lease for $a{4}$b payments of + $a{3}$b each. Payments are due every $a{5} + # 0: player name, 1: + # claim prefix, 2: claim type, 3: + # formatted price, 4: duration + RentBroadcast: $a{0} $bhas created {1} {2} rent for $a{3}$b per $a{4} + # 0: player name, + # 1: claim prefix, 2: + # claim type, 3: formatted price, 4: + # formatted bid step, 5: time remaining + AuctionBroadcast: $a{0} $bhas created {1} {2} auction for $a{3}$b. The bid + step is $a{4}$b. The auction will end in $a{5} + List: + # 0: RE Offers|Sell Offers|Rent + # Offers|Lease Offers; 1: Page number; 2: + # Page count + Header: $1----= $f[ $6{0} page $2 {1} $6/ $2{2} $f] $1=---- + # 0: all|sell|rent|lease; 1: + # next page number + NextPage: $6To see the next page, type $a/re list {0} {1} + Sign: + Auction: + # 0: player name, 1: + # formatted price + HighestBidder: '$b{0}: $a{1}' + NoBider: $bNo bidder + # 0: formatted time + RemainingTime: $b$a{0} + Ended: $bAuction ended + # next line: winner + Won: $bAuction won by From 6b8f54b62d5189d3c7e85155144fa3eacf5e1d52 Mon Sep 17 00:00:00 2001 From: Michael Burgess Date: Sun, 16 Feb 2025 17:12:05 -0500 Subject: [PATCH 08/14] Moved Language Files Moved Language resource files to better manage community translations. --- resources/{languages => dist/es}/es.yml | 0 resources/{languages => dist/pt-br}/pt-br.yml | 0 resources/{languages => dist/ru}/ru.yml | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename resources/{languages => dist/es}/es.yml (100%) rename resources/{languages => dist/pt-br}/pt-br.yml (100%) rename resources/{languages => dist/ru}/ru.yml (100%) diff --git a/resources/languages/es.yml b/resources/dist/es/es.yml similarity index 100% rename from resources/languages/es.yml rename to resources/dist/es/es.yml diff --git a/resources/languages/pt-br.yml b/resources/dist/pt-br/pt-br.yml similarity index 100% rename from resources/languages/pt-br.yml rename to resources/dist/pt-br/pt-br.yml diff --git a/resources/languages/ru.yml b/resources/dist/ru/ru.yml similarity index 100% rename from resources/languages/ru.yml rename to resources/dist/ru/ru.yml From b42c94d157d41ac9d0d910efd567ce8e1efbf134 Mon Sep 17 00:00:00 2001 From: Michael Burgess Date: Sun, 16 Feb 2025 18:04:46 -0500 Subject: [PATCH 09/14] Fixes GitLocalize --- resources/dist/es/{es.yml => en-us.yml} | 0 resources/dist/pt-br/{pt-br.yml => en-us.yml} | 0 resources/dist/ru/{ru.yml => en-us.yml} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename resources/dist/es/{es.yml => en-us.yml} (100%) rename resources/dist/pt-br/{pt-br.yml => en-us.yml} (100%) rename resources/dist/ru/{ru.yml => en-us.yml} (100%) diff --git a/resources/dist/es/es.yml b/resources/dist/es/en-us.yml similarity index 100% rename from resources/dist/es/es.yml rename to resources/dist/es/en-us.yml diff --git a/resources/dist/pt-br/pt-br.yml b/resources/dist/pt-br/en-us.yml similarity index 100% rename from resources/dist/pt-br/pt-br.yml rename to resources/dist/pt-br/en-us.yml diff --git a/resources/dist/ru/ru.yml b/resources/dist/ru/en-us.yml similarity index 100% rename from resources/dist/ru/ru.yml rename to resources/dist/ru/en-us.yml From 565c7ee845331b22e441f7bb00f6f439ee66c254 Mon Sep 17 00:00:00 2001 From: Michael Burgess Date: Sun, 16 Feb 2025 19:00:52 -0500 Subject: [PATCH 10/14] Update README.md Per request --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4e83aba..4aed4f0 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,19 @@ # RealEstate -RealEstate is a spigot plugin built on top of GriefPrevention to provide players with an ability to sell and rent claims to other players. +RealEstate is a comprehensive Minecraft plugin that empowers players and administrators to sell, rent, lease, and auction land claims managed by popular protection plugins such as GriefPrevention, GriefDefender, WorldGuard, and Towny. Integrated with Vault for economy and permissions, RealEstate offers intuitive sign-based interactions, detailed transaction logging, and seamless compatibility with multiple claim management systems, making real estate management in Minecraft both engaging and efficient. The plugin is documented [here](https://www.michael-burgess.xyz/minecraft-plugins/realestate/). +[Javadoc](https://www.michael-burgess.xyz/minecraft-plugins/realestate/javadoc). +Original Wiki can be found [here](https://github.com/EtienneDx/RealEstate/wiki). + +Please feel free to report any issue in the [GitHub Issue section](https://github.com/EtienneDx/RealEstate/issues). -Please feel free to report any issue in the [GitHub Issue section](https://github.com/msburgess3200/RealEstate/issues). ## GriefPrevention This plugin is dependent on GriefPrevention version 16.18 and up. -GriefPrevention plugin can be found [here](https://github.com/GriefPrevention/GriefPrevention). \ No newline at end of file +GriefPrevention plugin can be found [here](https://github.com/GriefPrevention/GriefPrevention). +GriefDefender plugin can be found [here](https://www.spigotmc.org/resources/1-12-2-1-21-4-griefdefender-claim-plugin-grief-prevention-protection.68900/). +WorldGuard plugin can be found [here](https://dev.bukkit.org/projects/worldguard). +Towny plugin can be found [here](https://www.spigotmc.org/resources/towny-advanced.72694/). \ No newline at end of file From 648304c94d76445ef2abdc33dbf6f1a6b675e933 Mon Sep 17 00:00:00 2001 From: Michael Burgess Date: Mon, 17 Feb 2025 07:52:34 -0500 Subject: [PATCH 11/14] Update README.md --- README.md | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4aed4f0..0b16962 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,40 @@ +![RealEstate logo](https://cdn.michael-burgess.xyz/minecraft/plugins/realestate/logo.png) # RealEstate RealEstate is a comprehensive Minecraft plugin that empowers players and administrators to sell, rent, lease, and auction land claims managed by popular protection plugins such as GriefPrevention, GriefDefender, WorldGuard, and Towny. Integrated with Vault for economy and permissions, RealEstate offers intuitive sign-based interactions, detailed transaction logging, and seamless compatibility with multiple claim management systems, making real estate management in Minecraft both engaging and efficient. The plugin is documented [here](https://www.michael-burgess.xyz/minecraft-plugins/realestate/). -[Javadoc](https://www.michael-burgess.xyz/minecraft-plugins/realestate/javadoc). + +Javadoc can be found [here](https://www.michael-burgess.xyz/minecraft-plugins/realestate/javadoc). + Original Wiki can be found [here](https://github.com/EtienneDx/RealEstate/wiki). Please feel free to report any issue in the [GitHub Issue section](https://github.com/EtienneDx/RealEstate/issues). -## GriefPrevention - -This plugin is dependent on GriefPrevention version 16.18 and up. +## Real Estate Dependencies: +You MUST include at least ONE of the following plugins: GriefPrevention plugin can be found [here](https://github.com/GriefPrevention/GriefPrevention). + GriefDefender plugin can be found [here](https://www.spigotmc.org/resources/1-12-2-1-21-4-griefdefender-claim-plugin-grief-prevention-protection.68900/). + WorldGuard plugin can be found [here](https://dev.bukkit.org/projects/worldguard). -Towny plugin can be found [here](https://www.spigotmc.org/resources/towny-advanced.72694/). \ No newline at end of file + +Towny plugin can be found [here](https://www.spigotmc.org/resources/towny-advanced.72694/). + +## Translation +We are looking to increase our language locale. If you speak multiple languages, you can help contribute to our project. + + +Many of the strings can be translated automatically, but we do need help with the more complex strings, and to confirm that the automatic translations are correct. + +[![gitlocalized ](https://gitlocalize.com/repo/10023/whole_project/badge.svg)](https://gitlocalize.com/repo/10023?utm_source=badge) +| Language | Locale | GitLocalize Status | Merged into RealEstate | Contributors | +| ------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------ | ---------------------- | --------------------------------------------------------- | +| [French](https://gitlocalize.com/repo/10023/fr/resources/languages/en-us.yml) | France | [![gitlocalized](https://gitlocalize.com/repo/10023/fr/badge.svg)](https://gitlocalize.com/repo/10023/fr/resources/languages/en-us.yml?utm_source=badge) | Not yet | Looking for a Volunteer | +| [Chinese](https://gitlocalize.com/repo/10023/zh-CN/resources/languages/en-us.yml) | China | [![gitlocalized](https://gitlocalize.com/repo/10023/zh-CN/badge.svg)](https://gitlocalize.com/repo/10023/zh-CN/resources/languages/en-us.yml?utm_source=badge) | Not yet | Looking for a Volunteer | +| [Russian](https://gitlocalize.com/repo/10023/ru/resources/languages/en-us.yml) | Russia | [![gitlocalized](https://gitlocalize.com/repo/10023/ru/badge.svg)](https://gitlocalize.com/repo/10023/ru/resources/languages/en-us.yml?utm_source=badge) | Not yet | Looking for a Volunteer | +| [Dutch](https://gitlocalize.com/repo/10023/nl-NL/resources/languages/en-us.yml) | Dutch | [![gitlocalized](https://gitlocalize.com/repo/10023/nl-NL/badge.svg)](https://gitlocalize.com/repo/10023/nl-NL/resources/languages/en-us.yml?utm_source=badge) | Not yet | Looking for a Volunteer | +| [Spanish](https://gitlocalize.com/repo/10023/es-ES/resources/languages/en-us.yml) | Spanish | [![gitlocalized](https://gitlocalize.com/repo/10023/es-ES/badge.svg)](https://gitlocalize.com/repo/10023/es-ES/resources/languages/en-us.yml?utm_source=badge) | Not yet | Looking for a Volunteer | +| [Portuguese](https://gitlocalize.com/repo/10023/pt-br/resources/languages/en-us.yml) | Portuguese | [![gitlocalized](https://gitlocalize.com/repo/10023/pt-br/badge.svg)](https://gitlocalize.com/repo/10023/pt-br/resources/languages/en-us.yml?utm_source=badge) | Not yet | Looking for a Volunteer | From b2425ffecaa2935ea7e3f81e3a53597706406049 Mon Sep 17 00:00:00 2001 From: Michael Burgess Date: Mon, 17 Feb 2025 08:21:51 -0500 Subject: [PATCH 12/14] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0b16962..015ca94 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,6 @@ Many of the strings can be translated automatically, but we do need help with th | [French](https://gitlocalize.com/repo/10023/fr/resources/languages/en-us.yml) | France | [![gitlocalized](https://gitlocalize.com/repo/10023/fr/badge.svg)](https://gitlocalize.com/repo/10023/fr/resources/languages/en-us.yml?utm_source=badge) | Not yet | Looking for a Volunteer | | [Chinese](https://gitlocalize.com/repo/10023/zh-CN/resources/languages/en-us.yml) | China | [![gitlocalized](https://gitlocalize.com/repo/10023/zh-CN/badge.svg)](https://gitlocalize.com/repo/10023/zh-CN/resources/languages/en-us.yml?utm_source=badge) | Not yet | Looking for a Volunteer | | [Russian](https://gitlocalize.com/repo/10023/ru/resources/languages/en-us.yml) | Russia | [![gitlocalized](https://gitlocalize.com/repo/10023/ru/badge.svg)](https://gitlocalize.com/repo/10023/ru/resources/languages/en-us.yml?utm_source=badge) | Not yet | Looking for a Volunteer | -| [Dutch](https://gitlocalize.com/repo/10023/nl-NL/resources/languages/en-us.yml) | Dutch | [![gitlocalized](https://gitlocalize.com/repo/10023/nl-NL/badge.svg)](https://gitlocalize.com/repo/10023/nl-NL/resources/languages/en-us.yml?utm_source=badge) | Not yet | Looking for a Volunteer | +| [Dutch](https://gitlocalize.com/repo/10023/nl-NL/resources/languages/en-us.yml) | Dutch | [![gitlocalized](https://gitlocalize.com/repo/10023/nl-NL/badge.svg)](https://gitlocalize.com/repo/10023/nl-NL/resources/languages/en-us.yml?utm_source=badge) | Not yet | [smarcelissen](https://github.com/smarcelissen) | | [Spanish](https://gitlocalize.com/repo/10023/es-ES/resources/languages/en-us.yml) | Spanish | [![gitlocalized](https://gitlocalize.com/repo/10023/es-ES/badge.svg)](https://gitlocalize.com/repo/10023/es-ES/resources/languages/en-us.yml?utm_source=badge) | Not yet | Looking for a Volunteer | | [Portuguese](https://gitlocalize.com/repo/10023/pt-br/resources/languages/en-us.yml) | Portuguese | [![gitlocalized](https://gitlocalize.com/repo/10023/pt-br/badge.svg)](https://gitlocalize.com/repo/10023/pt-br/resources/languages/en-us.yml?utm_source=badge) | Not yet | Looking for a Volunteer | From 66eca291a62f794b2aabd5df0afbe4b57aa25ed8 Mon Sep 17 00:00:00 2001 From: Michael Burgess Date: Mon, 17 Feb 2025 08:46:54 -0500 Subject: [PATCH 13/14] 1.4.4 Pre-Release Introduces Javadoc, WorldGuard, and Towny --- .gitignore | 1 + plugin.yml | 1 + pom.xml | 71 +- .../RealEstate/ClaimAPI/ClaimPermission.java | 29 +- .../ClaimAPI/GriefDefender/GDClaim.java | 146 +- .../GriefDefender/GDPermissionListener.java | 59 +- .../ClaimAPI/GriefDefender/GDPlayerData.java | 39 +- .../GriefDefender/GriefDefenderAPI.java | 56 +- .../ClaimPermissionListener.java | 52 +- .../ClaimAPI/GriefPrevention/GPClaim.java | 133 +- .../GriefPrevention/GPPlayerData.java | 42 +- .../GriefPrevention/GriefPreventionAPI.java | 30 +- .../EtienneDx/RealEstate/ClaimAPI/IClaim.java | 130 ++ .../RealEstate/ClaimAPI/IClaimAPI.java | 44 + .../RealEstate/ClaimAPI/IPlayerData.java | 33 + .../ClaimAPI/Towny/TownyAPIWrapper.java | 109 ++ .../RealEstate/ClaimAPI/Towny/TownyClaim.java | 289 ++++ .../ClaimAPI/Towny/TownyPlayerData.java | 89 ++ .../ClaimAPI/WorldGuard/WGClaim.java | 197 ++- .../ClaimAPI/WorldGuard/WGPlayerData.java | 59 +- .../ClaimAPI/WorldGuard/WorldGuardAPI.java | 99 +- src/me/EtienneDx/RealEstate/ClaimEvents.java | 58 +- src/me/EtienneDx/RealEstate/Config.java | 240 ++++ src/me/EtienneDx/RealEstate/Messages.java | 1235 ++++++++++++++--- src/me/EtienneDx/RealEstate/RECommand.java | 872 +++++++----- src/me/EtienneDx/RealEstate/REListener.java | 1005 +++++++------- src/me/EtienneDx/RealEstate/RealEstate.java | 704 ++++++---- .../EtienneDx/RealEstate/RealEstateSign.java | 74 +- .../Transactions/BoughtTransaction.java | 52 + .../RealEstate/Transactions/ClaimAuction.java | 342 +++-- .../RealEstate/Transactions/ClaimLease.java | 918 ++++++------ .../RealEstate/Transactions/ClaimRent.java | 860 ++++++------ .../RealEstate/Transactions/ClaimSell.java | 400 +++--- .../Transactions/ClaimTransaction.java | 100 +- .../RealEstate/Transactions/ExitOffer.java | 31 + .../RealEstate/Transactions/Transaction.java | 82 ++ .../Transactions/TransactionsStore.java | 108 +- src/me/EtienneDx/RealEstate/Utils.java | 331 +++-- 38 files changed, 6218 insertions(+), 2902 deletions(-) create mode 100644 src/me/EtienneDx/RealEstate/ClaimAPI/Towny/TownyAPIWrapper.java create mode 100644 src/me/EtienneDx/RealEstate/ClaimAPI/Towny/TownyClaim.java create mode 100644 src/me/EtienneDx/RealEstate/ClaimAPI/Towny/TownyPlayerData.java diff --git a/.gitignore b/.gitignore index 3da09c9..974fe80 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ build.bat .vscode report.cmd report.txt +javadoc.cmd diff --git a/plugin.yml b/plugin.yml index bea5ac1..732512a 100644 --- a/plugin.yml +++ b/plugin.yml @@ -8,6 +8,7 @@ authors: - SkyWalker3200 - Tadhunt load: POSTWORLD +folia-supported: false # prevent Folia from loading this plugin. It's not ready yet. depend: - Vault softdepend: diff --git a/pom.xml b/pom.xml index 2f85ab1..f14ed2c 100644 --- a/pom.xml +++ b/pom.xml @@ -35,40 +35,21 @@ 16 - + + org.apache.maven.plugins + maven-javadoc-plugin + 3.3.2 + + + ${settings.localRepository}/net/kyori/adventure-api/4.18.0/adventure-api-4.18.0.jar + ${settings.localRepository}/net/kyori/adventure-text-serializer-plain/4.18.0/adventure-text-serializer-plain-4.18.0.jar + + + + -Xdoclint:none + + + maven-assembly-plugin 2.4.1 @@ -137,6 +118,10 @@ glaremasters repo https://repo.glaremasters.me/repository/towny/ + + adventure + https://repo1.maven.org/maven2/ + @@ -175,9 +160,9 @@ provided - com.griefdefender - api - 2.1.0-20220122.032038-5 + com.github.bloodmc + GriefDefenderAPI + master-4e5f379629-1 provided @@ -203,6 +188,18 @@ 7.2.0 provided + + net.kyori + adventure-api + 4.18.0 + provided + + + net.kyori + adventure-text-serializer-plain + 4.18.0 + provided + com.palmergames.bukkit.towny towny diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/ClaimPermission.java b/src/me/EtienneDx/RealEstate/ClaimAPI/ClaimPermission.java index b575028..2828b10 100644 --- a/src/me/EtienneDx/RealEstate/ClaimAPI/ClaimPermission.java +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/ClaimPermission.java @@ -1,10 +1,35 @@ package me.EtienneDx.RealEstate.ClaimAPI; -public enum ClaimPermission -{ +/** + * An enumeration of the various permission types that can be applied to a claim. + *

+ * These permissions define what actions a player is allowed to perform on a claim, + * such as building, accessing containers, managing, or editing the claim. + *

+ */ +public enum ClaimPermission { + /** + * Allows a player to build on the claim. + */ BUILD, + + /** + * Allows a player to access containers (e.g., chests) within the claim. + */ CONTAINER, + + /** + * Allows a player to manage the claim. + */ MANAGE, + + /** + * Allows a player to edit the claim. + */ EDIT, + + /** + * Allows a player to access the claim. + */ ACCESS } diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GDClaim.java b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GDClaim.java index 4e2fcbe..d23628c 100644 --- a/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GDClaim.java +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GDClaim.java @@ -13,53 +13,116 @@ import me.EtienneDx.RealEstate.ClaimAPI.ClaimPermission; import me.EtienneDx.RealEstate.ClaimAPI.IClaim; -public class GDClaim implements IClaim{ - +/** + * Implementation of the {@link IClaim} interface using the GriefDefender API. + *

+ * This class wraps a GriefDefender {@link Claim} and provides access to claim properties + * such as area, coordinates, owner, and permissions management. + *

+ */ +public class GDClaim implements IClaim { + + /** + * The underlying GriefDefender claim. + */ private Claim claim; + /** + * Constructs a new GDClaim that wraps the given GriefDefender claim. + * + * @param claim the GriefDefender claim to wrap + */ public GDClaim(Claim claim) { this.claim = claim; } + /** + * Returns the underlying GriefDefender claim. + * + * @return the wrapped GriefDefender {@link Claim} + */ public Claim getClaim() { return claim; } + /** + * Gets the unique identifier for this claim. + * + * @return the claim's unique ID as a String + */ @Override public String getId() { return claim.getUniqueId().toString(); } + /** + * Calculates the area of the claim. + * + * @return the area of the claim in blocks + */ @Override public int getArea() { return claim.getArea(); } + /** + * Retrieves the Bukkit {@link World} where the claim is located. + * + * @return the Bukkit World corresponding to the claim's world + */ @Override public World getWorld() { return Bukkit.getWorld(claim.getWorldUniqueId()); } + /** + * Gets the X coordinate of the claim's lesser boundary corner. + * + * @return the X coordinate + */ @Override public int getX() { return claim.getLesserBoundaryCorner().getX(); } + /** + * Gets the Y coordinate of the claim's lesser boundary corner. + * + * @return the Y coordinate + */ @Override public int getY() { return claim.getLesserBoundaryCorner().getY(); } + /** + * Gets the Z coordinate of the claim's lesser boundary corner. + * + * @return the Z coordinate + */ @Override public int getZ() { return claim.getLesserBoundaryCorner().getZ(); } + /** + * Determines if this claim is an admin claim. + * + * @return {@code true} if this claim is an admin claim, {@code false} otherwise + */ @Override public boolean isAdminClaim() { return claim.isAdminClaim(); } + /** + * Returns an iterable over the child claims. + *

+ * In GriefDefender, child claims are obtained via the {@code getChildren(true)} method. + *

+ * + * @return an {@link Iterable} of {@link IClaim} representing child claims + */ @Override public Iterable getChildren() { return new Iterable() { @@ -87,31 +150,62 @@ public void remove() { }; } + /** + * Checks if this claim represents wilderness. + * + * @return {@code true} if this claim is wilderness, {@code false} otherwise + */ @Override public boolean isWilderness() { return claim.isWilderness(); } + /** + * Determines if this claim is a subclaim. + * + * @return {@code true} if this claim has a non-wilderness parent; {@code false} otherwise + */ @Override public boolean isSubClaim() { return claim.getParent() != null && !claim.getParent().isWilderness(); } + /** + * Determines if this claim is a parent claim. + * + * @return {@code true} if this claim has no parent or its parent is wilderness + */ @Override public boolean isParentClaim() { return claim.getParent() == null || claim.getParent().isWilderness(); } + /** + * Retrieves the parent claim of this claim, if any. + * + * @return the parent {@link IClaim} or {@code null} if this is a parent claim + */ @Override public IClaim getParent() { return isParentClaim() ? null : new GDClaim(claim.getParent()); } + /** + * Drops all player permissions for the specified player in this claim. + * + * @param player the UUID of the player whose permissions should be dropped + */ @Override public void dropPlayerPermissions(UUID player) { claim.removeUserTrust(player, TrustTypes.NONE); } + /** + * Adds a specific permission for a player to this claim. + * + * @param player the UUID of the player to grant permission to + * @param permission the {@link ClaimPermission} to grant + */ @Override public void addPlayerPermissions(UUID player, ClaimPermission permission) { TrustType trust = TrustTypes.NONE; @@ -132,43 +226,81 @@ public void addPlayerPermissions(UUID player, ClaimPermission permission) { trust = TrustTypes.MANAGER; break; } - claim.addUserTrust(player, trust); } + /** + * Clears all player permissions from this claim. + */ @Override public void clearPlayerPermissions() { claim.removeAllUserTrusts(); } + /** + * Removes a manager from this claim. + *

+ * Not supported in GriefDefender. + *

+ * + * @param player the UUID of the manager to remove + */ @Override public void removeManager(UUID player) { - // No equivalent in GD + // No equivalent in GD. } + /** + * Adds a manager to this claim. + *

+ * Not supported in GriefDefender. + *

+ * + * @param player the UUID of the manager to add + */ @Override public void addManager(UUID player) { - // No equivalent in GD + // No equivalent in GD. } + /** + * Clears all managers from this claim. + *

+ * Not supported in GriefDefender. + *

+ */ @Override public void clearManagers() { - // No equivalent in GD + // No equivalent in GD. } + /** + * Retrieves the owner of the claim. + * + * @return the UUID of the claim's owner + */ @Override public UUID getOwner() { return claim.getOwnerUniqueId(); } + /** + * Retrieves the name of the claim's owner. + * + * @return the owner's name as provided by GriefDefender + */ @Override public String getOwnerName() { return claim.getOwnerName(); } + /** + * Sets whether the claim should inherit permissions from its parent claim. + * + * @param inherit {@code true} to inherit permissions, {@code false} otherwise + */ @Override public void setInheritPermissions(boolean inherit) { claim.getData().setInheritParent(inherit); } - } diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GDPermissionListener.java b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GDPermissionListener.java index 082be96..7cfffea 100644 --- a/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GDPermissionListener.java +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GDPermissionListener.java @@ -10,25 +10,42 @@ import com.griefdefender.lib.kyori.adventure.text.Component; import com.griefdefender.lib.kyori.event.EventBus; import com.griefdefender.lib.kyori.event.EventSubscriber; - import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.checkerframework.checker.nullness.qual.NonNull; - import me.EtienneDx.RealEstate.ClaimEvents; import me.EtienneDx.RealEstate.ClaimAPI.ClaimPermission; -public class GDPermissionListener -{ - +/** + * Listens for GriefDefender claim events and delegates them to the RealEstate claim event logic. + *

+ * This listener handles trust events, claim changes, and claim deletions to enforce RealEstate's + * restrictions on claim modifications. + *

+ */ +public class GDPermissionListener { + + /** + * Constructs a new GDPermissionListener and registers its event subscribers. + */ public GDPermissionListener() { new ProcessTrustUserEventListener(); new ChangeClaimEventListener(); new RemoveClaimEventListener(); } + /** + * Handles ProcessTrustUserEvent events from GriefDefender. + *

+ * This inner class subscribes to trust events and checks if a player's trust action is permitted. + * It uses RealEstate's ClaimEvents logic to determine if the action should be denied. + *

+ */ private class ProcessTrustUserEventListener { + /** + * Constructs and subscribes to the ProcessTrustUserEvent. + */ public ProcessTrustUserEventListener() { final EventBus eventBus = GriefDefender.getEventManager().getBus(); @@ -45,16 +62,13 @@ public void on(@NonNull ProcessTrustUserEvent event) throws Throwable { return; } ClaimPermission permission = null; - if(event.getTrustType().equals(TrustTypes.ACCESSOR)) { + if (event.getTrustType().equals(TrustTypes.ACCESSOR)) { permission = ClaimPermission.ACCESS; - } - else if(event.getTrustType().equals(TrustTypes.BUILDER)) { + } else if (event.getTrustType().equals(TrustTypes.BUILDER)) { permission = ClaimPermission.BUILD; - } - else if(event.getTrustType().equals(TrustTypes.CONTAINER)) { + } else if (event.getTrustType().equals(TrustTypes.CONTAINER)) { permission = ClaimPermission.CONTAINER; - } - else if(event.getTrustType().equals(TrustTypes.MANAGER)) { + } else if (event.getTrustType().equals(TrustTypes.MANAGER)) { permission = ClaimPermission.MANAGE; } String denialReason = ClaimEvents.onClaimPermission( @@ -71,8 +85,18 @@ else if(event.getTrustType().equals(TrustTypes.MANAGER)) { } } + /** + * Handles ChangeClaimEvent events from GriefDefender. + *

+ * This inner class subscribes to claim change events and verifies if a claim change is allowed. + * It uses RealEstate's claim event logic for validation. + *

+ */ private class ChangeClaimEventListener { + /** + * Constructs and subscribes to the ChangeClaimEvent. + */ public ChangeClaimEventListener() { final EventBus eventBus = GriefDefender.getEventManager().getBus(); @@ -102,8 +126,17 @@ public void on(@NonNull ChangeClaimEvent event) throws Throwable { } } + /** + * Handles RemoveClaimEvent events from GriefDefender. + *

+ * This inner class subscribes to claim deletion events and ensures that any ongoing transactions are cancelled. + *

+ */ private class RemoveClaimEventListener { + /** + * Constructs and subscribes to the RemoveClaimEvent. + */ public RemoveClaimEventListener() { final EventBus eventBus = GriefDefender.getEventManager().getBus(); @@ -132,4 +165,4 @@ public void on(@NonNull RemoveClaimEvent event) throws Throwable { }); } } -} \ No newline at end of file +} diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GDPlayerData.java b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GDPlayerData.java index acb78fd..444f5b8 100644 --- a/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GDPlayerData.java +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GDPlayerData.java @@ -1,40 +1,75 @@ package me.EtienneDx.RealEstate.ClaimAPI.GriefDefender; import com.griefdefender.api.data.PlayerData; - import me.EtienneDx.RealEstate.ClaimAPI.IPlayerData; +/** + * GDPlayerData is an implementation of the {@link IPlayerData} interface for GriefDefender. + * It wraps a GriefDefender {@link PlayerData} instance to provide player claim block information. + */ public class GDPlayerData implements IPlayerData { + /** + * The underlying GriefDefender PlayerData instance. + */ private PlayerData playerData; + /** + * Constructs a new GDPlayerData instance using the specified PlayerData. + * + * @param playerData the GriefDefender PlayerData instance to wrap + */ public GDPlayerData(PlayerData playerData) { this.playerData = playerData; } + /** + * Retrieves the number of accrued claim blocks for the player. + * + * @return the number of accrued claim blocks + */ @Override public int getAccruedClaimBlocks() { return playerData.getAccruedClaimBlocks(); } + /** + * Retrieves the number of bonus claim blocks for the player. + * + * @return the number of bonus claim blocks + */ @Override public int getBonusClaimBlocks() { return playerData.getBonusClaimBlocks(); } + /** + * Sets the number of accrued claim blocks for the player. + * + * @param accruedClaimBlocks the new number of accrued claim blocks + */ @Override public void setAccruedClaimBlocks(int accruedClaimBlocks) { playerData.setAccruedClaimBlocks(accruedClaimBlocks); } + /** + * Sets the number of bonus claim blocks for the player. + * + * @param bonusClaimBlocks the new number of bonus claim blocks + */ @Override public void setBonusClaimBlocks(int bonusClaimBlocks) { playerData.setBonusClaimBlocks(bonusClaimBlocks); } + /** + * Retrieves the number of remaining claim blocks available to the player. + * + * @return the number of remaining claim blocks + */ @Override public int getRemainingClaimBlocks() { return playerData.getRemainingClaimBlocks(); } - } diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GriefDefenderAPI.java b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GriefDefenderAPI.java index c2cdede..09d276f 100644 --- a/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GriefDefenderAPI.java +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefDefender/GriefDefenderAPI.java @@ -12,36 +12,82 @@ import me.EtienneDx.RealEstate.ClaimAPI.IClaimAPI; import me.EtienneDx.RealEstate.ClaimAPI.IPlayerData; -public class GriefDefenderAPI implements IClaimAPI{ +/** + * GriefDefenderAPI is an implementation of the {@link IClaimAPI} interface that integrates with the GriefDefender plugin. + *

+ * It provides methods to retrieve claims, save claims, access player data, change claim ownership, and register + * event listeners for claim-related events. + *

+ */ +public class GriefDefenderAPI implements IClaimAPI { + /** + * Default constructor for GriefDefenderAPI. + */ + public GriefDefenderAPI() { + // Default constructor. + } + + /** + * Retrieves the claim at the specified Bukkit location. + * + * @param location the Bukkit location where the claim is being checked + * @return an IClaim instance representing the claim at the given location + */ @Override public IClaim getClaimAt(Location location) { return new GDClaim(GriefDefender.getCore().getClaimAt(location)); } + /** + * Saves the specified claim. + *

+ * Note: GriefDefender automatically saves claims, so no additional action is required. + *

+ * + * @param claim the claim to save + */ @Override public void saveClaim(IClaim claim) { - // GD auto saves + // GriefDefender auto-saves claims; no additional implementation needed. } + /** + * Retrieves the player data for the specified player UUID. + * + * @param player the UUID of the player whose claim data is to be retrieved + * @return an IPlayerData instance representing the player's claim data + */ @Override public IPlayerData getPlayerData(UUID player) { return new GDPlayerData(GriefDefender.getCore().getPlayerData(Bukkit.getPlayer(player).getWorld().getUID(), player)); } + /** + * Changes the owner of the specified claim to a new owner. + * + * @param claim the claim whose ownership is to be changed + * @param newOwner the UUID of the new owner + * @throws RuntimeException if the transfer of ownership is unsuccessful + */ @Override public void changeClaimOwner(IClaim claim, UUID newOwner) { - if(claim instanceof GDClaim) { + if (claim instanceof GDClaim) { ClaimResult res = ((GDClaim) claim).getClaim().transferOwner(newOwner); - if(!res.successful()) { + if (!res.successful()) { throw new RuntimeException(res.getResultType().toString()); } } } + /** + * Registers event listeners for GriefDefender integration. + *

+ * This method initializes the GriefDefender permission listener. + *

+ */ @Override public void registerEvents() { new GDPermissionListener(); } - } diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/ClaimPermissionListener.java b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/ClaimPermissionListener.java index 98b4575..f373998 100644 --- a/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/ClaimPermissionListener.java +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/ClaimPermissionListener.java @@ -9,14 +9,43 @@ import me.ryanhamshire.GriefPrevention.events.ClaimDeletedEvent; import me.ryanhamshire.GriefPrevention.events.ClaimPermissionCheckEvent; +/** + * Listens for GriefPrevention claim events and delegates them to the RealEstate claim event logic. + *

+ * This listener handles claim permission checks and claim deletions to enforce RealEstate's rules. + *

+ */ public class ClaimPermissionListener implements Listener { - void registerEvents() - { - PluginManager pm = RealEstate.instance.getServer().getPluginManager(); - pm.registerEvents(this, RealEstate.instance); - } + /** + * Default constructor for ClaimPermissionListener. + *

+ * This constructor is provided for documentation purposes. Use {@link #registerEvents()} to register this listener. + *

+ */ + public ClaimPermissionListener() { + // No initialization required. + } + + /** + * Registers this listener with the Bukkit PluginManager. + * Call this method during plugin initialization to ensure that events are captured. + */ + public void registerEvents() { + PluginManager pm = RealEstate.instance.getServer().getPluginManager(); + pm.registerEvents(this, RealEstate.instance); + } + /** + * Handles the ClaimPermissionCheckEvent triggered by GriefPrevention. + *

+ * Maps GriefPrevention's required permission to a corresponding RealEstate {@link me.EtienneDx.RealEstate.ClaimAPI.ClaimPermission} + * and calls {@link ClaimEvents#onClaimPermission} to determine if the action should be denied. + * If a denial reason is provided, it sets the denial reason on the event. + *

+ * + * @param event the claim permission check event from GriefPrevention + */ @EventHandler public void onClaimPermission(ClaimPermissionCheckEvent event) { me.EtienneDx.RealEstate.ClaimAPI.ClaimPermission permission = null; @@ -43,13 +72,20 @@ public void onClaimPermission(ClaimPermissionCheckEvent event) { event.getCheckedPlayer(), permission ); - if(denialReason != null) - { + if (denialReason != null) { event.setDenialReason(() -> denialReason); } } - // more of a safety measure, normally it shouldn't be needed + /** + * Handles the ClaimDeletedEvent triggered by GriefPrevention. + *

+ * This method ensures that when a claim is deleted, RealEstate is notified so that any ongoing + * transactions for that claim can be cancelled. + *

+ * + * @param event the claim deletion event from GriefPrevention + */ @EventHandler public void onClaimDeleted(ClaimDeletedEvent event) { ClaimEvents.onClaimDeleted(new GPClaim(event.getClaim())); diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/GPClaim.java b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/GPClaim.java index 2a4129f..7fc35cd 100644 --- a/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/GPClaim.java +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/GPClaim.java @@ -11,56 +11,108 @@ import me.EtienneDx.RealEstate.ClaimAPI.IClaim; import me.ryanhamshire.GriefPrevention.Claim; -public class GPClaim implements IClaim -{ +/** + * GPClaim is an implementation of the {@link IClaim} interface for GriefPrevention claims. + * It wraps a GriefPrevention {@link Claim} object and provides methods to access claim properties, + * as well as to manage permissions and claim relationships. + */ +public class GPClaim implements IClaim { + private Claim claim; - public GPClaim(Claim claim) - { + /** + * Constructs a new GPClaim that wraps the given GriefPrevention claim. + * + * @param claim the GriefPrevention claim to wrap + */ + public GPClaim(Claim claim) { this.claim = claim; } - public Claim getClaim() - { + /** + * Returns the underlying GriefPrevention claim. + * + * @return the GriefPrevention {@link Claim} object + */ + public Claim getClaim() { return claim; } + /** + * Returns the unique identifier of this claim. + * + * @return a string representing the claim's unique ID + */ @Override - public String getId() - { + public String getId() { return claim.getID().toString(); } + /** + * Returns the area of this claim. + * + * @return the area (in blocks) of the claim + */ @Override public int getArea() { return claim.getArea(); } + /** + * Returns the world in which this claim is located. + * + * @return the {@link World} object for the claim's world + */ @Override public World getWorld() { return claim.getLesserBoundaryCorner().getWorld(); } + /** + * Returns the X-coordinate of the claim's lesser boundary corner. + * + * @return the X-coordinate + */ @Override public int getX() { return claim.getLesserBoundaryCorner().getBlockX(); } + /** + * Returns the Y-coordinate of the claim's lesser boundary corner. + * + * @return the Y-coordinate + */ @Override public int getY() { return claim.getLesserBoundaryCorner().getBlockY(); } + /** + * Returns the Z-coordinate of the claim's lesser boundary corner. + * + * @return the Z-coordinate + */ @Override public int getZ() { return claim.getLesserBoundaryCorner().getBlockZ(); } + /** + * Determines if this claim is an admin claim. + * + * @return true if the claim is an admin claim, false otherwise + */ @Override public boolean isAdminClaim() { return claim.isAdminClaim(); } + /** + * Returns an {@link Iterable} over the child claims of this claim. + * + * @return an Iterable of {@link IClaim} objects representing child claims + */ @Override public Iterable getChildren() { return new Iterable() { @@ -88,31 +140,63 @@ public void remove() { }; } + /** + * Indicates whether this claim represents wilderness. + * For GriefPrevention claims, this always returns false. + * + * @return false + */ @Override public boolean isWilderness() { return false; } + /** + * Determines if this claim is a subclaim. + * + * @return true if the claim has a parent claim, false otherwise + */ @Override public boolean isSubClaim() { return claim.parent != null; } + /** + * Determines if this claim is a parent claim. + * + * @return true if the claim does not have a parent, false otherwise + */ @Override public boolean isParentClaim() { return claim.parent == null; } + /** + * Returns the parent claim of this claim. + * + * @return a new GPClaim representing the parent, or null if this is a parent claim + */ @Override public IClaim getParent() { return isSubClaim() ? new GPClaim(claim.parent) : null; } + /** + * Removes all player permissions for the specified player from this claim. + * + * @param player the UUID of the player whose permissions will be removed + */ @Override public void dropPlayerPermissions(UUID player) { claim.dropPermission(player.toString()); } + /** + * Adds player permissions to this claim based on the specified {@link ClaimPermission}. + * + * @param player the UUID of the player to add permissions for + * @param permission the {@link ClaimPermission} to add + */ @Override public void addPlayerPermissions(UUID player, ClaimPermission permission) { me.ryanhamshire.GriefPrevention.ClaimPermission gpPermission = null; @@ -136,39 +220,70 @@ public void addPlayerPermissions(UUID player, ClaimPermission permission) { claim.setPermission(player.toString(), gpPermission); } + /** + * Removes the specified player from the list of managers for this claim. + * + * @param player the UUID of the player to remove as a manager + */ @Override public void removeManager(UUID player) { claim.managers.remove(player.toString()); } + /** + * Returns the UUID of the owner of this claim. + * + * @return the owner's UUID + */ @Override public UUID getOwner() { return claim.ownerID; } + /** + * Returns the name of the owner of this claim. + * If the owner is not available, a default server keyword is returned. + * + * @return the owner's name, or a default value if not available + */ @Override public String getOwnerName() { return getOwner() != null ? Bukkit.getPlayer(getOwner()).getName() : RealEstate.instance.messages.keywordTheServer; } + /** + * Sets whether this claim should inherit permissions from its parent. + * + * @param inherit true if the claim should inherit permissions, false otherwise + */ @Override public void setInheritPermissions(boolean inherit) { claim.setSubclaimRestrictions(!inherit); } + /** + * Clears all player permissions for this claim. + */ @Override public void clearPlayerPermissions() { claim.clearPermissions(); } + /** + * Adds the specified player as a manager for this claim. + * + * @param player the UUID of the player to add as a manager + */ @Override public void addManager(UUID player) { claim.managers.add(player.toString()); } + /** + * Clears all managers from this claim. + */ @Override public void clearManagers() { claim.managers.clear(); } - } diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/GPPlayerData.java b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/GPPlayerData.java index a1e0ed1..d007660 100644 --- a/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/GPPlayerData.java +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/GPPlayerData.java @@ -3,38 +3,70 @@ import me.EtienneDx.RealEstate.ClaimAPI.IPlayerData; import me.ryanhamshire.GriefPrevention.PlayerData; -public class GPPlayerData implements IPlayerData -{ +/** + * GPPlayerData is an implementation of the {@link IPlayerData} interface for GriefPrevention. + * It wraps a GriefPrevention {@link PlayerData} object and provides access to a player's claim block data. + */ +public class GPPlayerData implements IPlayerData { + private PlayerData playerData; - public GPPlayerData(PlayerData playerData) - { + /** + * Constructs a new GPPlayerData instance that wraps the provided PlayerData. + * + * @param playerData the GriefPrevention PlayerData instance to wrap + */ + public GPPlayerData(PlayerData playerData) { this.playerData = playerData; } + /** + * Retrieves the number of accrued claim blocks for the player. + * + * @return the accrued claim blocks + */ @Override public int getAccruedClaimBlocks() { return playerData.getAccruedClaimBlocks(); } + /** + * Retrieves the number of bonus claim blocks for the player. + * + * @return the bonus claim blocks + */ @Override public int getBonusClaimBlocks() { return playerData.getBonusClaimBlocks(); } + /** + * Sets the number of accrued claim blocks for the player. + * + * @param accruedClaimBlocks the new value for accrued claim blocks + */ @Override public void setAccruedClaimBlocks(int accruedClaimBlocks) { playerData.setAccruedClaimBlocks(accruedClaimBlocks); } + /** + * Sets the number of bonus claim blocks for the player. + * + * @param bonusClaimBlocks the new value for bonus claim blocks + */ @Override public void setBonusClaimBlocks(int bonusClaimBlocks) { playerData.setBonusClaimBlocks(bonusClaimBlocks); } + /** + * Retrieves the number of remaining claim blocks available for the player. + * + * @return the remaining claim blocks + */ @Override public int getRemainingClaimBlocks() { return playerData.getRemainingClaimBlocks(); } - } diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/GriefPreventionAPI.java b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/GriefPreventionAPI.java index 387f011..acec923 100644 --- a/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/GriefPreventionAPI.java +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/GriefPrevention/GriefPreventionAPI.java @@ -10,22 +10,39 @@ import me.ryanhamshire.GriefPrevention.GriefPrevention; import me.ryanhamshire.GriefPrevention.Claim; -public class GriefPreventionAPI implements IClaimAPI -{ +/** + * Implementation of the {@link IClaimAPI} interface for GriefPrevention. + *

+ * This class provides methods to interact with GriefPrevention claims, + * including retrieving a claim at a given location, saving claim data, + * accessing player claim data, changing claim ownership, and registering + * claim-related events. + *

+ */ +public class GriefPreventionAPI implements IClaimAPI { + + /** + * Default constructor for GriefPreventionAPI. + *

+ * No additional initialization is required. + *

+ */ + public GriefPreventionAPI() { + // Default constructor. + } + @Override public IClaim getClaimAt(Location location) { Claim gpclaim = GriefPrevention.instance.dataStore.getClaimAt(location, false, null); - if (gpclaim == null) { return null; } - return new GPClaim(gpclaim); } @Override public void saveClaim(IClaim claim) { - if(claim instanceof GPClaim) + if (claim instanceof GPClaim) GriefPrevention.instance.dataStore.saveClaim(((GPClaim) claim).getClaim()); } @@ -36,7 +53,7 @@ public IPlayerData getPlayerData(UUID player) { @Override public void changeClaimOwner(IClaim claim, UUID newOwner) { - if(claim instanceof GPClaim) + if (claim instanceof GPClaim) GriefPrevention.instance.dataStore.changeClaimOwner(((GPClaim) claim).getClaim(), newOwner); } @@ -44,5 +61,4 @@ public void changeClaimOwner(IClaim claim, UUID newOwner) { public void registerEvents() { new ClaimPermissionListener().registerEvents(); } - } diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/IClaim.java b/src/me/EtienneDx/RealEstate/ClaimAPI/IClaim.java index 6ee3f4e..9fe5b87 100644 --- a/src/me/EtienneDx/RealEstate/ClaimAPI/IClaim.java +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/IClaim.java @@ -3,26 +3,156 @@ import java.util.UUID; import org.bukkit.World; +/** + * Represents a claim in the RealEstate plugin. + *

+ * An IClaim provides methods to access details about a claim, + * such as its location, area, ownership, and permissions management. + *

+ */ public interface IClaim { + + /** + * Retrieves the unique identifier for this claim. + * + * @return the claim's unique ID as a String + */ public String getId(); + + /** + * Retrieves the area of this claim in blocks. + * + * @return the area of the claim + */ public int getArea(); + + /** + * Retrieves the Bukkit {@link World} in which this claim is located. + * + * @return the world of the claim + */ public World getWorld(); + + /** + * Retrieves the X coordinate of this claim's location. + * + * @return the X coordinate + */ public int getX(); + + /** + * Retrieves the Y coordinate of this claim's location. + * + * @return the Y coordinate + */ public int getY(); + + /** + * Retrieves the Z coordinate of this claim's location. + * + * @return the Z coordinate + */ public int getZ(); + + /** + * Determines if this claim is an admin claim. + * + * @return {@code true} if this is an admin claim, {@code false} otherwise + */ public boolean isAdminClaim(); + + /** + * Retrieves an iterable collection of child claims. + * + * @return an {@link Iterable} of child claims + */ public Iterable getChildren(); + + /** + * Checks if this claim represents wilderness. + * + * @return {@code true} if this is a wilderness claim, {@code false} otherwise + */ public boolean isWilderness(); + + /** + * Determines if this claim is a subclaim. + * + * @return {@code true} if this is a subclaim, {@code false} otherwise + */ public boolean isSubClaim(); + + /** + * Determines if this claim is a parent claim. + * + * @return {@code true} if this is a parent claim, {@code false} otherwise + */ public boolean isParentClaim(); + + /** + * Retrieves the parent claim of this claim, if any. + * + * @return the parent claim, or {@code null} if there is no parent + */ public IClaim getParent(); + + /** + * Removes all permissions for the specified player from this claim. + * + * @param player the UUID of the player whose permissions should be removed + */ public void dropPlayerPermissions(UUID player); + + /** + * Grants a specific permission to a player for this claim. + * + * @param player the UUID of the player to grant permission to + * @param permission the {@link ClaimPermission} to grant + */ public void addPlayerPermissions(UUID player, ClaimPermission permission); + + /** + * Clears all player-specific permissions from this claim. + */ public void clearPlayerPermissions(); + + /** + * Removes the specified player from the list of managers for this claim. + * + * @param player the UUID of the manager to remove + */ public void removeManager(UUID player); + + /** + * Adds the specified player as a manager for this claim. + * + * @param player the UUID of the manager to add + */ public void addManager(UUID player); + + /** + * Clears all managers from this claim. + */ public void clearManagers(); + + /** + * Retrieves the owner of this claim. + * + * @return the UUID of the claim's owner + */ public UUID getOwner(); + + /** + * Retrieves the name of the claim's owner. + * + * @return the owner's name as a String + */ public String getOwnerName(); + + /** + * Sets whether this claim should inherit permissions from its parent claim. + * + * @param inherit {@code true} to enable inheritance; {@code false} to disable it + */ public void setInheritPermissions(boolean inherit); } diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/IClaimAPI.java b/src/me/EtienneDx/RealEstate/ClaimAPI/IClaimAPI.java index cb5ce20..63fe460 100644 --- a/src/me/EtienneDx/RealEstate/ClaimAPI/IClaimAPI.java +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/IClaimAPI.java @@ -3,10 +3,54 @@ import java.util.UUID; import org.bukkit.Location; +/** + * The IClaimAPI interface defines the methods for interacting with claims. + * Implementations of this interface provide functionality to: + *
    + *
  • Retrieve a claim at a specific location.
  • + *
  • Save or update claim data.
  • + *
  • Access player-specific claim data.
  • + *
  • Change the ownership of a claim.
  • + *
  • Register claim-related events.
  • + *
+ */ public interface IClaimAPI { + + /** + * Retrieves the claim at the specified Bukkit location. + * + * @param location the Bukkit location to check for a claim + * @return an instance of IClaim representing the claim at the given location, + * or {@code null} if no claim exists at that location + */ public IClaim getClaimAt(Location location); + + /** + * Saves the specified claim. + * Implementations should persist any changes to the claim data. + * + * @param claim the claim to be saved + */ public void saveClaim(IClaim claim); + + /** + * Retrieves the player data associated with the specified UUID. + * + * @param player the UUID of the player whose claim data is being requested + * @return an instance of IPlayerData representing the player's claim data + */ public IPlayerData getPlayerData(UUID player); + + /** + * Changes the owner of the specified claim to the new owner. + * + * @param claim the claim whose ownership is to be transferred + * @param newOwner the UUID of the new owner + */ public void changeClaimOwner(IClaim claim, UUID newOwner); + + /** + * Registers any necessary event listeners for claim-related events. + */ public void registerEvents(); } diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/IPlayerData.java b/src/me/EtienneDx/RealEstate/ClaimAPI/IPlayerData.java index 1ecb5af..34336fb 100644 --- a/src/me/EtienneDx/RealEstate/ClaimAPI/IPlayerData.java +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/IPlayerData.java @@ -1,9 +1,42 @@ package me.EtienneDx.RealEstate.ClaimAPI; +/** + * The IPlayerData interface defines methods to retrieve and modify a player's claim block data. + */ public interface IPlayerData { + + /** + * Retrieves the number of accrued claim blocks for the player. + * + * @return the number of accrued claim blocks + */ public int getAccruedClaimBlocks(); + + /** + * Retrieves the number of bonus claim blocks for the player. + * + * @return the number of bonus claim blocks + */ public int getBonusClaimBlocks(); + + /** + * Sets the number of accrued claim blocks for the player. + * + * @param accruedClaimBlocks the new number of accrued claim blocks + */ public void setAccruedClaimBlocks(int accruedClaimBlocks); + + /** + * Sets the number of bonus claim blocks for the player. + * + * @param bonusClaimBlocks the new number of bonus claim blocks + */ public void setBonusClaimBlocks(int bonusClaimBlocks); + + /** + * Retrieves the total number of claim blocks remaining for the player. + * + * @return the number of remaining claim blocks + */ public int getRemainingClaimBlocks(); } diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/Towny/TownyAPIWrapper.java b/src/me/EtienneDx/RealEstate/ClaimAPI/Towny/TownyAPIWrapper.java new file mode 100644 index 0000000..3982279 --- /dev/null +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/Towny/TownyAPIWrapper.java @@ -0,0 +1,109 @@ +package me.EtienneDx.RealEstate.ClaimAPI.Towny; + +import com.palmergames.bukkit.towny.TownyUniverse; +import com.palmergames.bukkit.towny.object.Resident; +import me.EtienneDx.RealEstate.ClaimAPI.IClaim; +import me.EtienneDx.RealEstate.ClaimAPI.IClaimAPI; +import me.EtienneDx.RealEstate.ClaimAPI.IPlayerData; +import org.bukkit.Location; +import java.util.UUID; + +/** + * TownyAPIWrapper provides a Towny-specific implementation of the IClaimAPI interface. + *

+ * Since Towny does not use claims in the same manner as other claim management plugins, + * this implementation wraps a Towny plot as an IClaim. + *

+ */ +public class TownyAPIWrapper implements IClaimAPI { + + + /** + * Instantiates a new TownyAPIWrapper. + */ + public TownyAPIWrapper() {} + + /** + * Retrieves the Towny plot (wrapped as an IClaim) at the given location. + *

+ * Note: Towny does not have claims like GriefPrevention, so this method wraps + * the Towny plot at the specified location. + *

+ * + * @param location the Bukkit location to check for a Towny plot + * @return an instance of IClaim representing the Towny plot at the given location + */ + @Override + public IClaim getClaimAt(Location location) { + return new TownyClaim(location); + } + + /** + * Saves the given claim. + *

+ * Towny claims are managed internally by Towny; therefore, no saving is necessary. + *

+ * + * @param claim the claim to be saved + */ + @Override + public void saveClaim(IClaim claim) { + // Towny claims are managed internally by Towny. + // No saving is necessary. + } + + /** + * Retrieves the player data for the specified player UUID. + *

+ * This method creates a TownyPlayerData wrapper for the given player's data. + *

+ * + * @param player the UUID of the player whose claim data is requested + * @return an instance of IPlayerData representing the player's data + */ + @Override + public IPlayerData getPlayerData(UUID player) { + return new TownyPlayerData(player); + } + + /** + * Changes the owner of the specified claim to the new owner. + *

+ * Changing the owner of a Towny plot is not supported in the same way as with other claim + * management systems. This method always throws an UnsupportedOperationException. + *

+ * + * @param claim the claim whose ownership is to be transferred + * @param newOwner the UUID of the new owner + * @throws UnsupportedOperationException always, since changing owner is not supported + */ + @Override + public void changeClaimOwner(IClaim claim, UUID newOwner) { + throw new UnsupportedOperationException("Changing claim owner is not supported for Towny claims."); + } + + /** + * Registers any necessary Towny-specific events. + *

+ * This implementation is empty; if Towny-specific events need to be registered, + * add the registration logic here. + *

+ */ + @Override + public void registerEvents() { + // If you want to register Towny-specific events, do so here. + } + + /** + * Retrieves a Resident object corresponding to the given resident name. + *

+ * If the resident is not registered within Towny, this method returns null. + *

+ * + * @param name the name of the resident + * @return the Resident associated with the given name, or null if not found + */ + public Resident getResident(String name) { + return TownyUniverse.getInstance().getResident(name); + } +} diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/Towny/TownyClaim.java b/src/me/EtienneDx/RealEstate/ClaimAPI/Towny/TownyClaim.java new file mode 100644 index 0000000..3f13d8f --- /dev/null +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/Towny/TownyClaim.java @@ -0,0 +1,289 @@ +package me.EtienneDx.RealEstate.ClaimAPI.Towny; + +import me.EtienneDx.RealEstate.ClaimAPI.ClaimPermission; +import me.EtienneDx.RealEstate.ClaimAPI.IClaim; +import org.bukkit.Location; +import org.bukkit.World; +import java.util.ArrayList; +import java.util.UUID; + +/** + * A simple Towny claim wrapper. + *

+ * In Towny, the “claim” is the plot belonging to a town. + * This class creates an IClaim implementation from a Bukkit Location. + *

+ */ +public class TownyClaim implements IClaim { + + private Location location; + private String ownerName; // For example, the name of the town or the plot owner + + /** + * Constructs a new TownyClaim using the given location. + * + * @param location the Bukkit Location representing the Towny plot + */ + public TownyClaim(Location location) { + this.location = location; + // In a full implementation you would look up the town/plot data. + // For this example, we set a dummy owner name. + this.ownerName = "TownyOwner"; + } + + /** + * Returns a unique identifier for this Towny claim. + *

+ * The ID is generated based on the world name and the block X and Z coordinates. + *

+ * + * @return a unique string identifier for the claim + */ + @Override + public String getId() { + return "towny-" + location.getWorld().getName() + "-" + location.getBlockX() + "-" + location.getBlockZ(); + } + + /** + * Returns the area of the Towny claim. + *

+ * Assumes a standard Towny plot size of 16x16 blocks. + *

+ * + * @return the area of the claim in blocks + */ + @Override + public int getArea() { + return 16 * 16; + } + + /** + * Returns the world where this claim is located. + * + * @return the {@link World} of the claim + */ + @Override + public World getWorld() { + return location.getWorld(); + } + + /** + * Returns the X-coordinate of the claim's location. + * + * @return the block X coordinate + */ + @Override + public int getX() { + return location.getBlockX(); + } + + /** + * Returns the Y-coordinate of the claim's location. + * + * @return the block Y coordinate + */ + @Override + public int getY() { + return location.getBlockY(); + } + + /** + * Returns the Z-coordinate of the claim's location. + * + * @return the block Z coordinate + */ + @Override + public int getZ() { + return location.getBlockZ(); + } + + /** + * Indicates whether this claim is an admin claim. + *

+ * Towny does not designate "admin claims" in the same manner. + *

+ * + * @return {@code false} always + */ + @Override + public boolean isAdminClaim() { + return false; + } + + /** + * Returns an iterable over the child claims. + *

+ * Towny plots do not have child claims. + *

+ * + * @return an empty list + */ + @Override + public Iterable getChildren() { + return new ArrayList<>(); + } + + /** + * Indicates whether this claim represents wilderness. + *

+ * For simplicity, a Towny claim is never considered wilderness. + *

+ * + * @return {@code false} always + */ + @Override + public boolean isWilderness() { + return false; + } + + /** + * Indicates whether this claim is a subclaim. + *

+ * Not applicable for Towny. + *

+ * + * @return {@code false} always + */ + @Override + public boolean isSubClaim() { + return false; + } + + /** + * Indicates whether this claim is a parent claim. + *

+ * All Towny claims are treated as parent claims. + *

+ * + * @return {@code true} always + */ + @Override + public boolean isParentClaim() { + return true; + } + + /** + * Returns the parent claim of this claim. + *

+ * Towny plots do not have a parent claim. + *

+ * + * @return {@code null} always + */ + @Override + public IClaim getParent() { + return null; + } + + /** + * Drops any player-specific permissions for the given player on this claim. + *

+ * Not applicable for Towny. + *

+ * + * @param player the UUID of the player + */ + @Override + public void dropPlayerPermissions(UUID player) { + // Not applicable for Towny. + } + + /** + * Adds a specific permission for the given player on this claim. + *

+ * Not applicable for Towny. + *

+ * + * @param player the UUID of the player + * @param permission the permission to add + */ + @Override + public void addPlayerPermissions(UUID player, ClaimPermission permission) { + // Not applicable for Towny. + } + + /** + * Clears all player-specific permissions on this claim. + *

+ * Not applicable for Towny. + *

+ */ + @Override + public void clearPlayerPermissions() { + // Not applicable for Towny. + } + + /** + * Removes the specified manager from this claim. + *

+ * Not applicable for Towny. + *

+ * + * @param player the UUID of the manager to remove + */ + @Override + public void removeManager(UUID player) { + // Not applicable for Towny. + } + + /** + * Adds a manager to this claim. + *

+ * Not applicable for Towny. + *

+ * + * @param player the UUID of the manager to add + */ + @Override + public void addManager(UUID player) { + // Not applicable for Towny. + } + + /** + * Clears all managers from this claim. + *

+ * Not applicable for Towny. + *

+ */ + @Override + public void clearManagers() { + // Not applicable for Towny. + } + + /** + * Returns the owner of the claim as a UUID. + *

+ * In a full implementation, this method would resolve the owner's UUID from Towny data. + * For this example, it returns {@code null}. + *

+ * + * @return the UUID of the claim's owner, or {@code null} if not available + */ + @Override + public UUID getOwner() { + return null; + } + + /** + * Returns the name of the claim's owner. + * + * @return the owner name as a String + */ + @Override + public String getOwnerName() { + return ownerName; + } + + /** + * Sets whether this claim should inherit permissions from its parent. + *

+ * Not applicable for Towny. + *

+ * + * @param inherit {@code true} to inherit permissions, {@code false} otherwise + */ + @Override + public void setInheritPermissions(boolean inherit) { + // Not applicable for Towny. + } +} diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/Towny/TownyPlayerData.java b/src/me/EtienneDx/RealEstate/ClaimAPI/Towny/TownyPlayerData.java new file mode 100644 index 0000000..1fb6c8b --- /dev/null +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/Towny/TownyPlayerData.java @@ -0,0 +1,89 @@ +package me.EtienneDx.RealEstate.ClaimAPI.Towny; + +import me.EtienneDx.RealEstate.ClaimAPI.IPlayerData; +import java.util.UUID; + +/** + * A stub implementation of IPlayerData for Towny. + *

+ * Towny does not use claim blocks so these methods return zero or do nothing. + *

+ */ +public class TownyPlayerData implements IPlayerData { + + private UUID playerUUID; + + /** + * Constructs a new TownyPlayerData for the specified player UUID. + * + * @param playerUUID the UUID of the player + */ + public TownyPlayerData(UUID playerUUID) { + this.playerUUID = playerUUID; + } + + /** + * Returns the number of accrued claim blocks. + *

+ * Always returns 0 as Towny does not use claim blocks. + *

+ * + * @return 0 + */ + @Override + public int getAccruedClaimBlocks() { + return 0; + } + + /** + * Returns the number of bonus claim blocks. + *

+ * Always returns 0 as Towny does not use claim blocks. + *

+ * + * @return 0 + */ + @Override + public int getBonusClaimBlocks() { + return 0; + } + + /** + * Sets the accrued claim blocks. + *

+ * This operation is not applicable for Towny. + *

+ * + * @param accruedClaimBlocks the number of accrued claim blocks (ignored) + */ + @Override + public void setAccruedClaimBlocks(int accruedClaimBlocks) { + // Not applicable for Towny. + } + + /** + * Sets the bonus claim blocks. + *

+ * This operation is not applicable for Towny. + *

+ * + * @param bonusClaimBlocks the number of bonus claim blocks (ignored) + */ + @Override + public void setBonusClaimBlocks(int bonusClaimBlocks) { + // Not applicable for Towny. + } + + /** + * Returns the remaining claim blocks. + *

+ * Always returns 0 as Towny does not use claim blocks. + *

+ * + * @return 0 + */ + @Override + public int getRemainingClaimBlocks() { + return 0; + } +} diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/WorldGuard/WGClaim.java b/src/me/EtienneDx/RealEstate/ClaimAPI/WorldGuard/WGClaim.java index 376788b..01314cf 100644 --- a/src/me/EtienneDx/RealEstate/ClaimAPI/WorldGuard/WGClaim.java +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/WorldGuard/WGClaim.java @@ -1,34 +1,61 @@ package me.EtienneDx.RealEstate.ClaimAPI.WorldGuard; import com.sk89q.worldedit.math.BlockVector3; -import com.sk89q.worldedit.math.Vector3; import com.sk89q.worldguard.protection.regions.ProtectedRegion; import org.bukkit.Bukkit; import org.bukkit.World; import java.util.Collections; import java.util.UUID; import me.EtienneDx.RealEstate.ClaimAPI.IClaim; -import me.EtienneDx.RealEstate.RealEstate; import me.EtienneDx.RealEstate.ClaimAPI.ClaimPermission; +/** + * WGClaim is an implementation of the {@link IClaim} interface for WorldGuard regions. + *

+ * This class wraps a WorldGuard {@link ProtectedRegion} and provides methods to access claim information + * such as its area, location, ownership, and permission management. + *

+ */ public class WGClaim implements IClaim { - // A constant used to represent the "server" owner for admin claims. + /** + * A constant used to represent the "server" owner for admin claims. + */ public static final UUID SERVER_UUID = new UUID(0L, 0L); private final ProtectedRegion region; private final World world; + /** + * Constructs a new WGClaim instance. + * + * @param region the WorldGuard ProtectedRegion representing the claim. + * @param world the Bukkit World in which the claim exists. + */ public WGClaim(ProtectedRegion region, World world) { this.region = region; this.world = world; } + /** + * Returns the unique identifier of this claim. + * + * @return the region ID as a String. + */ @Override public String getId() { return region.getId(); } + /** + * Calculates and returns the area of this claim. + *

+ * The area is computed by subtracting the minimum point from the maximum point along the X and Z axes, + * then multiplying the resulting width and length. + *

+ * + * @return the area of the claim. + */ @Override public int getArea() { // Convert the region’s minimum and maximum BlockVector3 into dimensions. @@ -39,92 +66,225 @@ public int getArea() { return width * length; } + /** + * Returns the world in which this claim is located. + * + * @return the Bukkit World object. + */ @Override public World getWorld() { return world; } + /** + * Returns the X-coordinate of the claim's minimum point. + * + * @return the X-coordinate as an integer. + */ @Override public int getX() { return (int) region.getMinimumPoint().getX(); } + /** + * Returns the Y-coordinate of the claim's minimum point. + * + * @return the Y-coordinate as an integer. + */ @Override public int getY() { return (int) region.getMinimumPoint().getY(); } + /** + * Returns the Z-coordinate of the claim's minimum point. + * + * @return the Z-coordinate as an integer. + */ @Override public int getZ() { return (int) region.getMinimumPoint().getZ(); } + /** + * Determines whether this claim is an admin claim. + *

+ * A claim is considered an admin claim if it has no owners. + *

+ * + * @return {@code true} if the region has no owners, {@code false} otherwise. + */ @Override public boolean isAdminClaim() { // If the region has no owners, treat it as an admin claim. return region.getOwners().getUniqueIds().isEmpty(); } + /** + * Returns the child claims of this claim. + *

+ * WorldGuard does not support child claims in this implementation, + * so an empty list is returned. + *

+ * + * @return an empty iterable. + */ @Override public Iterable getChildren() { // WorldGuard does not support child claims; return an empty list. return Collections.emptyList(); } + /** + * Indicates whether this claim is considered "wilderness". + *

+ * In this implementation, WorldGuard regions are never considered wilderness. + *

+ * + * @return {@code false} always. + */ @Override public boolean isWilderness() { // For our purposes, assume WorldGuard regions are never "wilderness". return false; } + /** + * Indicates whether this claim is a subclaim. + *

+ * Not supported in this implementation. + *

+ * + * @return {@code false}. + */ @Override public boolean isSubClaim() { // Not supported. return false; } + /** + * Indicates whether this claim is a parent claim. + *

+ * Every WorldGuard region is treated as a parent claim in this implementation. + *

+ * + * @return {@code true} always. + */ @Override public boolean isParentClaim() { // Every region here is treated as a parent claim. return true; } + /** + * Returns the parent claim of this claim. + *

+ * Not supported in this implementation. + *

+ * + * @return {@code null}. + */ @Override public IClaim getParent() { // Not supported. return null; } + /** + * Removes the specified player's permissions from this claim. + *

+ * This is done by removing the player's name from the region's members list. + *

+ * + * @param player the UUID of the player whose permissions should be removed. + */ @Override public void dropPlayerPermissions(UUID player) { - // Not implemented. + // Remove the player's name from the region's members list. + String name = Bukkit.getOfflinePlayer(player).getName(); + if (name != null) { + region.getMembers().removePlayer(name); + } } + /** + * Adds the specified permission for the given player to this claim. + *

+ * For this implementation, all permissions are granted by adding the player's name + * to the region's members list. + *

+ * + * @param player the UUID of the player. + * @param permission the claim permission to add. + */ @Override public void addPlayerPermissions(UUID player, ClaimPermission permission) { - // Not implemented. + // For this implementation, all permissions are granted by adding the player to the members. + String name = Bukkit.getOfflinePlayer(player).getName(); + if (name != null) { + region.getMembers().addPlayer(name); + } } + /** + * Clears all player permissions from this claim. + *

+ * This is done by clearing the entire members list of the region. + *

+ */ @Override public void clearPlayerPermissions() { - // Not implemented. + // Clear all members from the region. + region.getMembers().getPlayers().clear(); } + /** + * Removes a manager from this claim. + *

+ * Not supported in this implementation. + *

+ * + * @param player the UUID of the player to remove as manager. + */ @Override public void removeManager(UUID player) { - // Not implemented. + // Not supported in this implementation. } + /** + * Adds a manager to this claim. + *

+ * Not supported in this implementation. + *

+ * + * @param player the UUID of the player to add as manager. + */ @Override public void addManager(UUID player) { - // Not implemented. + // Not supported in this implementation. } + /** + * Clears all managers from this claim. + *

+ * Not supported in this implementation. + *

+ */ @Override public void clearManagers() { - // Not implemented. + // Not supported in this implementation. } + /** + * Returns the owner of this claim. + *

+ * If the region has no owners, this method returns the constant {@link #SERVER_UUID} + * to indicate an admin claim. Otherwise, it returns the first owner's UUID. + *

+ * + * @return the owner's UUID, or {@code SERVER_UUID} if no owners exist. + */ @Override public UUID getOwner() { // If no owners, then this is an admin claim. Return the constant SERVER_UUID. @@ -135,6 +295,15 @@ public UUID getOwner() { return region.getOwners().getUniqueIds().iterator().next(); } + /** + * Returns the owner's name of this claim. + *

+ * If this is an admin claim, returns "SERVER". Otherwise, it attempts to look up the owner's name + * via Bukkit's OfflinePlayer; if not found, returns "Unknown". + *

+ * + * @return the owner's name as a String. + */ @Override public String getOwnerName() { // If it's an admin claim, return a fixed name. @@ -143,10 +312,18 @@ public String getOwnerName() { } // Otherwise, look up the owner by UUID. UUID owner = getOwner(); - return (owner != null && Bukkit.getOfflinePlayer(owner) != null) + return (owner != null && Bukkit.getOfflinePlayer(owner) != null) ? Bukkit.getOfflinePlayer(owner).getName() : "Unknown"; } + /** + * Sets whether this claim should inherit permissions from its parent. + *

+ * Not supported in this implementation. + *

+ * + * @param inherit if {@code true}, the claim should inherit permissions (ignored). + */ @Override public void setInheritPermissions(boolean inherit) { // Not supported. diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/WorldGuard/WGPlayerData.java b/src/me/EtienneDx/RealEstate/ClaimAPI/WorldGuard/WGPlayerData.java index 02deb8d..090fe43 100644 --- a/src/me/EtienneDx/RealEstate/ClaimAPI/WorldGuard/WGPlayerData.java +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/WorldGuard/WGPlayerData.java @@ -2,31 +2,80 @@ import me.EtienneDx.RealEstate.ClaimAPI.IPlayerData; +/** + * A dummy implementation of {@link IPlayerData} for WorldGuard. + *

+ * Since WorldGuard does not supply claim block data, this implementation always returns zero + * and does nothing for the setter methods. + *

+ */ public class WGPlayerData implements IPlayerData { - // Since WorldGuard does not supply claim block data, - // we return zero or do nothing in our dummy implementation. - + /** + * Instantiates a new WGPlayerData object. + */ + public WGPlayerData() {} + + /** + * Returns the number of accrued claim blocks for a player. + *

+ * Since WorldGuard does not use claim blocks, this method returns zero. + *

+ * + * @return 0 always. + */ @Override public int getAccruedClaimBlocks() { return 0; } + /** + * Returns the number of bonus claim blocks for a player. + *

+ * Since WorldGuard does not use claim blocks, this method returns zero. + *

+ * + * @return 0 always. + */ @Override public int getBonusClaimBlocks() { return 0; } + /** + * Sets the number of accrued claim blocks for a player. + *

+ * This is a no-operation implementation because WorldGuard does not support claim block data. + *

+ * + * @param accruedClaimBlocks the number of accrued claim blocks (ignored) + */ @Override public void setAccruedClaimBlocks(int accruedClaimBlocks) { - // No-op. + // No operation performed. } + /** + * Sets the number of bonus claim blocks for a player. + *

+ * This is a no-operation implementation because WorldGuard does not support claim block data. + *

+ * + * @param bonusClaimBlocks the number of bonus claim blocks (ignored) + */ @Override public void setBonusClaimBlocks(int bonusClaimBlocks) { - // No-op. + // No operation performed. } + /** + * Returns the number of remaining claim blocks for a player. + *

+ * Since WorldGuard does not utilize claim blocks, this method returns zero. + *

+ * + * @return 0 always. + */ @Override public int getRemainingClaimBlocks() { return 0; diff --git a/src/me/EtienneDx/RealEstate/ClaimAPI/WorldGuard/WorldGuardAPI.java b/src/me/EtienneDx/RealEstate/ClaimAPI/WorldGuard/WorldGuardAPI.java index f34d8a6..c4d257f 100644 --- a/src/me/EtienneDx/RealEstate/ClaimAPI/WorldGuard/WorldGuardAPI.java +++ b/src/me/EtienneDx/RealEstate/ClaimAPI/WorldGuard/WorldGuardAPI.java @@ -7,26 +7,51 @@ import com.sk89q.worldguard.protection.regions.ProtectedRegion; import com.sk89q.worldedit.bukkit.BukkitAdapter; import org.bukkit.Location; -import org.bukkit.World; import me.EtienneDx.RealEstate.ClaimAPI.IClaim; import me.EtienneDx.RealEstate.ClaimAPI.IClaimAPI; import me.EtienneDx.RealEstate.ClaimAPI.IPlayerData; +import me.EtienneDx.RealEstate.ClaimAPI.ClaimPermission; +import java.util.UUID; + +/** + * WorldGuardAPI provides an implementation of the IClaimAPI interface using WorldGuard. + *

+ * This class uses WorldGuard's platform API to query and wrap claims in the WGClaim class. + * Note that some operations (such as changing a claim's owner) are not supported. + *

+ */ public class WorldGuardAPI implements IClaimAPI { private final RegionContainer container; + /** + * Constructs a new WorldGuardAPI instance. + *

+ * It obtains the RegionContainer via the WorldGuard platform API. + *

+ */ public WorldGuardAPI() { // Obtain the RegionContainer via the WorldGuard platform API. container = WorldGuard.getInstance().getPlatform().getRegionContainer(); } + /** + * Retrieves a claim at the specified Bukkit location. + *

+ * The method converts the given Bukkit location into a WorldEdit location and queries for applicable + * WorldGuard regions. If one or more regions are found, the first region is wrapped in a WGClaim instance. + *

+ * + * @param bukkitLocation the Bukkit location to check for a claim + * @return an IClaim instance representing the claim at the location, or {@code null} if no claim exists + */ @Override public IClaim getClaimAt(Location bukkitLocation) { // Convert the Bukkit location to a WorldEdit location using BukkitAdapter. com.sk89q.worldedit.util.Location weLocation = BukkitAdapter.adapt(bukkitLocation); RegionQuery query = container.createQuery(); - // Use size() instead of isEmpty() for ApplicableRegionSet + // Query applicable regions at this location. ApplicableRegionSet regions = query.getApplicableRegions(weLocation); if (regions.size() > 0) { // For this example, we take the first region. @@ -37,25 +62,91 @@ public IClaim getClaimAt(Location bukkitLocation) { return null; } + /** + * Saves the specified claim. + *

+ * For WorldGuard, claims are automatically saved; thus, no action is required. + *

+ * + * @param claim the claim to be saved + */ @Override public void saveClaim(IClaim claim) { // WorldGuard auto-saves; no action needed. } + /** + * Retrieves player-specific claim data. + *

+ * Not supported in this implementation. + *

+ * + * @param player the UUID of the player + * @return {@code null} as player data is not implemented + */ @Override - public IPlayerData getPlayerData(java.util.UUID player) { + public IPlayerData getPlayerData(UUID player) { // Not supported in this implementation; return null or a dummy implementation. return null; } + /** + * Changes the owner of the specified claim. + *

+ * Changing a claim's owner is not supported by WorldGuard. This method always throws an exception. + *

+ * + * @param claim the claim whose owner is to be changed + * @param newOwner the UUID of the new owner + * @throws UnsupportedOperationException always thrown as this operation is not supported + */ @Override - public void changeClaimOwner(IClaim claim, java.util.UUID newOwner) { + public void changeClaimOwner(IClaim claim, UUID newOwner) { // Changing a claim's owner is not supported by WorldGuard. throw new UnsupportedOperationException("Changing claim owner is not supported with WorldGuard."); } + /** + * Registers any necessary event listeners for claim-related events. + *

+ * No events are registered for WorldGuard integration in this implementation. + *

+ */ @Override public void registerEvents() { // No events to register for WorldGuard integration in this example. } + + // -- Added helper methods for granting/revoking player permissions -- + + /** + * Adds the given player's permission to the specified claim. + *

+ * This method delegates to the WGClaim implementation's {@code addPlayerPermissions} method. + *

+ * + * @param claim the claim (must be an instance of WGClaim) + * @param player the UUID of the player to grant permission + * @param permission the permission type to add (e.g. BUILD, ACCESS, etc.) + */ + public void addPlayerPermission(IClaim claim, UUID player, ClaimPermission permission) { + if (claim instanceof WGClaim) { + ((WGClaim) claim).addPlayerPermissions(player, permission); + } + } + + /** + * Removes any permission for the given player from the specified claim. + *

+ * This method delegates to the WGClaim implementation's {@code dropPlayerPermissions} method. + *

+ * + * @param claim the claim (must be an instance of WGClaim) + * @param player the UUID of the player whose permission should be removed + */ + public void removePlayerPermission(IClaim claim, UUID player) { + if (claim instanceof WGClaim) { + ((WGClaim) claim).dropPlayerPermissions(player); + } + } } diff --git a/src/me/EtienneDx/RealEstate/ClaimEvents.java b/src/me/EtienneDx/RealEstate/ClaimEvents.java index 209626e..b2c9724 100644 --- a/src/me/EtienneDx/RealEstate/ClaimEvents.java +++ b/src/me/EtienneDx/RealEstate/ClaimEvents.java @@ -1,16 +1,52 @@ package me.EtienneDx.RealEstate; import org.bukkit.entity.Player; - import me.EtienneDx.RealEstate.ClaimAPI.ClaimPermission; import me.EtienneDx.RealEstate.ClaimAPI.IClaim; import me.EtienneDx.RealEstate.Transactions.BoughtTransaction; import me.EtienneDx.RealEstate.Transactions.Transaction; +/** + * Provides helper methods to handle claim-related events. + *

+ * These methods are used by the RealEstate plugin to check permissions on claims + * involved in transactions and to cancel transactions when a claim is deleted. + *

+ */ public class ClaimEvents { + + /** + * Default constructor for ClaimEvents. + *

+ * This class is not meant to be instantiated; all methods are static. + * The constructor is provided only to eliminate Javadoc warnings. + *

+ */ + public ClaimEvents() { + // No instantiation required. + } + + /** + * Checks if a player is allowed to perform a certain action on a claim, + * given an ongoing transaction. + *

+ * The method examines an active transaction for the specified claim. + * If the transaction is a {@link BoughtTransaction} with a non-null buyer and the player is either + * the owner of the transaction or (in the case of admin claims) has the appropriate admin permission, + * then a specific error message is returned based on the type of permission (EDIT, ACCESS, BUILD, CONTAINER, or MANAGE). + * In addition, if the action involves editing or managing, the method checks all child claims + * for active transactions and returns an error message if any are found. + * If no conditions are met that would prevent the action, {@code null} is returned. + *

+ * + * @param claim the claim being checked + * @param player the player attempting the action + * @param permission the permission type being checked + * @return an error message if the action is not allowed; {@code null} if the action is permitted + */ public static String onClaimPermission(IClaim claim, Player player, ClaimPermission permission) { Transaction transaction = RealEstate.transactionsStore.getTransaction(claim); - // we only have to remove the owner's access, the rest is handled by GP + // We only have to remove the owner's access; the rest is handled by GriefPrevention. if (transaction != null && (player.getUniqueId().equals(transaction.getOwner()) || (claim.isAdminClaim() && player.hasPermission("griefprevention.adminclaims"))) @@ -31,6 +67,7 @@ public static String onClaimPermission(IClaim claim, Player player, ClaimPermiss } } + // Check child claims for active transactions if the permission is EDIT or MANAGE. if (permission == ClaimPermission.EDIT || permission == ClaimPermission.MANAGE) { for (IClaim child : claim.getChildren()) { Transaction tr = RealEstate.transactionsStore.getTransaction(child); @@ -44,12 +81,25 @@ public static String onClaimPermission(IClaim claim, Player player, ClaimPermiss return null; } + /** + * Handles the deletion of a claim by cancelling any ongoing transactions. + *

+ * When a claim is deleted, this method cancels the transaction associated with the claim, + * and then iterates over all child claims (if any) to cancel their transactions as well. + *

+ * + * @param claim the claim that has been deleted + */ public static void onClaimDeleted(IClaim claim) { Transaction tr = RealEstate.transactionsStore.getTransaction(claim); - if(tr != null) tr.tryCancelTransaction(null, true); + if (tr != null) { + tr.tryCancelTransaction(null, true); + } for (IClaim child : claim.getChildren()) { tr = RealEstate.transactionsStore.getTransaction(child); - if(tr != null) tr.tryCancelTransaction(null, true); + if (tr != null) { + tr.tryCancelTransaction(null, true); + } } } } diff --git a/src/me/EtienneDx/RealEstate/Config.java b/src/me/EtienneDx/RealEstate/Config.java index b5a5076..c30e017 100644 --- a/src/me/EtienneDx/RealEstate/Config.java +++ b/src/me/EtienneDx/RealEstate/Config.java @@ -11,152 +11,389 @@ import me.EtienneDx.AnnotationConfig.ConfigField; import me.EtienneDx.AnnotationConfig.ConfigFile; +/** + * Represents the configuration settings for the RealEstate plugin. + *

+ * This class uses the AnnotationConfig system to load and save configurable options + * such as keywords for signs, rules for transactions, default prices, durations, + * messaging settings, and database settings. + *

+ */ @ConfigFile(header = "RealEstate wiki and newest versions are available at http://www.github.com/msburgess3200/RealEstate") public class Config extends AnnotationConfig { + + /** + * The plugin description file. + */ public PluginDescriptionFile pdf; + /** + * The file path for the configuration file. + */ public final String configFilePath = RealEstate.pluginDirPath + "config.yml"; + + /** + * The file path for the log file. + */ public final String logFilePath = RealEstate.pluginDirPath + "GriefProtection_RealEstate.log"; + // Keyword configurations + + /** + * The prefix displayed before any chat message. + */ @ConfigField(name="RealEstate.Keywords.ChatPrefix", comment="What is displayed before any chat message") public String chatPrefix = "$f[$6RealEstate$f] "; + /** + * The header displayed on the top of the signs. + */ @ConfigField(name="RealEstate.Keywords.SignsHeader", comment = "What is displayed in top of the signs") public String cfgSignsHeader = "$6[RealEstate]"; + /** + * A list of possible sign headers used to indicate a claim is for sale. + */ @ConfigField(name="RealEstate.Keywords.Sell", comment = "List of all possible signs headers to sell a claim") public List cfgSellKeywords = Arrays.asList("[sell]", "[sell claim]", "[sc]", "[re]", "[realestate]"); + + /** + * A list of possible sign headers used to indicate a claim is for rent. + */ @ConfigField(name="RealEstate.Keywords.Rent", comment = "List of all possible signs headers to rent a claim") public List cfgRentKeywords = Arrays.asList("[rent]", "[rent claim]", "[rc]"); + + /** + * A list of possible sign headers used for renting container access only. + */ @ConfigField(name="RealEstate.Keywords.ContainerRent", comment = "List of all possible signs headers to rent container access only") public List cfgContainerRentKeywords = Arrays.asList("[container rent]", "[crent]"); + + /** + * A list of possible sign headers used to indicate a claim is for lease. + */ @ConfigField(name="RealEstate.Keywords.Lease", comment = "List of all possible signs headers to lease a claim") public List cfgLeaseKeywords = Arrays.asList("[lease]", "[lease claim]", "[lc]"); + + /** + * A list of possible sign headers used to indicate a claim is for auction. + */ @ConfigField(name="RealEstate.Keywords.Auction", comment = "List of all possible signs headers to auction a claim") public List cfgAuctionKeywords = Arrays.asList("[auction]", "[auction claim]", "[ac]"); + // Replacement texts for signs + + /** + * The text displayed on signs for properties to sell. + */ @ConfigField(name="RealEstate.Keywords.Replace.Sell", comment = "What is displayed on signs for properties to sell") public String cfgReplaceSell = "FOR SALE"; + + /** + * The text displayed on signs for properties to rent. + */ @ConfigField(name="RealEstate.Keywords.Replace.Rent", comment = "What is displayed on signs for properties to rent") public String cfgReplaceRent = "FOR RENT"; + + /** + * The text displayed on signs for properties to lease. + */ @ConfigField(name="RealEstate.Keywords.Replace.Lease", comment = "What is displayed on signs for properties to lease") public String cfgReplaceLease = "FOR LEASE"; + + /** + * The text displayed on signs for properties to auction. + */ @ConfigField(name="RealEstate.Keywords.Replace.Auction", comment = "What is displayed on signs for properties to auction") public String cfgReplaceAuction = "FOR AUCTION"; + + /** + * The text displayed on the first line of a sign once a claim is rented. + */ @ConfigField(name="RealEstate.Keywords.Replace.Ongoing.Rent", comment = "What is displayed on the first line of the sign once someone rents a claim.") public String cfgReplaceOngoingRent = "[Rented]"; + + /** + * The text displayed on the third line of a sign when renting container access only. + */ @ConfigField(name="RealEstate.Keywords.Replace.ContainerRent", comment = "What is displayed on the third line of the sign when renting container access only.") public String cfgContainerRentLine = ChatColor.BLUE + "Containers only"; + // Rule configurations + + /** + * Whether selling claims is enabled. + */ @ConfigField(name="RealEstate.Rules.Sell", comment = "Is selling claims enabled?") public boolean cfgEnableSell = true; + + /** + * Whether renting claims is enabled. + */ @ConfigField(name="RealEstate.Rules.Rent", comment = "Is renting claims enabled?") public boolean cfgEnableRent = true; + + /** + * Whether leasing claims is enabled. + */ @ConfigField(name="RealEstate.Rules.Lease", comment = "Is leasing claims enabled?") public boolean cfgEnableLease = true; + + /** + * Whether auctioning claims is enabled. + */ @ConfigField(name="RealEstate.Rules.Auction", comment = "Is auctioning claims enabled?") public boolean cfgEnableAuction = true; + /** + * Whether an auctioneer can cancel their auction after receiving offers. + */ @ConfigField(name="RealEstate.Rules.CancelAuction", comment = "Can an auctioneer cancel his auction if he already received offers?") public boolean cfgCancelAuction = false; + + /** + * Whether an auctioneer can outbid themselves. + */ @ConfigField(name="RealEstate.Rules.DisableOutbidSelf", comment = "Can an auctioneer outbid himself?") public boolean cfgDisableOutbidSelf = false; + /** + * Whether players renting claims can enable automatic renew. + */ @ConfigField(name="RealEstate.Rules.AutomaticRenew", comment = "Can players renting claims enable automatic renew of their contracts?") public boolean cfgEnableAutoRenew = true; + + /** + * Whether rent signs should be destroyed once the claim is rented. + */ @ConfigField(name="RealEstate.Rules.DestroySigns.Rent", comment = "Should the rent signs get destroyed once the claim is rented?") public boolean cfgDestroyRentSigns = false; + + /** + * Whether lease signs should be destroyed once the claim is leased. + */ @ConfigField(name="RealEstate.Rules.DestroySigns.Lease", comment = "Should the lease signs get destroyed once the claim is leased?") public boolean cfgDestroyLeaseSigns = true; + /** + * Whether claim blocks are transferred to the new owner on purchase. + */ @ConfigField(name="RealEstate.Rules.TransferClaimBlocks", comment = "Are the claim blocks transferred to the new owner on purchase or should the buyer provide them?") public boolean cfgTransferClaimBlocks = true; + /** + * Whether to display prices with a currency symbol on signs. + */ @ConfigField(name="RealEstate.Rules.UseCurrencySymbol", comment = "Should the signs display the prices with a currency symbol instead of the full currency name?") public boolean cfgUseCurrencySymbol = false; + + /** + * The currency symbol to use if UseCurrencySymbol is enabled. + */ @ConfigField(name="RealEstate.Rules.CurrencySymbol", comment = "In case UseCurrencySymbol is true, what symbol should be used?") public String cfgCurrencySymbol = "$"; + + /** + * Whether decimal currency values are allowed. + */ @ConfigField(name="RealEstate.Rules.UseDecimalCurrency", comment = "Allow players to use decimal currency e.g. $10.15") public boolean cfgUseDecimalCurrency = true; + // Messaging configurations + + /** + * Whether the owner should be messaged when a claim is rented, leased, or bought. + */ @ConfigField(name="RealEstate.Messaging.MessageOwner", comment = "Should the owner get messaged once one of his claim is rented/leased/bought and on end of contracts?") public boolean cfgMessageOwner = true; + + /** + * Whether the buyer should be messaged when a claim is rented, leased, or bought. + */ @ConfigField(name="RealEstate.Messaging.MessageBuyer", comment = "Should the buyer get messaged once one of his claim is rented/leased/bought and on end of contracts?") public boolean cfgMessageBuyer = true; + + /** + * Whether a message should be broadcast when a claim is put up for sale, rent, or lease. + */ @ConfigField(name="RealEstate.Messaging.BroadcastSell", comment = "Should a message get broadcasted when a player puts a claim for sale/rent/lease?") public boolean cfgBroadcastSell = true; + + /** + * Whether offline owners/buyers should receive mail notifications. + */ @ConfigField(name="RealEstate.Messaging.MailOffline", comment = "Should offline owners/buyers receive mails (using the Essentials plugin) when they're offline?") public boolean cfgMailOffline = true; + // Default pricing and duration configurations + + /** + * The default price per block when selling a claim. + */ @ConfigField(name="RealEstate.Default.PricesPerBlock.Sell", comment = "The default price per block when selling a claim") public double cfgPriceSellPerBlock = 5.0; + + /** + * The default price per block when renting a claim. + */ @ConfigField(name="RealEstate.Default.PricesPerBlock.Rent", comment = "The default price per block when renting a claim") public double cfgPriceRentPerBlock = 2.0; + + /** + * The default price per block when leasing a claim. + */ @ConfigField(name="RealEstate.Default.PricesPerBlock.Lease", comment = "The default price per block when leasing a claim") public double cfgPriceLeasePerBlock = 2.0; + + /** + * The default price per block when auctioning a claim. + */ @ConfigField(name="RealEstate.Default.PricesPerBlock.Auction", comment = "The default price per block when auctioning a claim") public double cfgPriceAuctionPerBlock = 1.0; + + /** + * The default auction bid step when auctioning a claim. + */ @ConfigField(name="RealEstate.Default.Prices.AuctionBidStep", comment = "The default bid step when auctioning a claim") public double cfgPriceAuctionBidStep = 2.0; + /** + * The default duration of a rent period. + */ @ConfigField(name="RealEstate.Default.Duration.Rent", comment = "How long is a rent period by default") public String cfgRentTime = "7D"; + + /** + * The default duration of a lease period. + */ @ConfigField(name="RealEstate.Default.Duration.Lease", comment = "How long is a lease period by default") public String cfgLeaseTime = "7D"; + + /** + * The default duration of an auction period. + */ @ConfigField(name="RealEstate.Default.Duration.Auction", comment = "How long is an auction period by default") public String cfgAuctionTime = "7D"; + /** + * The default number of lease payments required before the buyer gains ownership. + */ @ConfigField(name="RealEstate.Default.LeasePaymentsCount", comment = "How many lease periods are required before the buyer gets the claim's ownership by default") public int cfgLeasePayments = 5; + /** + * The number of offers to display per page in the '/re list' command. + */ @ConfigField(name="RealEstate.Settings.PageSize", comment = "How many Real Estate offers should be shown per page using the '/re list' command") public int cfgPageSize = 8; + /** + * The language file to be used (found in the languages directory). + */ @ConfigField(name="RealEstate.Settings.MessagesFiles", comment="Language file to be used. See the languages directory for available files.") public String languageFile = "en-us.yml"; + /** + * The type of database to use: MySQL, YML, or SQLite. + */ @ConfigField(name="RealEstate.Settings.Database.DatabaseType", comment="Database type: MySQL, YML, or SQLite") public String databaseType = "YML"; + /** + * The SQLite database name. + */ @ConfigField(name="RealEstate.Settings.Database.SQLite.Database", comment="SQLite database name") public String sqliteDatabase = "RealEstate.db"; + /** + * The MySQL database host. + */ @ConfigField(name="RealEstate.Settings.Database.MySQL.Host", comment="MySQL database host") public String mysqlHost = "localhost"; + /** + * The MySQL database port. + */ @ConfigField(name="RealEstate.Settings.Database.MySQL.Port", comment="MySQL database port") public int mysqlPort = 3306; + /** + * The MySQL database name. + */ @ConfigField(name="RealEstate.Settings.Database.MySQL.Database", comment="MySQL database name") public String mysqlDatabase = "RealEstate"; + /** + * The MySQL database username. + */ @ConfigField(name="RealEstate.Settings.Database.MySQL.Username", comment="MySQL database username") public String mysqlUsername = ""; + /** + * The MySQL database password. + */ @ConfigField(name="RealEstate.Settings.Database.MySQL.Password", comment="MySQL database password") public String mysqlPassword = ""; + /** + * The MySQL table prefix. + */ @ConfigField(name="RealEstate.Settings.Database.MySQL.Prefix", comment="MySQL database table prefix") public String mysqlPrefix = "realestate_"; + /** + * Whether to use SSL for the MySQL connection. + */ @ConfigField(name="RealEstate.Settings.Database.MySQL.UseSSL", comment="MySQL database use SSL") public boolean mysqlUseSSL = false; + /** + * Whether the MySQL connection should automatically reconnect. + */ @ConfigField(name="RealEstate.Settings.Database.MySQL.AutoReconnect", comment="MySQL database auto reconnect") public boolean mysqlAutoReconnect = true; + /** + * Constructs a new configuration instance. + *

+ * Initializes the plugin description file from the RealEstate instance. + *

+ */ public Config() { this.pdf = RealEstate.instance.getDescription(); } + /** + * Converts a list of strings into a single string with each element separated by a semicolon. + * + * @param li the list of strings + * @return the concatenated string + */ public String getString(List li) { return String.join(";", li); } + /** + * Splits a string into a list using semicolon as the delimiter. + * + * @param str the string to split + * @return a list of strings + */ public List getList(String str) { return Arrays.asList(str.split(";")); } + /** + * Retrieves a list of strings from the provided YamlConfiguration at the specified path. + *

+ * Adds a default value if the path is not present and converts all entries to lowercase. + *

+ * + * @param config the YamlConfiguration instance + * @param path the configuration path + * @param defVal the default list of strings + * @return the list of strings from the configuration + */ List getConfigList(YamlConfiguration config, String path, List defVal) { config.addDefault(path, defVal); List ret = config.getStringList(path); @@ -164,6 +401,9 @@ List getConfigList(YamlConfiguration config, String path, List d return ret; } + /** + * Loads the configuration from the file specified by {@code configFilePath}. + */ @Override public void loadConfig() { this.loadConfig(this.configFilePath); diff --git a/src/me/EtienneDx/RealEstate/Messages.java b/src/me/EtienneDx/RealEstate/Messages.java index ee351ed..0643908 100644 --- a/src/me/EtienneDx/RealEstate/Messages.java +++ b/src/me/EtienneDx/RealEstate/Messages.java @@ -10,564 +10,1411 @@ import me.EtienneDx.AnnotationConfig.ConfigFile; import net.md_5.bungee.api.ChatColor; -@ConfigFile(header = "Use a YAML editor like NotepadPlusPlus to edit this file. \nAfter editing, back up your changes before reloading the server in case you made a syntax error. \nUse dollar signs ($) for formatting codes, which are documented here: http://minecraft.gamepedia.com/Formatting_codes.\n You can use {0}, {1} to include the different values indicated in the comments") -public class Messages extends AnnotationConfig -{ +/** + * The Messages class holds all configurable message templates for the RealEstate plugin. + *

+ * These messages use placeholders (e.g. {0}, {1}) that are replaced at runtime and + * support Minecraft color codes via dollar signs ($). The messages are loaded from a YAML + * configuration file, which can be edited with a YAML editor. + *

+ */ +@ConfigFile(header = "Use a YAML editor like NotepadPlusPlus to edit this file.\nAfter editing, back up your changes before reloading the server in case you made a syntax error.\nUse dollar signs ($) for formatting codes, which are documented here: http://minecraft.gamepedia.com/Formatting_codes.\nYou can use {0}, {1} to include the different values indicated in the comments") +public class Messages extends AnnotationConfig { + + /** + * The plugin description file. + */ public PluginDescriptionFile pdf; + + /** + * Constructs a new Messages instance and initializes the plugin description file. + */ + public Messages() { + this.pdf = RealEstate.instance.getDescription(); + } - @ConfigField(name="RealEstate.Keywords.Enabled", comment = "Keywords used within other messages but with a longer text at the end just because i need to test some stuff") + //===================================================================== + // Configurable Keywords and Messages + //===================================================================== + + /** + * Keyword indicating that a feature is enabled. + */ + @ConfigField(name = "RealEstate.Keywords.Enabled", comment = "Keywords used within other messages but with a longer text at the end just because i need to test some stuff") public String keywordEnabled = "enabled"; - @ConfigField(name="RealEstate.Keywords.Disabled") + /** + * Keyword indicating that a feature is disabled. + */ + @ConfigField(name = "RealEstate.Keywords.Disabled") public String keywordDisabled = "disabled"; - @ConfigField(name="RealEstate.Keywords.Claim") + /** + * Keyword for a claim. + */ + @ConfigField(name = "RealEstate.Keywords.Claim") public String keywordClaim = "claim"; - @ConfigField(name="RealEstate.Keywords.Subclaim") + /** + * Keyword for a subclaim. + */ + @ConfigField(name = "RealEstate.Keywords.Subclaim") public String keywordSubclaim = "subclaim"; - @ConfigField(name="RealEstate.Keywords.AdminClaimPrefix") + /** + * Prefix used for admin claims. + */ + @ConfigField(name = "RealEstate.Keywords.AdminClaimPrefix") public String keywordAdminClaimPrefix = "an admin"; - @ConfigField(name="RealEstate.Keywords.ClaimPrefix") + /** + * Prefix used for claims. + */ + @ConfigField(name = "RealEstate.Keywords.ClaimPrefix") public String keywordClaimPrefix = "a"; - @ConfigField(name="RealEstate.Keywords.TheServer") + /** + * Keyword representing the server. + */ + @ConfigField(name = "RealEstate.Keywords.TheServer") public String keywordTheServer = "The server"; - @ConfigField(name="RealEstate.NoTransactionFound") + /** + * Message displayed when no transaction is found. + */ + @ConfigField(name = "RealEstate.NoTransactionFound") public String msgNoTransactionFound = "$cNo transaction found!"; - @ConfigField(name="RealEstate.NoTransactionFoundHere") + /** + * Message displayed when no transaction is found at the player's location. + */ + @ConfigField(name = "RealEstate.NoTransactionFoundHere") public String msgNoTransactionFoundHere = "$cNo transaction found at your location!"; - @ConfigField(name="RealEstate.PageMustBePositive") + /** + * Message indicating that the page number must be a positive option. + */ + @ConfigField(name = "RealEstate.PageMustBePositive") public String msgPageMustBePositive = "$cPage must be a positive option"; - @ConfigField(name="RealEstate.PageNotExists") + /** + * Message displayed when the requested page does not exist. + */ + @ConfigField(name = "RealEstate.PageNotExists") public String msgPageNotExists = "$cThis page does not exist!"; - @ConfigField(name="RealEstate.RenewRentNow", comment = "0: enabled/disabled; 1: type of claim") + /** + * Message indicating that automatic rent renewal is now active or inactive. + *

+ * Placeholders: {0} - enabled/disabled; {1} - type of claim. + *

+ */ + @ConfigField(name = "RealEstate.RenewRentNow", comment = "0: enabled/disabled; 1: type of claim") public String msgRenewRentNow = "$bAutomatic renew is now $a{0} $bfor this {1}"; - @ConfigField(name="RealEstate.RenewRentCurrently", comment = "0: enabled/disabled; 1: type of claim") + /** + * Message indicating the current status of automatic rent renewal. + *

+ * Placeholders: {0} - enabled/disabled; {1} - type of claim. + *

+ */ + @ConfigField(name = "RealEstate.RenewRentCurrently", comment = "0: enabled/disabled; 1: type of claim") public String msgRenewRentCurrently = "$bAutomatic renew is currently $a{0} $bfor this {1}"; - - @ConfigField(name="RealEstate.Errors.OutOfClaim") + + /** + * Error message when a player is not standing within a claim. + */ + @ConfigField(name = "RealEstate.Errors.OutOfClaim") public String msgErrorOutOfClaim = "$cYou must stand inside of a claim to use this command!"; - - @ConfigField(name="RealEstate.Errors.PlayerOnlyCmd") + + /** + * Error message when the command is only for players. + */ + @ConfigField(name = "RealEstate.Errors.PlayerOnlyCmd") public String msgErrorPlayerOnly = "$cOnly Players can perform this command!"; - - @ConfigField(name="RealEstate.Errors.NoOngoingTransaction") + + /** + * Error message when there is no ongoing transaction for a claim. + */ + @ConfigField(name = "RealEstate.Errors.NoOngoingTransaction") public String msgErrorNoOngoingTransaction = "$cThis claim has no ongoing transactions!"; - - @ConfigField(name="RealEstate.Errors.NotRentNorLease") + + /** + * Error message when a claim is neither available for rent nor lease. + */ + @ConfigField(name = "RealEstate.Errors.NotRentNorLease") public String msgErrorNotRentNorLease = "$cThis claim is neither to rent or to lease!"; - - @ConfigField(name="RealEstate.Errors.AlreadyBought") + + /** + * Error message when a claim already has a buyer. + */ + @ConfigField(name = "RealEstate.Errors.AlreadyBought") public String msgErrorAlreadyBought = "$cThis claim already has a buyer!"; - - @ConfigField(name="RealEstate.Errors.NotPartOfTransaction") + + /** + * Error message when the player is not part of the current transaction. + */ + @ConfigField(name = "RealEstate.Errors.NotPartOfTransaction") public String msgErrorNotPartOfTransaction = "$cYou are not part of this transaction!"; - - @ConfigField(name="RealEstate.Errors.RentOnly") + + /** + * Error message when the command only applies to rented claims. + */ + @ConfigField(name = "RealEstate.Errors.RentOnly") public String msgErrorRentOnly = "$cThis command only applies to rented claims!"; - @ConfigField(name="RealEstate.Errors.AuctionOnly") + /** + * Error message when the command only applies to auctioned claims. + */ + @ConfigField(name = "RealEstate.Errors.AuctionOnly") public String msgErrorAuctionOnly = "$cThis command only applies to auctioned claims!"; - - @ConfigField(name="RealEstate.Errors.ValueGreaterThanZero") + + /** + * Error message when a numeric value is not greater than zero. + */ + @ConfigField(name = "RealEstate.Errors.ValueGreaterThanZero") public String msgErrorValueGreaterThanZero = "$cThe value must be greater than zero!"; - - @ConfigField(name="RealEstate.Errors.InvalidOption") + + /** + * Error message when an invalid option is provided. + */ + @ConfigField(name = "RealEstate.Errors.InvalidOption") public String msgErrorInvalidOption = "$cInvalid option provided!"; - @ConfigField(name="RealEstate.Errors.ClaimInTransaction.CantOwner") + /** + * Error message when the claim is involved in a transaction and cannot be modified. + */ + @ConfigField(name = "RealEstate.Errors.ClaimInTransaction.CantOwner") public String msgErrorClaimInTransactionCantOwner = "$cThis claim is currently involved in a transaction, you can't modify it!"; - @ConfigField(name="RealEstate.Errors.ClaimInTransaction.CantEdit") + /** + * Error message when editing is not permitted due to an ongoing transaction. + */ + @ConfigField(name = "RealEstate.Errors.ClaimInTransaction.CantEdit") public String msgErrorClaimInTransactionCantEdit = "$cThis claim is currently involved in a transaction, you can't edit it!"; - @ConfigField(name="RealEstate.Errors.ClaimInTransaction.CantAccess") + /** + * Error message when access is not permitted due to an ongoing transaction. + */ + @ConfigField(name = "RealEstate.Errors.ClaimInTransaction.CantAccess") public String msgErrorClaimInTransactionCantAccess = "$cThis claim is currently involved in a transaction, you can't access it!"; - @ConfigField(name="RealEstate.Errors.ClaimInTransaction.CantBuild") + /** + * Error message when building is not permitted on a claim in transaction. + */ + @ConfigField(name = "RealEstate.Errors.ClaimInTransaction.CantBuild") public String msgErrorClaimInTransactionCantBuild = "$cThis claim is currently involved in a transaction, you can't build on it!"; - @ConfigField(name="RealEstate.Errors.ClaimInTransaction.CantInventory") + /** + * Error message when container access is not allowed due to a transaction. + */ + @ConfigField(name = "RealEstate.Errors.ClaimInTransaction.CantInventory") public String msgErrorClaimInTransactionCantInventory = "$cThis claim is currently involved in a transaction, you can't access its containers!"; - @ConfigField(name="RealEstate.Errors.ClaimInTransaction.CantManage") + /** + * Error message when management actions are blocked by a transaction. + */ + @ConfigField(name = "RealEstate.Errors.ClaimInTransaction.CantManage") public String msgErrorClaimInTransactionCantManage = "$cThis claim is currently involved in a transaction, you can't manage it!"; - @ConfigField(name="RealEstate.Errors.ClaimInTransaction.Subclaim") + /** + * Error message when a subclaim is involved in a transaction. + */ + @ConfigField(name = "RealEstate.Errors.ClaimInTransaction.Subclaim") public String msgErrorSubclaimInTransaction = "$cA subclaim is currently involved in a transaction, you can't edit or manage the parent claim!"; - - @ConfigField(name="RealEstate.Errors.Command.Usage", comment = "0: command usage") + + /** + * Error message for invalid command usage. + *

+ * Placeholder: {0} - correct command usage. + *

+ */ + @ConfigField(name = "RealEstate.Errors.Command.Usage", comment = "0: command usage") public String msgErrorCommandUsage = "$cUsage: {0}"; - @ConfigField(name="RealEstate.Errors.BuyerOnly") + /** + * Error message when a command is restricted to the buyer only. + */ + @ConfigField(name = "RealEstate.Errors.BuyerOnly") public String msgErrorBuyerOnly = "$cOnly the buyer can perform this command!"; - @ConfigField(name="RealEstate.Errors.Unexpected") + /** + * Error message for unexpected errors. + */ + @ConfigField(name = "RealEstate.Errors.Unexpected") public String msgErrorUnexpected = "$cAn unexpected error has occured!"; - @ConfigField(name="RealEstate.Errors.InvalidNumber", comment = "0: number") + /** + * Error message for an invalid number. + *

+ * Placeholder: {0} - the invalid number. + *

+ */ + @ConfigField(name = "RealEstate.Errors.InvalidNumber", comment = "0: number") public String msgErrorInvalidNumber = "$c{0} is not a valid number!"; - @ConfigField(name="RealEstate.Errors.NegativeNumber", comment = "0: number") + /** + * Error message for a negative number. + *

+ * Placeholder: {0} - the negative number. + *

+ */ + @ConfigField(name = "RealEstate.Errors.NegativeNumber", comment = "0: number") public String msgErrorNegativeNumber = "$c{0} is a negative number!"; - @ConfigField(name="RealEstate.Errors.NegativePrice", comment = "0: price") + /** + * Error message for a negative price. + *

+ * Placeholder: {0} - the price. + *

+ */ + @ConfigField(name = "RealEstate.Errors.NegativePrice", comment = "0: price") public String msgErrorNegativePrice = "$cThe price must be greater than zero!"; - @ConfigField(name="RealEstate.Errors.NegativeBidStep", comment = "0: bid step") + /** + * Error message for a negative bid step. + *

+ * Placeholder: {0} - the bid step. + *

+ */ + @ConfigField(name = "RealEstate.Errors.NegativeBidStep", comment = "0: bid step") public String msgErrorNegativeBidStep = "$cThe bid step must be greater than zero!"; - @ConfigField(name="RealEstate.Errors.NonIntegerPrice", comment = "0: price") + /** + * Error message when the price is not an integer. + *

+ * Placeholder: {0} - the price. + *

+ */ + @ConfigField(name = "RealEstate.Errors.NonIntegerPrice", comment = "0: price") public String msgErrorNonIntegerPrice = "$cThe price must be an integer!"; - @ConfigField(name="RealEstate.Errors.InvalidDuration", comment = "0: duration, 1: example of duration format, 2: example, 3: example") + /** + * Error message for an invalid duration. + *

+ * Placeholders: {0} - the invalid duration, {1}, {2}, {3} - examples of valid formats. + *

+ */ + @ConfigField(name = "RealEstate.Errors.InvalidDuration", comment = "0: duration, 1: example of duration format, 2: example, 3: example") public String msgErrorInvalidDuration = "$c{0} is not a valid duration! Durations must be in the format $a{1}$c or $a{2}$c or $a{3}$c!"; - @ConfigField(name="RealEstate.Errors.NoMoneySelf") + /** + * Error message when the player doesn't have enough money. + */ + @ConfigField(name = "RealEstate.Errors.NoMoneySelf") public String msgErrorNoMoneySelf = "$cYou don't have enough money to make this transaction!"; - @ConfigField(name="RealEstate.Errors.NoMoneyOther", comment = "0: Other player") + /** + * Error message when another player doesn't have enough money. + *

+ * Placeholder: {0} - other player's name. + *

+ */ + @ConfigField(name = "RealEstate.Errors.NoMoneyOther", comment = "0: Other player") public String msgErrorNoMoneyOther = "$c{0} doesn't have enough money to make this transaction!"; - @ConfigField(name="RealEstate.Errors.NoWithdrawSelf") + /** + * Error message when money cannot be withdrawn from the player. + */ + @ConfigField(name = "RealEstate.Errors.NoWithdrawSelf") public String msgErrorNoWithdrawSelf = "$cCould not withdraw the money!"; - @ConfigField(name="RealEstate.Errors.NoWithdrawOther", comment = "0: Other player") + /** + * Error message when money cannot be withdrawn from another player. + *

+ * Placeholder: {0} - other player's name. + *

+ */ + @ConfigField(name = "RealEstate.Errors.NoWithdrawOther", comment = "0: Other player") public String msgErrorNoWithdrawOther = "$cCould not withdraw the money from {0}!"; - @ConfigField(name="RealEstate.Errors.NoDepositSelf", comment = "0: Other player") + /** + * Error message when money cannot be deposited to the player. + *

+ * Placeholder: {0} - the amount. + *

+ */ + @ConfigField(name = "RealEstate.Errors.NoDepositSelf", comment = "0: Other player") public String msgErrorNoDepositSelf = "$cCould not deposit the money to you, refunding {0}!"; - @ConfigField(name="RealEstate.Errors.NoDepositOther", comment = "0: Other player") + /** + * Error message when money cannot be deposited to another player. + *

+ * Placeholder: {0} - other player's name. + *

+ */ + @ConfigField(name = "RealEstate.Errors.NoDepositOther", comment = "0: Other player") public String msgErrorNoDepositOther = "$cCould not deposit the money to {0}, refunding you!"; - @ConfigField(name="RealEstate.Errors.CantCancelAlreadyLeased", comment = "0: claim type") + /** + * Error message when a leased claim cannot be cancelled. + *

+ * Placeholder: {0} - claim type. + *

+ */ + @ConfigField(name = "RealEstate.Errors.CantCancelAlreadyLeased", comment = "0: claim type") public String msgErrorCantCancelAlreadyLeased = "$cThis {0} is currently being leased, you can't cancel the transaction!"; - @ConfigField(name="RealEstate.Errors.CantCancelAlreadyRented", comment = "0: claim type") + /** + * Error message when a rented claim cannot be cancelled. + *

+ * Placeholder: {0} - claim type. + *

+ */ + @ConfigField(name = "RealEstate.Errors.CantCancelAlreadyRented", comment = "0: claim type") public String msgErrorCantCancelAlreadyRented = "$cThis {0} is currently being rented, you can't cancel the transaction!"; - @ConfigField(name="RealEstate.Errors.CantCancelAuction") + /** + * Error message when an auctioned claim cannot be cancelled. + */ + @ConfigField(name = "RealEstate.Errors.CantCancelAuction") public String msgErrorCantCancelAuction = "$cThis claim is currently being auctioned, you can't cancel the transaction!"; - @ConfigField(name="RealEstate.Errors.CouldntReimburseSelf", comment = "0: formatted price") + /** + * Error message when the player cannot be reimbursed. + *

+ * Placeholder: {0} - formatted price. + *

+ */ + @ConfigField(name = "RealEstate.Errors.CouldntReimburseSelf", comment = "0: formatted price") public String msgErrorCouldntReimburseSelf = "$cCould not reimburse you, refunding {0}!"; - @ConfigField(name="RealEstate.Errors.CouldntReimburseOther", comment = "0: formatted price") + /** + * Error message when another player cannot be reimbursed. + *

+ * Placeholder: {0} - formatted price. + *

+ */ + @ConfigField(name = "RealEstate.Errors.CouldntReimburseOther", comment = "0: formatted price") public String msgErrorCouldntReimburseOther = "$cCould not reimburse {0} to another player, the action has been cancelled!"; - @ConfigField(name="RealEstate.Errors.ContactAdmin") + /** + * Error message instructing the player to contact an admin. + */ + @ConfigField(name = "RealEstate.Errors.ContactAdmin") public String msgErrorContactAdmin = "$cAn unexpected error occured, please contact an admin to resolve this issue!"; - @ConfigField(name="RealEstate.Errors.AutoRenew.Disabled") + /** + * Error message when automatic renew is disabled. + */ + @ConfigField(name = "RealEstate.Errors.AutoRenew.Disabled") public String msgErrorAutoRenewDisabled = "$cAutomatic renew is disabled!"; - @ConfigField(name="RealEstate.Errors.ExitOffer.AlreadyExists") + /** + * Error message when an exit offer already exists. + */ + @ConfigField(name = "RealEstate.Errors.ExitOffer.AlreadyExists") public String msgErrorExitOfferAlreadyExists = "$cThere is already an exit proposition for this transaction!"; - @ConfigField(name="RealEstate.Errors.ExitOffer.NoBuyer") + /** + * Error message when there is no buyer engaged in the transaction. + */ + @ConfigField(name = "RealEstate.Errors.ExitOffer.NoBuyer") public String msgErrorExitOfferNoBuyer = "$cNo one is engaged by this transaction yet!"; - @ConfigField(name="RealEstate.Errors.ExitOffer.None") + /** + * Error message when there is no exit offer for the claim. + */ + @ConfigField(name = "RealEstate.Errors.ExitOffer.None") public String msgErrorExitOfferNone = "$cThere is currently no exit offer for this claim!"; - @ConfigField(name="RealEstate.Errors.ExitOffer.CantAcceptSelf") + /** + * Error message when the player tries to accept their own exit offer. + */ + @ConfigField(name = "RealEstate.Errors.ExitOffer.CantAcceptSelf") public String msgErrorExitOfferCantAcceptSelf = "$cYou can't accept your own exit offer!"; - @ConfigField(name="RealEstate.Errors.ExitOffer.CantRefuseSelf") + /** + * Error message when the player tries to refuse their own exit offer. + */ + @ConfigField(name = "RealEstate.Errors.ExitOffer.CantRefuseSelf") public String msgErrorExitOfferCantRefuseSelf = "$cYou can't refuse your own exit offer!"; - @ConfigField(name="RealEstate.Errors.ExitOffer.CantCancelOther") + /** + * Error message when someone other than the offer creator tries to cancel the exit offer. + */ + @ConfigField(name = "RealEstate.Errors.ExitOffer.CantCancelOther") public String msgErrorExitOfferCantCancelOther = "$cOnly the player who created this exit proposition may cancel it!"; - @ConfigField(name="RealEstate.Errors.Sign.NotInClaim") + /** + * Error message when the sign is not inside a claim. + */ + @ConfigField(name = "RealEstate.Errors.Sign.NotInClaim") public String msgErrorSignNotInClaim = "$cThe sign you placed is not inside a claim!"; - @ConfigField(name="RealEstate.Errors.Sign.OngoingTransaction") + /** + * Error message when the claim already has an ongoing transaction. + */ + @ConfigField(name = "RealEstate.Errors.Sign.OngoingTransaction") public String msgErrorSignOngoingTransaction = "$cThis claim already has an ongoing transaction!"; - @ConfigField(name="RealEstate.Errors.Sign.ParentOngoingTransaction") + /** + * Error message when the parent claim already has an ongoing transaction. + */ + @ConfigField(name = "RealEstate.Errors.Sign.ParentOngoingTransaction") public String msgErrorSignParentOngoingTransaction = "$cThis claim's parent already has an ongoing transaction!"; - @ConfigField(name="RealEstate.Errors.Sign.SubclaimOngoingTransaction") + /** + * Error message when a subclaim has an ongoing transaction. + */ + @ConfigField(name = "RealEstate.Errors.Sign.SubclaimOngoingTransaction") public String msgErrorSignSubclaimOngoingTransaction = "$cThis claim has subclaims with ongoing transactions!"; - @ConfigField(name="RealEstate.Errors.Sign.SellingDisabled") + /** + * Error message when selling is disabled. + */ + @ConfigField(name = "RealEstate.Errors.Sign.SellingDisabled") public String msgErrorSignSellingDisabled = "$cSelling is disabled!"; - @ConfigField(name="RealEstate.Errors.Sign.LeasingDisabled") + /** + * Error message when leasing is disabled. + */ + @ConfigField(name = "RealEstate.Errors.Sign.LeasingDisabled") public String msgErrorSignLeasingDisabled = "$cLeasing is disabled!"; - @ConfigField(name="RealEstate.Errors.Sign.RentingDisabled") + /** + * Error message when renting is disabled. + */ + @ConfigField(name = "RealEstate.Errors.Sign.RentingDisabled") public String msgErrorSignRentingDisabled = "$cRenting is disabled!"; - @ConfigField(name="RealEstate.Errors.Sign.AuctionDisabled") + /** + * Error message when auctioning is disabled. + */ + @ConfigField(name = "RealEstate.Errors.Sign.AuctionDisabled") public String msgErrorSignAuctionDisabled = "$cAuctioning is disabled!"; - @ConfigField(name="RealEstate.Errors.Sign.NoSellPermission", comment = "0: claim type") + /** + * Error message when the player lacks permission to sell a claim. + *

+ * Placeholder: {0} - claim type. + *

+ */ + @ConfigField(name = "RealEstate.Errors.Sign.NoSellPermission", comment = "0: claim type") public String msgErrorSignNoSellPermission = "$cYou don't have permission to sell this {0}!"; - @ConfigField(name="RealEstate.Errors.Sign.NoLeasePermission", comment = "0: claim type") + /** + * Error message when the player lacks permission to lease a claim. + *

+ * Placeholder: {0} - claim type. + *

+ */ + @ConfigField(name = "RealEstate.Errors.Sign.NoLeasePermission", comment = "0: claim type") public String msgErrorSignNoLeasePermission = "$cYou don't have permission to lease this {0}!"; - @ConfigField(name="RealEstate.Errors.Sign.NoRentPermission", comment = "0: claim type") + /** + * Error message when the player lacks permission to rent a claim. + *

+ * Placeholder: {0} - claim type. + *

+ */ + @ConfigField(name = "RealEstate.Errors.Sign.NoRentPermission", comment = "0: claim type") public String msgErrorSignNoRentPermission = "$cYou don't have permission to rent this {0}!"; - @ConfigField(name="RealEstate.Errors.Sign.NoAuctionPermission", comment = "0: claim type") + /** + * Error message when the player lacks permission to auction a claim. + *

+ * Placeholder: {0} - claim type. + *

+ */ + @ConfigField(name = "RealEstate.Errors.Sign.NoAuctionPermission", comment = "0: claim type") public String msgErrorSignNoAuctionPermission = "$cYou don't have permission to auction this {0}!"; - @ConfigField(name="RealEstate.Errors.Sign.NoAdminSellPermission", comment = "0: claim type") + /** + * Error message when the player lacks permission to sell an admin claim. + *

+ * Placeholder: {0} - claim type. + *

+ */ + @ConfigField(name = "RealEstate.Errors.Sign.NoAdminSellPermission", comment = "0: claim type") public String msgErrorSignNoAdminSellPermission = "$cYou don't have permission to sell this admin {0}!"; - @ConfigField(name="RealEstate.Errors.Sign.NoAdminLeasePermission", comment = "0: claim type") + /** + * Error message when the player lacks permission to lease an admin claim. + *

+ * Placeholder: {0} - claim type. + *

+ */ + @ConfigField(name = "RealEstate.Errors.Sign.NoAdminLeasePermission", comment = "0: claim type") public String msgErrorSignNoAdminLeasePermission = "$cYou don't have permission to lease this admin {0}!"; - @ConfigField(name="RealEstate.Errors.Sign.NoAdminRentPermission", comment = "0: claim type") + /** + * Error message when the player lacks permission to rent an admin claim. + *

+ * Placeholder: {0} - claim type. + *

+ */ + @ConfigField(name = "RealEstate.Errors.Sign.NoAdminRentPermission", comment = "0: claim type") public String msgErrorSignNoAdminRentPermission = "$cYou don't have permission to rent this admin {0}!"; - @ConfigField(name="RealEstate.Errors.Sign.NoAdminAuctionPermission", comment = "0: claim type") + /** + * Error message when the player lacks permission to auction an admin claim. + *

+ * Placeholder: {0} - claim type. + *

+ */ + @ConfigField(name = "RealEstate.Errors.Sign.NoAdminAuctionPermission", comment = "0: claim type") public String msgErrorSignNoAdminAuctionPermission = "$cYou don't have permission to auction this admin {0}!"; - @ConfigField(name="RealEstate.Errors.Sign.NotOwner", comment = "0: claim type") + /** + * Error message when the player attempts to modify a claim they do not own. + *

+ * Placeholder: {0} - claim type. + *

+ */ + @ConfigField(name = "RealEstate.Errors.Sign.NotOwner", comment = "0: claim type") public String msgErrorSignNotOwner = "$cYou can only sell/rent/lease {0} you own!"; - @ConfigField(name="RealEstate.Errors.Sign.NotAuthor") + /** + * Error message when the sign is destroyed by someone who is not the author. + */ + @ConfigField(name = "RealEstate.Errors.Sign.NotAuthor") public String msgErrorSignNotAuthor = "$cOnly the author of the sell/rent/lease sign is allowed to destroy it!"; - @ConfigField(name="RealEstate.Errors.Sign.NotAdmin") + /** + * Error message when the sign can only be destroyed by an admin. + */ + @ConfigField(name = "RealEstate.Errors.Sign.NotAdmin") public String msgErrorSignNotAdmin = "$cOnly an admin is allowed to destroy this sign!"; - @ConfigField(name="RealEstate.Errors.Sign.NoTransaction") + /** + * Error message when no transaction is associated with the sign. + */ + @ConfigField(name = "RealEstate.Errors.Sign.NoTransaction") public String msgErrorSignNoTransaction = "$cThis claim is no longer for rent, sell or lease, sorry..."; - @ConfigField(name="RealEstate.Errors.Claim.DoesNotExist") + /** + * Error message when the claim does not exist. + */ + @ConfigField(name = "RealEstate.Errors.Claim.DoesNotExist") public String msgErrorClaimDoesNotExist = "$cThis claim does not exist!"; - @ConfigField(name="RealEstate.Errors.Claim.DoesNotExistAuction") + /** + * Error message when an auctioned claim does not exist. + */ + @ConfigField(name = "RealEstate.Errors.Claim.DoesNotExistAuction") public String msgErrorClaimDoesNotExistAuction = "$cThis auctioned claim does not exist!"; - @ConfigField(name="RealEstate.Errors.Claim.AlreadyOwner", comment = "0: claim type") + /** + * Error message when the player is already the owner of the claim. + *

+ * Placeholder: {0} - claim type. + *

+ */ + @ConfigField(name = "RealEstate.Errors.Claim.AlreadyOwner", comment = "0: claim type") public String msgErrorClaimAlreadyOwner = "$cYou are already the owner of this {0}!"; - @ConfigField(name="RealEstate.Errors.Claim.NotSoldByOwner", comment = "0: claim type") + /** + * Error message when the claim is not sold by its owner. + *

+ * Placeholder: {0} - claim type. + *

+ */ + @ConfigField(name = "RealEstate.Errors.Claim.NotSoldByOwner", comment = "0: claim type") public String msgErrorClaimNotSoldByOwner = "$cThis {0} is not sold by its owner!"; - @ConfigField(name="RealEstate.Errors.Claim.NotLeasedByOwner", comment = "0: claim type") + /** + * Error message when the claim is not leased by its owner. + *

+ * Placeholder: {0} - claim type. + *

+ */ + @ConfigField(name = "RealEstate.Errors.Claim.NotLeasedByOwner", comment = "0: claim type") public String msgErrorClaimNotLeasedByOwner = "$cThis {0} is not leased by its owner!"; - @ConfigField(name="RealEstate.Errors.Claim.NotRentedByOwner", comment = "0: claim type") + /** + * Error message when the claim is not rented by its owner. + *

+ * Placeholder: {0} - claim type. + *

+ */ + @ConfigField(name = "RealEstate.Errors.Claim.NotRentedByOwner", comment = "0: claim type") public String msgErrorClaimNotRentedByOwner = "$cThis {0} is not rented by its owner!"; - @ConfigField(name="RealEstate.Errors.Claim.NotAuctionedByOwner", comment = "0: claim type") + /** + * Error message when the claim is not auctioned by its owner. + *

+ * Placeholder: {0} - claim type. + *

+ */ + @ConfigField(name = "RealEstate.Errors.Claim.NotAuctionedByOwner", comment = "0: claim type") public String msgErrorClaimNotAuctionedByOwner = "$cThis {0} is not auctioned by its owner!"; - @ConfigField(name="RealEstate.Errors.Claim.NoBuyPermission", comment = "0: claim type") + /** + * Error message when the player does not have permission to buy a claim. + *

+ * Placeholder: {0} - claim type. + *

+ */ + @ConfigField(name = "RealEstate.Errors.Claim.NoBuyPermission", comment = "0: claim type") public String msgErrorClaimNoBuyPermission = "$cYou don't have permission to buy this {0}!"; - @ConfigField(name="RealEstate.Errors.Claim.NoLeasePermission", comment = "0: claim type") + /** + * Error message when the player does not have permission to lease a claim. + *

+ * Placeholder: {0} - claim type. + *

+ */ + @ConfigField(name = "RealEstate.Errors.Claim.NoLeasePermission", comment = "0: claim type") public String msgErrorClaimNoLeasePermission = "$cYou don't have permission to lease this {0}!"; - @ConfigField(name="RealEstate.Errors.Claim.NoRentPermission", comment = "0: claim type") + /** + * Error message when the player does not have permission to rent a claim. + *

+ * Placeholder: {0} - claim type. + *

+ */ + @ConfigField(name = "RealEstate.Errors.Claim.NoRentPermission", comment = "0: claim type") public String msgErrorClaimNoRentPermission = "$cYou don't have permission to rent this {0}!"; - @ConfigField(name="RealEstate.Errors.Claim.NoAuctionPermission", comment = "0: claim type") + /** + * Error message when the player does not have permission to auction a claim. + *

+ * Placeholder: {0} - claim type. + *

+ */ + @ConfigField(name = "RealEstate.Errors.Claim.NoAuctionPermission", comment = "0: claim type") public String msgErrorClaimNoAuctionPermission = "$cYou don't have permission to auction this {0}!"; - @ConfigField(name="RealEstate.Errors.Claim.AlreadyLeased", comment = "0: claim type") + /** + * Error message when the claim is already leased. + *

+ * Placeholder: {0} - claim type. + *

+ */ + @ConfigField(name = "RealEstate.Errors.Claim.AlreadyLeased", comment = "0: claim type") public String msgErrorClaimAlreadyLeased = "$cThis {0} is already leased!"; - @ConfigField(name="RealEstate.Errors.Claim.AlreadyRented", comment = "0: claim type") + /** + * Error message when the claim is already rented. + *

+ * Placeholder: {0} - claim type. + *

+ */ + @ConfigField(name = "RealEstate.Errors.Claim.AlreadyRented", comment = "0: claim type") public String msgErrorClaimAlreadyRented = "$cThis {0} is already rented!"; - @ConfigField(name="RealEstate.Errors.Claim.AlreadyHighestBidder", comment = "0: claim type") + /** + * Error message when the player is already the highest bidder. + *

+ * Placeholder: {0} - claim type. + *

+ */ + @ConfigField(name = "RealEstate.Errors.Claim.AlreadyHighestBidder", comment = "0: claim type") public String msgErrorClaimAlreadyHighestBidder = "$cYou are already the highest bidder of this {0}!"; - @ConfigField(name="RealEstate.Errors.Claim.NoInfoPermission") + /** + * Error message when the player does not have permission to view claim information. + */ + @ConfigField(name = "RealEstate.Errors.Claim.NoInfoPermission") public String msgErrorClaimNoInfoPermission = "$cYou don't have permission to view this real estate informations!"; - @ConfigField(name="RealEstate.Errors.Claim.NoClaimBlocks", comment = "0: area; 1: claim blocks remaining; 2: missing claim blocks") + /** + * Error message when the player does not have enough claim blocks. + *

+ * Placeholders: {0} - required area, {1} - remaining claim blocks, {2} - missing claim blocks. + *

+ */ + @ConfigField(name = "RealEstate.Errors.Claim.NoClaimBlocks", comment = "0: area; 1: claim blocks remaining; 2: missing claim blocks") public String msgErrorClaimNoClaimBlocks = "$cYou don't have enough claim blocks! You need $a{2}$c more claim blocks to claim this area. The claim requires $a{0}$c claim blocks, you only have $a{1}$c claim blocks left."; - @ConfigField(name="RealEstate.Errors.Auction.CouldntPayOwner") + /** + * Error message when the auction payment to the owner fails. + */ + @ConfigField(name = "RealEstate.Errors.Auction.CouldntPayOwner") public String msgErrorAuctionCouldntPayOwner = "$cCouldn't pay the owner of this auction! The auction is being cancelled."; - @ConfigField(name="RealEstate.Errors.Auction.CouldntReceiveOwner") + /** + * Error message when the auction payment from the owner fails. + */ + @ConfigField(name = "RealEstate.Errors.Auction.CouldntReceiveOwner") public String msgErrorAuctionCouldntReceiveOwner = "$cCouldn't receive the payment of this auction! The auction is being cancelled."; - @ConfigField(name="RealEstate.Info.ExitOffer.None") + /** + * Message indicating that no exit offer is available for the claim. + */ + @ConfigField(name = "RealEstate.Info.ExitOffer.None") public String msgInfoExitOfferNone = "$bThere is currently no exit offer for this claim!"; - @ConfigField(name="RealEstate.Info.ExitOffer.MadeByStatus", comment = "0: formatted price") + /** + * Message indicating the status of an exit offer created by the player. + *

+ * Placeholder: {0} - formatted price. + *

+ */ + @ConfigField(name = "RealEstate.Info.ExitOffer.MadeByStatus", comment = "0: formatted price") public String msgInfoExitOfferMadeByStatus = "$bYou offered to exit the contract for $a{0}$b, but your offer hasn't been accepted or denied yet..."; - @ConfigField(name="RealEstate.Info.ExitOffer.MadeToStatus", comment = "0: player who made the offer; 1: formatted price") + /** + * Message indicating the status of an exit offer made to the player. + *

+ * Placeholders: {0} - player who made the offer; {1} - formatted price. + *

+ */ + @ConfigField(name = "RealEstate.Info.ExitOffer.MadeToStatus", comment = "0: player who made the offer; 1: formatted price") public String msgInfoExitOfferMadeToStatus = "$a{0} $boffered to exit the contract for $a{1}"; - @ConfigField(name="RealEstate.Info.ExitOffer.Cancel", comment = "0: cancel command") + /** + * Message instructing the player on how to cancel their exit offer. + *

+ * Placeholder: {0} - cancel command. + *

+ */ + @ConfigField(name = "RealEstate.Info.ExitOffer.Cancel", comment = "0: cancel command") public String msgInfoExitOfferCancel = "$bTo cancel your offer, use $d{0}"; - @ConfigField(name="RealEstate.Info.ExitOffer.Accept", comment = "0: accept command") + /** + * Message instructing the player on how to accept an exit offer. + *

+ * Placeholder: {0} - accept command. + *

+ */ + @ConfigField(name = "RealEstate.Info.ExitOffer.Accept", comment = "0: accept command") public String msgInfoExitOfferAccept = "$bTo accept this offer, use $d{0}"; - @ConfigField(name="RealEstate.Info.ExitOffer.Reject", comment = "0: reject command") + /** + * Message instructing the player on how to reject an exit offer. + *

+ * Placeholder: {0} - reject command. + *

+ */ + @ConfigField(name = "RealEstate.Info.ExitOffer.Reject", comment = "0: reject command") public String msgInfoExitOfferReject = "$bTo reject this offer, use $d{0}"; - @ConfigField(name="RealEstate.Info.ExitOffer.CreatedBySelf", comment = "0: formatted price") + /** + * Message confirming that the exit offer was created by the player. + *

+ * Placeholder: {0} - formatted price. + *

+ */ + @ConfigField(name = "RealEstate.Info.ExitOffer.CreatedBySelf", comment = "0: formatted price") public String msgInfoExitOfferCreatedBySelf = "$bThe offer has been successfully created for $a{0}"; - @ConfigField(name="RealEstate.Info.ExitOffer.CreatedByOther", comment = "0: player name, 1: claim type, 2: formatted price, 3: claim location") + /** + * Broadcast message when another player creates an exit offer. + *

+ * Placeholders: {0} - player name; {1} - claim type; {2} - formatted price; {3} - claim location. + *

+ */ + @ConfigField(name = "RealEstate.Info.ExitOffer.CreatedByOther", comment = "0: player name, 1: claim type, 2: formatted price, 3: claim location") public String msgInfoExitOfferCreatedByOther = "$a{0} $bhas created an offer to exit the transaction for the {1} at $a{3} $bfor $a{2}"; - @ConfigField(name="RealEstate.Info.ExitOffer.AcceptedBySelf", comment = "0: claim type, 1:formatted price") + /** + * Message confirming that the exit offer was accepted by the player. + *

+ * Placeholders: {0} - claim type; {1} - formatted price. + *

+ */ + @ConfigField(name = "RealEstate.Info.ExitOffer.AcceptedBySelf", comment = "0: claim type, 1:formatted price") public String msgInfoExitOfferAcceptedBySelf = "$bThe {0} is no longer rented or leased, you have been charged $a{1}"; - @ConfigField(name="RealEstate.Info.ExitOffer.AcceptedByOther", comment = "0: player name, 1: claim type, 2: formatted price, 3: claim location") + /** + * Broadcast message when another player accepts the exit offer. + *

+ * Placeholders: {0} - player name; {1} - claim type; {2} - formatted price; {3} - claim location. + *

+ */ + @ConfigField(name = "RealEstate.Info.ExitOffer.AcceptedByOther", comment = "0: player name, 1: claim type, 2: formatted price, 3: claim location") public String msgInfoExitOfferAcceptedByOther = "$a{0} $bhas accepted the offer to exit the transaction for the {1} at $a{3} $bfor $a{2}. It is no longer leased or rented."; - @ConfigField(name="RealEstate.Info.ExitOffer.RejectedBySelf") + /** + * Message indicating that the exit offer has been rejected. + */ + @ConfigField(name = "RealEstate.Info.ExitOffer.RejectedBySelf") public String msgInfoExitOfferRejectedBySelf = "$bThe exit offer has been refused."; - @ConfigField(name="RealEstate.Info.ExitOffer.RejectedByOther", comment = "0: player name, 1: claim type, 2: claim location") + /** + * Broadcast message when another player rejects the exit offer. + *

+ * Placeholders: {0} - player name; {1} - claim type; {2} - claim location. + *

+ */ + @ConfigField(name = "RealEstate.Info.ExitOffer.RejectedByOther", comment = "0: player name, 1: claim type, 2: claim location") public String msgInfoExitOfferRejectedByOther = "$a{0} $bhas refused the offer to exit the transaction for the {1} at $a{2}"; - @ConfigField(name="RealEstate.Info.ExitOffer.CancelledBySelf") + /** + * Message confirming that the exit offer has been cancelled by the player. + */ + @ConfigField(name = "RealEstate.Info.ExitOffer.CancelledBySelf") public String msgInfoExitOfferCancelledBySelf = "$bThe exit offer has been cancelled."; - @ConfigField(name="RealEstate.Info.ExitOffer.CancelledByOther", comment = "0: player name, 1: claim type, 2: claim location") + /** + * Broadcast message when another player cancels the exit offer. + *

+ * Placeholders: {0} - player name; {1} - claim type; {2} - claim location. + *

+ */ + @ConfigField(name = "RealEstate.Info.ExitOffer.CancelledByOther", comment = "0: player name, 1: claim type, 2: claim location") public String msgInfoExitOfferCancelledByOther = "$a{0} $bhas cancelled the offer to exit the transaction for the {1} at $a{2}"; - @ConfigField(name="RealEstate.Info.Claim.OwnerSold", comment = "0: buyer name, 1: claim type, 2: formatted price, 3: claim location") + /** + * Broadcast message when a claim is bought by a new owner. + *

+ * Placeholders: {0} - buyer name; {1} - claim type; {2} - formatted price; {3} - claim location. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.OwnerSold", comment = "0: buyer name, 1: claim type, 2: formatted price, 3: claim location") public String msgInfoClaimOwnerSold = "$a{0} $bhas bought the {1} at $a{3} $bfor $a{2}"; - @ConfigField(name="RealEstate.Info.Claim.OwnerLeaseStarted", comment = "0: buyer name, 1: claim type, 2: formatted price, 3: claim location, 4: payments left") + /** + * Broadcast message when a claim lease has started. + *

+ * Placeholders: {0} - buyer name; {1} - claim type; {2} - formatted price; {3} - claim location; {4} - payments left. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.OwnerLeaseStarted", comment = "0: buyer name, 1: claim type, 2: formatted price, 3: claim location, 4: payments left") public String msgInfoClaimOwnerLeaseStarted = "$a{0} $bhas leased the {1} at $a{3} $bfor $a{2} with $a{4} $bpayments left"; - @ConfigField(name="RealEstate.Info.Claim.OwnerRented", comment = "0: buyer name, 1: claim type, 2: formatted price, 3: claim location") + /** + * Broadcast message when a claim is rented. + *

+ * Placeholders: {0} - buyer name; {1} - claim type; {2} - formatted price; {3} - claim location. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.OwnerRented", comment = "0: buyer name, 1: claim type, 2: formatted price, 3: claim location") public String msgInfoClaimOwnerRented = "$a{0} $bhas rented the {1} at $a{3} $bfor $a{2}"; - @ConfigField(name="RealEstate.Info.Claim.BuyerBought", comment = "0: claim type, 1: formatted price") + /** + * Message for when a buyer successfully purchases a claim. + *

+ * Placeholders: {0} - claim type; {1} - formatted price. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.BuyerBought", comment = "0: claim type, 1: formatted price") public String msgInfoClaimBuyerSold = "$bYou have bought the {0} for $a{1}"; - @ConfigField(name="RealEstate.Info.Claim.BuyerLeaseStarted", comment = "0: claim type, 1: formatted price, 2: payments left") + /** + * Message for when a buyer starts leasing a claim. + *

+ * Placeholders: {0} - claim type; {1} - formatted price; {2} - payments left. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.BuyerLeaseStarted", comment = "0: claim type, 1: formatted price, 2: payments left") public String msgInfoClaimBuyerLeaseStarted = "$bYou have leased the {0} for $a{1} with $a{2} $bpayments left"; - @ConfigField(name="RealEstate.Info.Claim.BuyerRented", comment = "0: claim type, 1: formatted price") + /** + * Message for when a buyer rents a claim. + *

+ * Placeholders: {0} - claim type; {1} - formatted price. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.BuyerRented", comment = "0: claim type, 1: formatted price") public String msgInfoClaimBuyerRented = "$bYou have rented the {0} for $a{1}"; - @ConfigField(name="RealEstate.Info.Claim.Info.Lease.Header") + /** + * Header for the lease information display. + */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Lease.Header") public String msgInfoClaimInfoLeaseHeader = "$9-----= $f[$6RealEstate Lease Info$f]$9 =-----"; - @ConfigField(name="RealEstate.Info.Claim.Info.Lease.GeneralNoBuyer", comment = "0: claim type, 1: payments left, 2: formatted price, 3: frequency") + /** + * General lease information message when no buyer is present. + *

+ * Placeholders: {0} - claim type; {1} - payments left; {2} - formatted price; {3} - frequency. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Lease.GeneralNoBuyer", comment = "0: claim type, 1: payments left, 2: formatted price, 3: frequency") public String msgInfoClaimInfoGeneralLeaseNoBuyer = "$bThis {0} is for lease for $a{1} $bpayments of $a{2} each. Payments are due every $a{3}"; - @ConfigField(name="RealEstate.Info.Claim.Info.Lease.GeneralBuyer", comment = "0: claim type, 1: buyer name, 2: formatted price, 3: payments left, 4: next payment due, 5: frequency") + /** + * General lease information message when a buyer is present. + *

+ * Placeholders: {0} - claim type; {1} - buyer name; {2} - formatted price; {3} - payments left; {4} - time until next payment; {5} - frequency. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Lease.GeneralBuyer", comment = "0: claim type, 1: buyer name, 2: formatted price, 3: payments left, 4: next payment due, 5: frequency") public String msgInfoClaimInfoGeneralLeaseBuyer = "$bThis {0} is currently leased by $a{1}$b for $a{2}$b. There is $a{3} $bpayments left. Next payment is in $a{4}$b. Payments are due every $a{5}"; - @ConfigField(name="RealEstate.Info.Claim.Info.Lease.Oneline", comment = "0: claim area, 1: location, 2: payments left, 3: period, 4: formatted price") + /** + * One-line lease information display. + *

+ * Placeholders: {0} - claim area; {1} - location; {2} - payments left; {3} - period; {4} - formatted price. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Lease.Oneline", comment = "0: claim area, 1: location, 2: payments left, 3: period, 4: formatted price") public String msgInfoClaimInfoLeaseOneline = "$2{0} $bblocks to $2Lease $bat $2{1} $bfor $a{2} periods of $a{3}$b, each period costs $a{4}"; - @ConfigField(name="RealEstate.Info.Claim.Info.Lease.PaymentBuyer", comment = "0: claim type, 1: location, 2: formatted price, 3: payments left") + /** + * Lease payment information message for the buyer. + *

+ * Placeholders: {0} - claim type; {1} - location; {2} - formatted price; {3} - payments left. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Lease.PaymentBuyer", comment = "0: claim type, 1: location, 2: formatted price, 3: payments left") public String msgInfoClaimInfoLeasePaymentBuyer = "$bPaid lease for the {0} at $a{1} $bfor $a{2}$b. There are $a{3} $bpayments left."; - @ConfigField(name="RealEstate.Info.Claim.Info.Lease.PaymentOwner", comment = "0: player name, 1: claim type, 2: location, 3: formatted price, 4: payments left") + /** + * Lease payment information message for the owner. + *

+ * Placeholders: {0} - buyer name; {1} - claim type; {2} - location; {3} - formatted price; {4} - payments left. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Lease.PaymentOwner", comment = "0: player name, 1: claim type, 2: location, 3: formatted price, 4: payments left") public String msgInfoClaimInfoLeasePaymentOwner = "$a{0} $bpaid lease for the {1} at $a{2} $bfor $a{3}$b. There are $a{4} $bpayments left."; - @ConfigField(name="RealEstate.Info.Claim.Info.Lease.PaymentBuyerFinal", comment = "0: claim type, 1: location, 2: formatted price") + /** + * Lease payment message for the buyer when it is the final payment. + *

+ * Placeholders: {0} - claim type; {1} - location; {2} - formatted price. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Lease.PaymentBuyerFinal", comment = "0: claim type, 1: location, 2: formatted price") public String msgInfoClaimInfoLeasePaymentBuyerFinal = "$bPaid final lease for the {0} at $a{1} $bfor $a{2}$b. The {0} is now your property."; - @ConfigField(name="RealEstate.Info.Claim.Info.Lease.PaymentOwnerFinal", comment = "0: player name, 1: claim type, 2: location, 3: formatted price") + /** + * Lease payment message for the owner when it is the final payment. + *

+ * Placeholders: {0} - buyer name; {1} - claim type; {2} - location; {3} - formatted price. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Lease.PaymentOwnerFinal", comment = "0: player name, 1: claim type, 2: location, 3: formatted price") public String msgInfoClaimInfoLeasePaymentOwnerFinal = "$a{0} $bpaid final lease for the {1} at $a{2} $bfor $a{3}$b. The {1} is now $a{0}$b's property."; + /** + * Lease payment cancellation message for the buyer. + *

+ * Placeholders: {0} - claim type; {1} - location; {2} - formatted price. + *

+ */ @ConfigField(name="RealEstate.Info.Claim.Info.Lease.PaymentBuyerCancelled", comment = "0: claim type, 1: location, 2: formatted price") public String msgInfoClaimInfoLeasePaymentBuyerCancelled = "$bCouldn't pay the lease for the {0} at $a{1} $bfor $a{2}$b. The lease has been cancelled."; - + + /** + * Lease payment cancellation message for the owner. + *

+ * Placeholders: {0} - player name; {1} - claim type; {2} - location; {3} - formatted price. + *

+ */ @ConfigField(name="RealEstate.Info.Claim.Info.Lease.PaymentOwnerCancelled", comment = "0: player name, 1: claim type, 2: location, 3: formatted price") public String msgInfoClaimInfoLeasePaymentOwnerCancelled = "$a{0} $bcouldn't pay the lease for the {1} at $a{2} $bfor $a{3}$b. The lease has been cancelled."; - - @ConfigField(name="RealEstate.Info.Claim.Info.Rent.Header") + + /** + * Rent information header. + */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Rent.Header") public String msgInfoClaimInfoRentHeader = "$9-----= $f[$6RealEstate Rent Info$f]$9 =-----"; - @ConfigField(name="RealEstate.Info.Claim.Info.Rent.GeneralNoBuyer", comment = "0: claim type, 1: formatted price, 2: duration") + /** + * General rent information message when no buyer is present. + *

+ * Placeholders: {0} - claim type; {1} - formatted price; {2} - duration. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Rent.GeneralNoBuyer", comment = "0: claim type, 1: formatted price, 2: duration") public String msgInfoClaimInfoGeneralRentNoBuyer = "$bThis {0} is for rent for $a{1}$b per $a{2}$b."; - @ConfigField(name="RealEstate.Info.Claim.Info.Rent.GeneralBuyer", comment = "0: claim type, 1: buyer name, 2: formatted price, 3: time left in current period, 4: duration of a period") + /** + * General rent information message when a buyer is present. + *

+ * Placeholders: {0} - claim type; {1} - buyer name; {2} - formatted price; {3} - time remaining; {4} - period duration. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Rent.GeneralBuyer", comment = "0: claim type, 1: buyer name, 2: formatted price, 3: time left in current period, 4: duration of a period") public String msgInfoClaimInfoGeneralRentBuyer = "$bThis {0} is currently rented by $a{1}$b for $a{2}$b. The {0} is rented for another $a{3}$b. The rent period is $a{4}"; - @ConfigField(name="RealEstate.Info.Claim.Info.Rent.AutoRenew", comment = "0: enabled / disabled") + /** + * Message indicating the current automatic renew status for rent. + *

+ * Placeholder: {0} - enabled/disabled. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Rent.AutoRenew", comment = "0: enabled / disabled") public String msgInfoClaimInfoRentAutoRenew = "$bAutomatic renew is currently $a{0}$b."; - @ConfigField(name="RealEstate.Info.Claim.Info.Rent.Oneline", comment = "0: claim area, 1: location, 2: formatted price, 3: duration") + /** + * One-line rent information message. + *

+ * Placeholders: {0} - claim area; {1} - location; {2} - formatted price; {3} - duration. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Rent.Oneline", comment = "0: claim area, 1: location, 2: formatted price, 3: duration") public String msgInfoClaimInfoRentOneline = "$2{0} $bblocks to $2Rent $bat $2{1} $bfor $a{2}$b per $a{3}"; - @ConfigField(name="RealEstate.Info.Claim.Info.Rent.PaymentBuyer", comment = "0: claim type, 1: location, 2: formatted price") + /** + * Rent payment information message for the buyer. + *

+ * Placeholders: {0} - claim type; {1} - location; {2} - formatted price. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Rent.PaymentBuyer", comment = "0: claim type, 1: location, 2: formatted price") public String msgInfoClaimInfoRentPaymentBuyer = "$bPaid rent for the {0} at $a{1} $bfor $a{2}$b."; - @ConfigField(name="RealEstate.Info.Claim.Info.Rent.PaymentOwner", comment = "0: player name, 1: claim type, 2: location, 3: formatted price") + /** + * Rent payment information message for the owner. + *

+ * Placeholders: {0} - buyer name; {1} - claim type; {2} - location; {3} - formatted price. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Rent.PaymentOwner", comment = "0: player name, 1: claim type, 2: location, 3: formatted price") public String msgInfoClaimInfoRentPaymentOwner = "$a{0} $bpaid rent for the {1} at $a{2} $bfor $a{3}$b."; - @ConfigField(name="RealEstate.Info.Claim.Info.Rent.PaymentBuyerCancelled", comment = "0: claim type, 1: location, 2: formatted price") + /** + * Rent payment cancellation message for the buyer. + *

+ * Placeholders: {0} - claim type; {1} - location; {2} - formatted price. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Rent.PaymentBuyerCancelled", comment = "0: claim type, 1: location, 2: formatted price") public String msgInfoClaimInfoRentPaymentBuyerCancelled = "$bCouldn't pay the rent for the {0} at $a{1} $bfor $a{2}$b. The rent has been cancelled."; - @ConfigField(name="RealEstate.Info.Claim.Info.Rent.PaymentOwnerCancelled", comment = "0: player name, 1: claim type, 2: location, 3: formatted price") + /** + * Rent payment cancellation message for the owner. + *

+ * Placeholders: {0} - buyer name; {1} - claim type; {2} - location; {3} - formatted price. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Rent.PaymentOwnerCancelled", comment = "0: player name, 1: claim type, 2: location, 3: formatted price") public String msgInfoClaimInfoRentPaymentOwnerCancelled = "$a{0} $bcouldn't pay the rent for the {1} at $a{2} $bfor $a{3}$b. The rent has been cancelled."; - @ConfigField(name="RealEstate.Info.Claim.Info.Rent.RentCancelled", comment = "0: claim type, 1: location") + /** + * Rent cancellation message. + *

+ * Placeholders: {0} - claim type; {1} - location. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Rent.RentCancelled", comment = "0: claim type, 1: location") public String msgInfoClaimInfoRentCancelled = "$bThe rent for the {0} at $a{1} $bis now over, your access has been revoked."; - @ConfigField(name="RealEstate.Info.Claim.Info.Sell.Header") + /** + * Header for the sale information display. + */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Sell.Header") public String msgInfoClaimInfoSellHeader = "$9-----= $f[$6RealEstate Sale Info$f]$9 =-----"; - @ConfigField(name="RealEstate.Info.Claim.Info.Sell.General", comment = "0: claim type, 1: formatted price") + /** + * General sale information message. + *

+ * Placeholders: {0} - claim type; {1} - formatted price. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Sell.General", comment = "0: claim type, 1: formatted price") public String msgInfoClaimInfoSellGeneral = "$bThis {0} is for sale for $a{1}"; - @ConfigField(name="RealEstate.Info.Claim.Info.Sell.Oneline", comment = "0: claim area, 1: location, 2: formatted price") + /** + * One-line sale information message. + *

+ * Placeholders: {0} - claim area; {1} - location; {2} - formatted price. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Sell.Oneline", comment = "0: claim area, 1: location, 2: formatted price") public String msgInfoClaimInfoSellOneline = "$2{0} $bblocks to $2Sell $bat $2{1} $bfor $a{2}"; - @ConfigField(name="RealEstate.Info.Claim.Info.Auction.Header") + /** + * Header for the auction information display. + */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Auction.Header") public String msgInfoClaimInfoAuctionHeader = "$9-----= $f[$6RealEstate Auction Info$f]$9 =-----"; - @ConfigField(name="RealEstate.Info.Claim.Info.Auction.NoBidder", comment = "0: claim type, 1: formatted price") + /** + * Auction information message when no bidder is present. + *

+ * Placeholders: {0} - claim type; {1} - formatted price. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Auction.NoBidder", comment = "0: claim type, 1: formatted price") public String msgInfoClaimInfoAuctionNoBidder = "$bThis {0} is currently being auctioned for $a{1}$b."; - @ConfigField(name="RealEstate.Info.Claim.Info.Auction.HighestBidder", comment = "0: claim type, 1: bidder name, 2: formatted price") + /** + * Auction information message showing the highest bidder. + *

+ * Placeholders: {0} - claim type; {1} - bidder name; {2} - formatted price. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Auction.HighestBidder", comment = "0: claim type, 1: bidder name, 2: formatted price") public String msgInfoClaimInfoAuctionHighestBidder = "$bThis {0} is currently being auctioned. The highest bidder is $a{1}$b for $a{2}$b."; - @ConfigField(name="RealEstate.Info.Claim.Info.Auction.TimeRemaining", comment = "0: time remaining") + /** + * Auction information message displaying the remaining time. + *

+ * Placeholder: {0} - formatted time remaining. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Auction.TimeRemaining", comment = "0: time remaining") public String msgInfoClaimInfoAuctionTimeRemaining = "$bThe auction will end in $a{0}$b."; - @ConfigField(name="RealEstate.Info.Claim.Info.Auction.BidStep", comment = "0: Bid Step") + /** + * Auction information message displaying the bid step. + *

+ * Placeholder: {0} - bid step. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Auction.BidStep", comment = "0: Bid Step") public String msgInfoClaimInfoAuctionBidStep = "$bThe bid step is $a{0}$b."; - @ConfigField(name="RealEstate.Info.Claim.Info.Auction.Oneline", comment = "0: claim area, 1: location, 2: formatted price, 3: time remaining, 4: bid step") + /** + * One-line auction information message. + *

+ * Placeholders: {0} - claim area; {1} - location; {2} - formatted price; {3} - time remaining; {4} - bid step. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Auction.Oneline", comment = "0: claim area, 1: location, 2: formatted price, 3: time remaining, 4: bid step") public String msgInfoClaimInfoAuctionOneline = "$2{0} $bblocks to $2Auction $bat $2{1}$b. Current highest bid is $a{2}$b. The auction will end in $a{3}$b. The bid step is $a{4}"; - @ConfigField(name="RealEstate.Info.Claim.Info.Auction.Ended", comment = "0: claim type") + /** + * Auction information message when the auction has ended. + *

+ * Placeholder: {0} - claim type. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Auction.Ended", comment = "0: claim type") public String msgInfoClaimInfoAuctionEnded = "$bThe auction for the {0} has ended."; - @ConfigField(name="RealEstate.Info.Claim.Info.Auction.Cancelled", comment = "0: claim type") + /** + * Auction information message when the auction is cancelled. + *

+ * Placeholder: {0} - claim type. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Auction.Cancelled", comment = "0: claim type") public String msgInfoClaimInfoAuctionCancelled = "$bThe auction for the {0} has been cancelled. You have been reimbursed."; - @ConfigField(name="RealEstate.Info.Claim.Info.Owner", comment = "0: owner name") + /** + * Message displaying the current owner of the claim. + *

+ * Placeholder: {0} - owner name. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Owner", comment = "0: owner name") public String msgInfoClaimInfoOwner = "$bThe current owner is $a{0}"; - @ConfigField(name="RealEstate.Info.Claim.Info.MainOwner", comment = "0: owner name") + /** + * Message displaying the main claim's owner. + *

+ * Placeholder: {0} - owner name. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Info.MainOwner", comment = "0: owner name") public String msgInfoClaimInfoMainOwner = "$bThe main claim's owner is $a{0}"; - @ConfigField(name="RealEstate.Info.Claim.Info.Note") + /** + * A note regarding subclaim access. + */ + @ConfigField(name = "RealEstate.Info.Claim.Info.Note") public String msgInfoClaimInfoNote = "$dNote: You will only get access to this subclaim."; - @ConfigField(name="RealEstate.Info.Claim.Created.Sell", comment = "0: claim prefix, 1: claim type, 2: formatted price") + /** + * Message confirming successful creation of a sale. + *

+ * Placeholders: {0} - claim prefix; {1} - claim type; {2} - formatted price. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Created.Sell", comment = "0: claim prefix, 1: claim type, 2: formatted price") public String msgInfoClaimCreatedSell = "$bYou have successfully created {0} {1} sale for $a{2}"; - @ConfigField(name="RealEstate.Info.Claim.Created.Lease", comment = "0: claim prefix, 1: claim type, 2: formatted price, 3: payments count, 4: frequency") + /** + * Message confirming successful creation of a lease. + *

+ * Placeholders: {0} - claim prefix; {1} - claim type; {2} - formatted price; {3} - payments count; {4} - frequency. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Created.Lease", comment = "0: claim prefix, 1: claim type, 2: formatted price, 3: payments count, 4: frequency") public String msgInfoClaimCreatedLease = "$bYou have successfully created {0} {1} lease for $a{3}$b payments of $a{2}$b each. Payments are due every $a{4}"; - @ConfigField(name="RealEstate.Info.Claim.Created.Rent", comment = "0: claim prefix, 1: claim type, 2: formatted price, 3: duration") + /** + * Message confirming successful creation of a rent offer. + *

+ * Placeholders: {0} - claim prefix; {1} - claim type; {2} - formatted price; {3} - duration. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Created.Rent", comment = "0: claim prefix, 1: claim type, 2: formatted price, 3: duration") public String msgInfoClaimCreatedRent = "$bYou have successfully created {0} {1} rent for $a{2}$b per $a{3}"; - @ConfigField(name="RealEstate.Info.Claim.Created.Auction", comment = "0: claim prefix, 1: claim type, 2: formatted price, 3: formatted bid step, 4: time remaining") + /** + * Message confirming successful creation of an auction. + *

+ * Placeholders: {0} - claim prefix; {1} - claim type; {2} - formatted price; {3} - formatted bid step; {4} - time remaining. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Created.Auction", comment = "0: claim prefix, 1: claim type, 2: formatted price, 3: formatted bid step, 4: time remaining") public String msgInfoClaimCreatedAuction = "$bYou have successfully created {0} {1} auction for $a{2}$b. The bid step is $a{3}$b. The auction will end in $a{4}"; - @ConfigField(name="RealEstate.Info.Claim.Created.SellBroadcast", comment = "0: player name, 1: claim prefix, 2: claim type, 3: formatted price") + /** + * Broadcast message when a sale is created. + *

+ * Placeholders: {0} - player name; {1} - claim prefix; {2} - claim type; {3} - formatted price. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Created.SellBroadcast", comment = "0: player name, 1: claim prefix, 2: claim type, 3: formatted price") public String msgInfoClaimCreatedSellBroadcast = "$a{0} $bhas created {1} {2} sale for $a{3}"; - @ConfigField(name="RealEstate.Info.Claim.Created.LeaseBroadcast", comment = "0: player name, 1: claim prefix, 2: claim type, 3: formatted price, 4: payments count, 5: frequency") + /** + * Broadcast message when a lease is created. + *

+ * Placeholders: {0} - player name; {1} - claim prefix; {2} - claim type; {3} - formatted price; {4} - payments count; {5} - frequency. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Created.LeaseBroadcast", comment = "0: player name, 1: claim prefix, 2: claim type, 3: formatted price, 4: payments count, 5: frequency") public String msgInfoClaimCreatedLeaseBroadcast = "$a{0} $bhas created {1} {2} lease for $a{4}$b payments of $a{3}$b each. Payments are due every $a{5}"; - @ConfigField(name="RealEstate.Info.Claim.Created.RentBroadcast", comment = "0: player name, 1: claim prefix, 2: claim type, 3: formatted price, 4: duration") + /** + * Broadcast message when a rent offer is created. + *

+ * Placeholders: {0} - player name; {1} - claim prefix; {2} - claim type; {3} - formatted price; {4} - duration. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Created.RentBroadcast", comment = "0: player name, 1: claim prefix, 2: claim type, 3: formatted price, 4: duration") public String msgInfoClaimCreatedRentBroadcast = "$a{0} $bhas created {1} {2} rent for $a{3}$b per $a{4}"; - @ConfigField(name="RealEstate.Info.Claim.Created.AuctionBroadcast", comment = "0: player name, 1: claim prefix, 2: claim type, 3: formatted price, 4: formatted bid step, 5: time remaining") + /** + * Broadcast message when an auction is created. + *

+ * Placeholders: {0} - player name; {1} - claim prefix; {2} - claim type; {3} - formatted price; {4} - formatted bid step; {5} - time remaining. + *

+ */ + @ConfigField(name = "RealEstate.Info.Claim.Created.AuctionBroadcast", comment = "0: player name, 1: claim prefix, 2: claim type, 3: formatted price, 4: formatted bid step, 5: time remaining") public String msgInfoClaimCreatedAuctionBroadcast = "$a{0} $bhas created {1} {2} auction for $a{3}$b. The bid step is $a{4}$b. The auction will end in $a{5}"; - @ConfigField(name="RealEstate.List.Header", comment = "0: RE Offers|Sell Offers|Rent Offers|Lease Offers; 1: Page number; 2: Page count") + /** + * Header for the transaction list. + *

+ * Placeholders: {0} - list type (e.g., RE Offers, Sell Offers, Rent Offers, Lease Offers); {1} - current page number; {2} - total page count. + *

+ */ + @ConfigField(name = "RealEstate.List.Header", comment = "0: RE Offers|Sell Offers|Rent Offers|Lease Offers; 1: Page number; 2: Page count") public String msgListTransactionsHeader = "$1----= $f[ $6{0} page $2 {1} $6/ $2{2} $f] $1=----"; - @ConfigField(name="RealEstate.List.NextPage", comment="0: all|sell|rent|lease; 1: next page number") + /** + * Message indicating how to view the next page of transactions. + *

+ * Placeholders: {0} - list type; {1} - next page number. + *

+ */ + @ConfigField(name = "RealEstate.List.NextPage", comment = "0: all|sell|rent|lease; 1: next page number") public String msgListNextPage = "$6To see the next page, type $a/re list {0} {1}"; - @ConfigField(name="RealEstate.Sign.Auction.HighestBidder", comment="0: player name, 1: formatted price") + /** + * Sign message displaying the highest bidder in an auction. + *

+ * Placeholders: {0} - bidder name; {1} - formatted price. + *

+ */ + @ConfigField(name = "RealEstate.Sign.Auction.HighestBidder", comment = "0: player name, 1: formatted price") public String msgSignAuctionHighestBidder = "$b{0}: $a{1}"; - @ConfigField(name="RealEstate.Sign.Auction.NoBider") + /** + * Sign message displayed when no bidder is present. + */ + @ConfigField(name = "RealEstate.Sign.Auction.NoBider") public String msgSignAuctionNoBider = "$bNo bidder"; - @ConfigField(name="RealEstate.Sign.Auction.RemainingTime", comment="0: formatted time") + /** + * Sign message showing the remaining time in an auction. + *

+ * Placeholder: {0} - formatted time remaining. + *

+ */ + @ConfigField(name = "RealEstate.Sign.Auction.RemainingTime", comment = "0: formatted time") public String msgSignAuctionRemainingTime = "$b$a{0}"; - @ConfigField(name="RealEstate.Sign.Auction.Ended") + /** + * Sign message indicating that the auction has ended. + */ + @ConfigField(name = "RealEstate.Sign.Auction.Ended") public String msgSignAuctionEnded = "$bAuction ended"; - @ConfigField(name="RealEstate.Sign.Auction.Won", comment="next line: winner") + /** + * Sign message indicating that the auction was won. + */ + @ConfigField(name = "RealEstate.Sign.Auction.Won", comment = "next line: winner") public String msgSignAuctionWon = "$bAuction won by"; - public Messages() - { - this.pdf = RealEstate.instance.getDescription(); - } - + /** + * Loads the language configuration file. + */ @Override - public void loadConfig() - { + public void loadConfig() { this.loadConfig(RealEstate.languagesDirectory + "/" + RealEstate.instance.config.languageFile); } + /** + * Retrieves a formatted message with placeholders replaced. + * + * @param msgTemplate the message template. + * @param args the values to replace placeholders. + * @return the formatted message with the default chat prefix. + */ public static String getMessage(String msgTemplate, String... args) { return getMessage(msgTemplate, true, args); } + /** + * Retrieves a formatted message. + * + * @param msgTemplate the message template. + * @param withPrefix if true, prepends the chat prefix. + * @param args the values to replace placeholders. + * @return the formatted message. + */ public static String getMessage(String msgTemplate, boolean withPrefix, String... args) { if (withPrefix) { msgTemplate = RealEstate.instance.config.chatPrefix + msgTemplate; } - msgTemplate = msgTemplate.replace('$', ChatColor.COLOR_CHAR); - for (int i = 0; i < args.length; i++) { String param = args[i]; msgTemplate = msgTemplate.replaceAll("\\{" + i + "\\}", Matcher.quoteReplacement(param)); } - return msgTemplate; } - //sends a color-coded message to a player + + /** + * Sends a formatted message to a CommandSender. + * + * @param player the recipient. + * @param msgTemplate the message template. + * @param args the values for placeholders. + */ public static void sendMessage(CommandSender player, String msgTemplate, String... args) { sendMessage(player, msgTemplate, 0, args); } - //sends a color-coded message to a player + /** + * Sends a formatted message to a CommandSender after a delay. + * + * @param player the recipient. + * @param msgTemplate the message template. + * @param delayInTicks delay in ticks. + * @param args the values for placeholders. + */ public static void sendMessage(CommandSender player, String msgTemplate, long delayInTicks, String... args) { String message = getMessage(msgTemplate, args); sendMessage(player, message, delayInTicks); } - //sends a color-coded message to a player + /** + * Sends a pre-formatted message to a CommandSender. + * + * @param player the recipient. + * @param message the formatted message. + */ public static void sendMessage(CommandSender player, String message) { sendMessage(player, getMessage(message), 0); } - //sends a color-coded message to a player + /** + * Sends a message to a CommandSender with an option to process color codes. + * + * @param player the recipient. + * @param message the message. + * @param fixColors if true, converts formatting codes. + */ public static void sendMessage(CommandSender player, String message, Boolean fixColors) { sendMessage(player, fixColors ? getMessage(message) : message, 0); } + /** + * Schedules a message to be sent to a CommandSender after a delay. + * + * @param player the recipient. + * @param message the message. + * @param delayInTicks delay in ticks. + */ public static void sendMessage(CommandSender player, String message, long delayInTicks) { SendPlayerMessageTask task = new SendPlayerMessageTask(player, message); - if (delayInTicks > 0) { RealEstate.instance.getServer().getScheduler().runTaskLater(RealEstate.instance, task, delayInTicks); } else { task.run(); } } - } diff --git a/src/me/EtienneDx/RealEstate/RECommand.java b/src/me/EtienneDx/RealEstate/RECommand.java index 5ba0061..19f09be 100644 --- a/src/me/EtienneDx/RealEstate/RECommand.java +++ b/src/me/EtienneDx/RealEstate/RECommand.java @@ -29,398 +29,506 @@ import me.EtienneDx.RealEstate.Transactions.ClaimRent; import me.EtienneDx.RealEstate.Transactions.ExitOffer; import me.EtienneDx.RealEstate.Transactions.Transaction; +import me.EtienneDx.RealEstate.Transactions.TransactionsStore; +import net.milkbowl.vault.economy.Economy; +import net.milkbowl.vault.permission.Permission; +/** + * The RECommand class provides command handlers for the RealEstate plugin. + *

+ * It includes subcommands for retrieving claim information, listing transactions, + * renewing rent, bidding on auctioned claims, cancelling transactions, and managing exit offers. + *

+ */ @CommandAlias("re|realestate") public class RECommand extends BaseCommand { - @Subcommand("info") - @Description("Gives the player informations about the claim he is standing in") - @CommandPermission("realestate.info") - public static void info(Player player) - { - if(RealEstate.transactionsStore.anyTransaction( - RealEstate.claimAPI.getClaimAt(((Player)player).getLocation()))) - { - Transaction tr = RealEstate.transactionsStore.getTransaction( - RealEstate.claimAPI.getClaimAt(((Player)player).getLocation())); - tr.preview((Player)player); - } - else - { - Messages.sendMessage(player, RealEstate.instance.messages.msgNoTransactionFoundHere); - } - - } - - @Subcommand("list") - @Description("Displays the list of all real estate offers currently existing") - @CommandCompletion("all|sell|rent|lease") - @Syntax("[all|sell|rent|lease] ") - public static void list(CommandSender sender, @Optional String type, @Default("1") int page) - { - Player player = null; - if (sender instanceof Player) { - player = (Player) sender; - } - if(page <= 0) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgPageMustBePositive); - return; - } - int count = 0; - int start = (page - 1) * RealEstate.instance.config.cfgPageSize; - String typeMsg; - if(type == null || type.equalsIgnoreCase("all")) - { - count = RealEstate.transactionsStore.claimSell.values().size() + RealEstate.transactionsStore.claimRent.values().size() + - RealEstate.transactionsStore.claimLease.values().size(); - typeMsg = "Real Estate offers"; - } - else if(type.equalsIgnoreCase("sell")) - { - count = RealEstate.transactionsStore.claimSell.values().size(); - typeMsg = "Sell offers"; - } - else if(type.equalsIgnoreCase("rent")) - { - count = RealEstate.transactionsStore.claimRent.values().size(); - typeMsg = "Rent offers"; - } - else if(type.equalsIgnoreCase("lease")) - { - count = RealEstate.transactionsStore.claimLease.values().size(); - typeMsg = "Lease offers"; - } - else - { - Messages.sendMessage(sender, RealEstate.instance.messages.msgErrorInvalidOption); - return; - } - if(count == 0) - { - Messages.sendMessage(sender, RealEstate.instance.messages.msgNoTransactionFound); - } - else - { - ArrayList transactions = new ArrayList(count); - if(type == null || type.equalsIgnoreCase("all")) - { - transactions.addAll(RealEstate.transactionsStore.claimSell.values()); - transactions.addAll(RealEstate.transactionsStore.claimRent.values()); - transactions.addAll(RealEstate.transactionsStore.claimLease.values()); - } - else if(type.equalsIgnoreCase("sell")) - { - transactions.addAll(RealEstate.transactionsStore.claimSell.values()); - } - else if(type.equalsIgnoreCase("rent")) - { - transactions.addAll(RealEstate.transactionsStore.claimRent.values()); - } - else if(type.equalsIgnoreCase("lease")) - { - transactions.addAll(RealEstate.transactionsStore.claimLease.values()); - } - - int max = Math.min(start + RealEstate.instance.config.cfgPageSize, count); - if(start <= max) - { - int pageCount = (int)Math.ceil(count / (double)RealEstate.instance.config.cfgPageSize); - Messages.sendMessage(sender, RealEstate.instance.messages.msgListTransactionsHeader, - typeMsg, String.valueOf(page), String.valueOf(pageCount)); - for(int i = start; i < max; i++) - { - RealEstate.instance.log.info("transaction " + i); - transactions.get(i).msgInfo(sender); - } - if(page < pageCount) - { - Messages.sendMessage(sender, RealEstate.instance.messages.msgListNextPage, (type != null ? type : "all"), String.valueOf(page + 1)); - } - } - else - { - Messages.sendMessage(sender, RealEstate.instance.messages.msgPageNotExists); - } - } - } + /** + * The RealEstate plugin instance. + *

+ * This is the base instance for the commands for the RealEstate plugin. + *

+ */ + public RECommand() {} - @Subcommand("renewrent") - @Description("Allows the player renting a claim or subclaim to enable or disable the automatic renew of his rent") - @Conditions("partOfRent") + /** + * Displays information about the claim that the player is standing in. + *

+ * If there is an ongoing transaction for the claim, its details are shown; + * otherwise, a "no transaction found" message is sent. + *

+ * + * @param player the player executing the command + */ + @Subcommand("info") + @Description("Gives the player informations about the claim he is standing in") + @CommandPermission("realestate.info") + public static void info(Player player) + { + if(RealEstate.transactionsStore.anyTransaction( + RealEstate.claimAPI.getClaimAt(player.getLocation()))) + { + Transaction tr = RealEstate.transactionsStore.getTransaction( + RealEstate.claimAPI.getClaimAt(player.getLocation())); + tr.preview(player); + } + else + { + Messages.sendMessage(player, RealEstate.instance.messages.msgNoTransactionFoundHere); + } + } + + /** + * Lists all RealEstate offers (sell, rent, or lease) currently existing. + *

+ * The type can be specified ("all", "sell", "rent", or "lease") and a page number. + *

+ * + * @param sender the command sender (player or console) + * @param type the type of offers to list (optional; defaults to "all") + * @param page the page number to display (optional; defaults to 1) + */ + @Subcommand("list") + @Description("Displays the list of all real estate offers currently existing") + @CommandCompletion("all|sell|rent|lease") + @Syntax("[all|sell|rent|lease] ") + public static void list(CommandSender sender, @Optional String type, @Default("1") int page) + { + Player player = null; + if (sender instanceof Player) { + player = (Player) sender; + } + if(page <= 0) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgPageMustBePositive); + return; + } + int count = 0; + int start = (page - 1) * RealEstate.instance.config.cfgPageSize; + String typeMsg; + if(type == null || type.equalsIgnoreCase("all")) + { + count = RealEstate.transactionsStore.claimSell.values().size() + RealEstate.transactionsStore.claimRent.values().size() + + RealEstate.transactionsStore.claimLease.values().size(); + typeMsg = "Real Estate offers"; + } + else if(type.equalsIgnoreCase("sell")) + { + count = RealEstate.transactionsStore.claimSell.values().size(); + typeMsg = "Sell offers"; + } + else if(type.equalsIgnoreCase("rent")) + { + count = RealEstate.transactionsStore.claimRent.values().size(); + typeMsg = "Rent offers"; + } + else if(type.equalsIgnoreCase("lease")) + { + count = RealEstate.transactionsStore.claimLease.values().size(); + typeMsg = "Lease offers"; + } + else + { + Messages.sendMessage(sender, RealEstate.instance.messages.msgErrorInvalidOption); + return; + } + if(count == 0) + { + Messages.sendMessage(sender, RealEstate.instance.messages.msgNoTransactionFound); + } + else + { + ArrayList transactions = new ArrayList(count); + if(type == null || type.equalsIgnoreCase("all")) + { + transactions.addAll(RealEstate.transactionsStore.claimSell.values()); + transactions.addAll(RealEstate.transactionsStore.claimRent.values()); + transactions.addAll(RealEstate.transactionsStore.claimLease.values()); + } + else if(type.equalsIgnoreCase("sell")) + { + transactions.addAll(RealEstate.transactionsStore.claimSell.values()); + } + else if(type.equalsIgnoreCase("rent")) + { + transactions.addAll(RealEstate.transactionsStore.claimRent.values()); + } + else if(type.equalsIgnoreCase("lease")) + { + transactions.addAll(RealEstate.transactionsStore.claimLease.values()); + } + + int max = Math.min(start + RealEstate.instance.config.cfgPageSize, count); + if(start <= max) + { + int pageCount = (int)Math.ceil(count / (double)RealEstate.instance.config.cfgPageSize); + Messages.sendMessage(sender, RealEstate.instance.messages.msgListTransactionsHeader, + typeMsg, String.valueOf(page), String.valueOf(pageCount)); + for(int i = start; i < max; i++) + { + RealEstate.instance.log.info("transaction " + i); + transactions.get(i).msgInfo(sender); + } + if(page < pageCount) + { + Messages.sendMessage(sender, RealEstate.instance.messages.msgListNextPage, (type != null ? type : "all"), String.valueOf(page + 1)); + } + } + else + { + Messages.sendMessage(sender, RealEstate.instance.messages.msgPageNotExists); + } + } + } + + /** + * Allows a player to view or change the automatic renew status of a rent transaction. + *

+ * Without a parameter, the current status is displayed; with a parameter ("enable" or "disable"), + * the status is updated. + *

+ * + * @param player the player executing the command + * @param newStatus the new status ("enable" or "disable"); optional + */ + @Subcommand("renewrent") + @Description("Allows the player renting a claim or subclaim to enable or disable the automatic renew of his rent") + @Conditions("partOfRent") @CommandCompletion("enable|disable") - @Syntax("[enable|disable]") - public static void renewRent(Player player, @Optional String newStatus) - { - Location loc = player.getLocation(); - IClaim claim = RealEstate.claimAPI.getClaimAt(loc); - ClaimRent cr = (ClaimRent)RealEstate.transactionsStore.getTransaction(claim); - String claimType = claim.isParentClaim() ? - RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; - if(!RealEstate.instance.config.cfgEnableAutoRenew) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorAutoRenewDisabled); - return; - } - if(newStatus == null) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgRenewRentCurrently, cr.autoRenew ? - RealEstate.instance.messages.keywordEnabled : - RealEstate.instance.messages.keywordDisabled, - claimType); - } - else if(!newStatus.equalsIgnoreCase("enable") && !newStatus.equalsIgnoreCase("disable")) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorCommandUsage, "/re renewrent [enable|disable]"); - } - else if(cr.buyer != null && cr.buyer.equals(player.getUniqueId())) - { - cr.autoRenew = newStatus.equalsIgnoreCase("enable"); - RealEstate.transactionsStore.saveData(); - Messages.sendMessage(player, RealEstate.instance.messages.msgRenewRentNow, cr.autoRenew ? - RealEstate.instance.messages.keywordEnabled : - RealEstate.instance.messages.keywordDisabled, - claimType); - } - else - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorBuyerOnly); - } - } - - @Subcommand("exitoffer") - @Conditions("partOfBoughtTransaction") - public class ExitOfferCommand extends BaseCommand - { - @Subcommand("info") - @Default - @Description("View informations about the exit offer") - public void info(Player player) - { - BoughtTransaction bt = (BoughtTransaction)RealEstate.transactionsStore.getTransaction(player); - if(bt.exitOffer == null) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgInfoExitOfferNone); - } - else if(bt.exitOffer.offerBy.equals(player.getUniqueId())) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgInfoExitOfferMadeByStatus, - RealEstate.econ.format(bt.exitOffer.price)); - Messages.sendMessage(player, RealEstate.instance.messages.msgInfoExitOfferCancel, - "/re exitoffer cancel"); - } - else// it is the other player - { - Messages.sendMessage(player, RealEstate.instance.messages.msgInfoExitOfferMadeToStatus, - Bukkit.getOfflinePlayer(bt.exitOffer.offerBy).getName(), RealEstate.econ.format(bt.exitOffer.price)); - Messages.sendMessage(player, RealEstate.instance.messages.msgInfoExitOfferAccept, - "/re exitoffer accept"); - Messages.sendMessage(player, RealEstate.instance.messages.msgInfoExitOfferReject, - "/re exitoffer refuse"); - } - } - - @Subcommand("create") - @Description("Creates an offer to break an ongoing transaction") - @Syntax("") - public void create(Player player, @Conditions("positiveDouble") Double price) - { - BoughtTransaction bt = (BoughtTransaction)RealEstate.transactionsStore.getTransaction(player); - if(bt.exitOffer != null) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorExitOfferAlreadyExists); - return; - } - if(bt.buyer == null) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorExitOfferNoBuyer); - return; - } - bt.exitOffer = new ExitOffer(player.getUniqueId(), price); + @Syntax("[enable|disable]") + public static void renewRent(Player player, @Optional String newStatus) + { + Location loc = player.getLocation(); + IClaim claim = RealEstate.claimAPI.getClaimAt(loc); + ClaimRent cr = (ClaimRent)RealEstate.transactionsStore.getTransaction(claim); + String claimType = claim.isParentClaim() ? + RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; + if(!RealEstate.instance.config.cfgEnableAutoRenew) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorAutoRenewDisabled); + return; + } + if(newStatus == null) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgRenewRentCurrently, cr.autoRenew ? + RealEstate.instance.messages.keywordEnabled : + RealEstate.instance.messages.keywordDisabled, + claimType); + } + else if(!newStatus.equalsIgnoreCase("enable") && !newStatus.equalsIgnoreCase("disable")) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorCommandUsage, "/re renewrent [enable|disable]"); + } + else if(cr.buyer != null && cr.buyer.equals(player.getUniqueId())) + { + cr.autoRenew = newStatus.equalsIgnoreCase("enable"); + RealEstate.transactionsStore.saveData(); + Messages.sendMessage(player, RealEstate.instance.messages.msgRenewRentNow, cr.autoRenew ? + RealEstate.instance.messages.keywordEnabled : + RealEstate.instance.messages.keywordDisabled, + claimType); + } + else + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorBuyerOnly); + } + } + + /** + * Subcommands for managing exit offers. + *

+ * This inner class provides commands to view, create, accept, refuse, or cancel an exit offer. + *

+ */ + @Subcommand("exitoffer") + @Conditions("partOfBoughtTransaction") + public class ExitOfferCommand extends BaseCommand + { + /** + * The ExitOfferCommand constructor. + *

+ * This is the constructor for the ExitOfferCommand class. + *

+ */ + public ExitOfferCommand() {} + + /** + * Displays information about the current exit offer. + * + * @param player the player executing the command + */ + @Subcommand("info") + @Default + @Description("View informations about the exit offer") + public void info(Player player) + { + BoughtTransaction bt = (BoughtTransaction)RealEstate.transactionsStore.getTransaction(player); + if(bt.exitOffer == null) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgInfoExitOfferNone); + } + else if(bt.exitOffer.offerBy.equals(player.getUniqueId())) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgInfoExitOfferMadeByStatus, + RealEstate.econ.format(bt.exitOffer.price)); + Messages.sendMessage(player, RealEstate.instance.messages.msgInfoExitOfferCancel, + "/re exitoffer cancel"); + } + else // it is the other player + { + Messages.sendMessage(player, RealEstate.instance.messages.msgInfoExitOfferMadeToStatus, + Bukkit.getOfflinePlayer(bt.exitOffer.offerBy).getName(), RealEstate.econ.format(bt.exitOffer.price)); + Messages.sendMessage(player, RealEstate.instance.messages.msgInfoExitOfferAccept, + "/re exitoffer accept"); + Messages.sendMessage(player, RealEstate.instance.messages.msgInfoExitOfferReject, + "/re exitoffer refuse"); + } + } + + /** + * Creates an exit offer with the specified price. + * + * @param player the player creating the exit offer + * @param price the price of the exit offer (must be positive) + */ + @Subcommand("create") + @Description("Creates an offer to break an ongoing transaction") + @Syntax("") + public void create(Player player, @Conditions("positiveDouble") Double price) + { + BoughtTransaction bt = (BoughtTransaction)RealEstate.transactionsStore.getTransaction(player); + if(bt.exitOffer != null) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorExitOfferAlreadyExists); + return; + } + if(bt.buyer == null) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorExitOfferNoBuyer); + return; + } + bt.exitOffer = new ExitOffer(player.getUniqueId(), price); - Messages.sendMessage(player, RealEstate.instance.messages.msgInfoExitOfferCreatedBySelf, - RealEstate.econ.format(price)); + Messages.sendMessage(player, RealEstate.instance.messages.msgInfoExitOfferCreatedBySelf, + RealEstate.econ.format(price)); - UUID other = player.getUniqueId().equals(bt.owner) ? bt.buyer : bt.owner; - if(other != null)// not an admin claim - { - OfflinePlayer otherP = Bukkit.getOfflinePlayer(other); - Location loc = player.getLocation(); - String claimType = RealEstate.claimAPI.getClaimAt(loc).isParentClaim() ? - RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; - String location = "[" + loc.getWorld().getName() + ", X: " + loc.getBlockX() + ", Y: " + loc.getBlockY() + ", Z: " - + loc.getBlockZ() + "]"; + UUID other = player.getUniqueId().equals(bt.owner) ? bt.buyer : bt.owner; + if(other != null) // not an admin claim + { + OfflinePlayer otherP = Bukkit.getOfflinePlayer(other); + Location loc = player.getLocation(); + String claimType = RealEstate.claimAPI.getClaimAt(loc).isParentClaim() ? + RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; + String location = "[" + loc.getWorld().getName() + ", X: " + loc.getBlockX() + ", Y: " + loc.getBlockY() + ", Z: " + + loc.getBlockZ() + "]"; - if(otherP.isOnline()) - { - Messages.sendMessage(otherP.getPlayer(), RealEstate.instance.messages.msgInfoExitOfferCreatedByOther, - player.getName(), claimType, RealEstate.econ.format(price), location); - } - else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) - { - User u = RealEstate.ess.getUser(other); - u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoExitOfferCreatedByOther, - player.getName(), claimType, RealEstate.econ.format(price), location)); - } - } - } - - @Subcommand("accept") - @Description("Accepts an offer to break an ongoing transaction") - public void accept(Player player) - { - Location loc = player.getLocation(); - IClaim claim = RealEstate.claimAPI.getClaimAt(loc); - BoughtTransaction bt = (BoughtTransaction)RealEstate.transactionsStore.getTransaction(claim); - String claimType = claim.isParentClaim() ? "claim" : "subclaim"; - if(bt.exitOffer == null) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorExitOfferNone); - } - else if(bt.exitOffer.offerBy.equals(player.getUniqueId())) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorExitOfferCantAcceptSelf); - } - else if(Utils.makePayment(player.getUniqueId(), bt.exitOffer.offerBy, bt.exitOffer.price, true, false)) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgInfoExitOfferAcceptedBySelf, - claimType, RealEstate.econ.format(bt.exitOffer.price)); + if(otherP.isOnline()) + { + Messages.sendMessage(otherP.getPlayer(), RealEstate.instance.messages.msgInfoExitOfferCreatedByOther, + player.getName(), claimType, RealEstate.econ.format(price), location); + } + else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) + { + User u = RealEstate.ess.getUser(other); + u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoExitOfferCreatedByOther, + player.getName(), claimType, RealEstate.econ.format(price), location)); + } + } + } + + /** + * Accepts the current exit offer. + * + * @param player the player accepting the exit offer + */ + @Subcommand("accept") + @Description("Accepts an offer to break an ongoing transaction") + public void accept(Player player) + { + Location loc = player.getLocation(); + IClaim claim = RealEstate.claimAPI.getClaimAt(loc); + BoughtTransaction bt = (BoughtTransaction)RealEstate.transactionsStore.getTransaction(claim); + String claimType = claim.isParentClaim() ? "claim" : "subclaim"; + if(bt.exitOffer == null) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorExitOfferNone); + } + else if(bt.exitOffer.offerBy.equals(player.getUniqueId())) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorExitOfferCantAcceptSelf); + } + else if(Utils.makePayment(player.getUniqueId(), bt.exitOffer.offerBy, bt.exitOffer.price, true, false)) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgInfoExitOfferAcceptedBySelf, + claimType, RealEstate.econ.format(bt.exitOffer.price)); - UUID other = player.getUniqueId().equals(bt.owner) ? bt.buyer : bt.owner; - String location = "[" + loc.getWorld().getName() + ", X: " + loc.getBlockX() + ", Y: " + loc.getBlockY() + - ", Z: " + loc.getBlockZ() + "]"; - if(other != null) - { - OfflinePlayer otherP = Bukkit.getOfflinePlayer(other); - if(otherP.isOnline()) - { - Messages.sendMessage(otherP.getPlayer(), RealEstate.instance.messages.msgInfoExitOfferAcceptedByOther, - player.getName(), claimType, RealEstate.econ.format(bt.exitOffer.price), location); - } - else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) - { - User u = RealEstate.ess.getUser(other); - - u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoExitOfferAcceptedByOther, - player.getName(), claimType, RealEstate.econ.format(bt.exitOffer.price), location)); - } - } - bt.exitOffer = null; - claim.dropPlayerPermissions(bt.buyer); - claim.removeManager(bt.buyer); - RealEstate.claimAPI.saveClaim(claim); - bt.buyer = null; - bt.update();// eventual cancel is contained in here - } - // the make payment takes care of sending error if need be - } - - @Subcommand("refuse") - @Description("Refuses an offer to break an ongoing transaction") - public void refuse(Player player) - { - Location loc = player.getLocation(); - IClaim claim = RealEstate.claimAPI.getClaimAt(loc); - BoughtTransaction bt = (BoughtTransaction)RealEstate.transactionsStore.getTransaction(claim); - String claimType = claim.isParentClaim() ? "claim" : "subclaim"; - if(bt.exitOffer == null) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorExitOfferNone); - } - else if(bt.exitOffer.offerBy.equals(player.getUniqueId())) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorExitOfferCantRefuseSelf); - } - else - { - bt.exitOffer = null; - Messages.sendMessage(player, RealEstate.instance.messages.msgInfoExitOfferRejectedBySelf); - UUID other = player.getUniqueId().equals(bt.owner) ? bt.buyer : bt.owner; - String location = "[" + loc.getWorld().getName() + ", X: " + loc.getBlockX() + ", Y: " + loc.getBlockY() + - ", Z: " + loc.getBlockZ() + "]"; - if(other != null) - { - OfflinePlayer otherP = Bukkit.getOfflinePlayer(other); - if(otherP.isOnline()) - { - Messages.sendMessage(otherP.getPlayer(), RealEstate.instance.messages.msgInfoExitOfferRejectedByOther, - player.getName(), claimType, location); - } - else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) - { - User u = RealEstate.ess.getUser(other); - u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoExitOfferRejectedByOther, - player.getName(), claimType, location)); - } - } - } - } - - @Subcommand("cancel") - @Description("Cancels an offer to break an ongoing transaction") - public void cancel(Player player) - { - Location loc = player.getLocation(); - IClaim claim = RealEstate.claimAPI.getClaimAt(loc); - BoughtTransaction bt = (BoughtTransaction)RealEstate.transactionsStore.getTransaction(claim); - String claimType = claim.isParentClaim() ? "claim" : "subclaim"; - if(bt.exitOffer.offerBy.equals(player.getUniqueId())) - { - bt.exitOffer = null; - Messages.sendMessage(player, RealEstate.instance.messages.msgInfoExitOfferCancelledBySelf); - - UUID other = player.getUniqueId().equals(bt.owner) ? bt.buyer : bt.owner; - String location = "[" + loc.getWorld().getName() + ", X: " + loc.getBlockX() + ", Y: " + loc.getBlockY() + - ", Z: " + loc.getBlockZ() + "]"; - if(other != null) - { - OfflinePlayer otherP = Bukkit.getOfflinePlayer(other); - if(otherP.isOnline()) - { - Messages.sendMessage(otherP.getPlayer(), RealEstate.instance.messages.msgInfoExitOfferCancelledByOther, - player.getName(), claimType, location); - } - else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) - { - User u = RealEstate.ess.getUser(other); - u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoExitOfferCancelledByOther, - player.getName(), claimType, location)); - } - } - } - else - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorExitOfferCantCancelOther); - } - } - } + UUID other = player.getUniqueId().equals(bt.owner) ? bt.buyer : bt.owner; + String location = "[" + loc.getWorld().getName() + ", X: " + loc.getBlockX() + ", Y: " + loc.getBlockY() + + ", Z: " + loc.getBlockZ() + "]"; + if(other != null) + { + OfflinePlayer otherP = Bukkit.getOfflinePlayer(other); + if(otherP.isOnline()) + { + Messages.sendMessage(otherP.getPlayer(), RealEstate.instance.messages.msgInfoExitOfferAcceptedByOther, + player.getName(), claimType, RealEstate.econ.format(bt.exitOffer.price), location); + } + else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) + { + User u = RealEstate.ess.getUser(other); + u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoExitOfferAcceptedByOther, + player.getName(), claimType, RealEstate.econ.format(bt.exitOffer.price), location)); + } + } + bt.exitOffer = null; + claim.dropPlayerPermissions(bt.buyer); + claim.removeManager(bt.buyer); + RealEstate.claimAPI.saveClaim(claim); + bt.buyer = null; + bt.update(); // eventual cancel is contained in here + } + // the makePayment takes care of sending error if need be + } + + /** + * Refuses the current exit offer. + * + * @param player the player refusing the exit offer + */ + @Subcommand("refuse") + @Description("Refuses an offer to break an ongoing transaction") + public void refuse(Player player) + { + Location loc = player.getLocation(); + IClaim claim = RealEstate.claimAPI.getClaimAt(loc); + BoughtTransaction bt = (BoughtTransaction)RealEstate.transactionsStore.getTransaction(claim); + String claimType = claim.isParentClaim() ? "claim" : "subclaim"; + if(bt.exitOffer == null) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorExitOfferNone); + } + else if(bt.exitOffer.offerBy.equals(player.getUniqueId())) + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorExitOfferCantRefuseSelf); + } + else + { + bt.exitOffer = null; + Messages.sendMessage(player, RealEstate.instance.messages.msgInfoExitOfferRejectedBySelf); + UUID other = player.getUniqueId().equals(bt.owner) ? bt.buyer : bt.owner; + String location = "[" + loc.getWorld().getName() + ", X: " + loc.getBlockX() + ", Y: " + loc.getBlockY() + + ", Z: " + loc.getBlockZ() + "]"; + if(other != null) + { + OfflinePlayer otherP = Bukkit.getOfflinePlayer(other); + if(otherP.isOnline()) + { + Messages.sendMessage(otherP.getPlayer(), RealEstate.instance.messages.msgInfoExitOfferRejectedByOther, + player.getName(), claimType, location); + } + else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) + { + User u = RealEstate.ess.getUser(other); + u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoExitOfferRejectedByOther, + player.getName(), claimType, location)); + } + } + } + } + + /** + * Cancels the current exit offer. + * + * @param player the player cancelling the exit offer + */ + @Subcommand("cancel") + @Description("Cancels an offer to break an ongoing transaction") + public void cancel(Player player) + { + Location loc = player.getLocation(); + IClaim claim = RealEstate.claimAPI.getClaimAt(loc); + BoughtTransaction bt = (BoughtTransaction)RealEstate.transactionsStore.getTransaction(claim); + String claimType = claim.isParentClaim() ? "claim" : "subclaim"; + if(bt.exitOffer.offerBy.equals(player.getUniqueId())) + { + bt.exitOffer = null; + Messages.sendMessage(player, RealEstate.instance.messages.msgInfoExitOfferCancelledBySelf); + + UUID other = player.getUniqueId().equals(bt.owner) ? bt.buyer : bt.owner; + String location = "[" + loc.getWorld().getName() + ", X: " + loc.getBlockX() + ", Y: " + loc.getBlockY() + + ", Z: " + loc.getBlockZ() + "]"; + if(other != null) + { + OfflinePlayer otherP = Bukkit.getOfflinePlayer(other); + if(otherP.isOnline()) + { + Messages.sendMessage(otherP.getPlayer(), RealEstate.instance.messages.msgInfoExitOfferCancelledByOther, + player.getName(), claimType, location); + } + else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) + { + User u = RealEstate.ess.getUser(other); + u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoExitOfferCancelledByOther, + player.getName(), claimType, location)); + } + } + } + else + { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorExitOfferCantCancelOther); + } + } + } - @Subcommand("bid") - @Conditions("claimIsAuctioned") - @CommandPermission("realestate.bid") - @Syntax("") - public static void bid(Player player, @Conditions("positiveDouble") double bid) - { - Location loc = player.getLocation(); - IClaim claim = RealEstate.claimAPI.getClaimAt(loc); - ClaimAuction ca = (ClaimAuction)RealEstate.transactionsStore.getTransaction(claim); - ca.bid(player, bid); - } - - @Subcommand("cancel") - @Conditions("claimHasTransaction") - @CommandPermission("realestate.admin") - public static void cancelTransaction(Player player) - { - Location loc = player.getLocation(); - IClaim claim = RealEstate.claimAPI.getClaimAt(loc); - Transaction t = RealEstate.transactionsStore.getTransaction(claim); - t.tryCancelTransaction(player, true); - } - - @HelpCommand - public static void onHelp(CommandSender sender, CommandHelp help) - { + /** + * Bids on an auctioned claim. + *

+ * This command requires the claim to be currently under auction. + *

+ * + * @param player the player placing the bid + * @param bid the bid amount (must be a positive double) + */ + @Subcommand("bid") + @Conditions("claimIsAuctioned") + @CommandPermission("realestate.bid") + @Syntax("") + public static void bid(Player player, @Conditions("positiveDouble") double bid) + { + Location loc = player.getLocation(); + IClaim claim = RealEstate.claimAPI.getClaimAt(loc); + ClaimAuction ca = (ClaimAuction)RealEstate.transactionsStore.getTransaction(claim); + ca.bid(player, bid); + } + + /** + * Cancels the transaction for the claim at the player's location. + *

+ * Requires the player to have the "realestate.admin" permission. + *

+ * + * @param player the player executing the cancellation command + */ + @Subcommand("cancel") + @Conditions("claimHasTransaction") + @CommandPermission("realestate.admin") + public static void cancelTransaction(Player player) + { + Location loc = player.getLocation(); + IClaim claim = RealEstate.claimAPI.getClaimAt(loc); + Transaction t = RealEstate.transactionsStore.getTransaction(claim); + t.tryCancelTransaction(player, true); + } + + /** + * Displays the help information for the RealEstate commands. + * + * @param sender the command sender requesting help + * @param help the CommandHelp object containing the help details + */ + @HelpCommand + public static void onHelp(CommandSender sender, CommandHelp help) + { help.showHelp(); - } + } } diff --git a/src/me/EtienneDx/RealEstate/REListener.java b/src/me/EtienneDx/RealEstate/REListener.java index 61b5906..da7daef 100644 --- a/src/me/EtienneDx/RealEstate/REListener.java +++ b/src/me/EtienneDx/RealEstate/REListener.java @@ -18,515 +18,498 @@ import me.EtienneDx.RealEstate.ClaimAPI.IClaim; import me.EtienneDx.RealEstate.Transactions.Transaction; -public class REListener implements Listener -{ - void registerEvents() - { - PluginManager pm = RealEstate.instance.getServer().getPluginManager(); - - pm.registerEvents(this, RealEstate.instance); - } - - @EventHandler - public void onSignChange(SignChangeEvent event) - { - if(RealEstate.instance.config.cfgSellKeywords.contains(event.getLine(0).toLowerCase()) || - RealEstate.instance.config.cfgLeaseKeywords.contains(event.getLine(0).toLowerCase()) || - RealEstate.instance.config.cfgRentKeywords.contains(event.getLine(0).toLowerCase()) || - RealEstate.instance.config.cfgContainerRentKeywords.contains(event.getLine(0).toLowerCase()) || - RealEstate.instance.config.cfgAuctionKeywords.contains(event.getLine(0).toLowerCase())) - { - Player player = event.getPlayer(); - Location loc = event.getBlock().getLocation(); - - IClaim claim = RealEstate.claimAPI.getClaimAt(loc); - if(claim == null || claim.isWilderness())// must have something to sell - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNotInClaim); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - if(RealEstate.transactionsStore.anyTransaction(claim)) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignOngoingTransaction); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - if(RealEstate.transactionsStore.anyTransaction(claim.getParent())) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignParentOngoingTransaction); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - for(IClaim c : claim.getChildren()) - { - if(RealEstate.transactionsStore.anyTransaction(c)) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignSubclaimOngoingTransaction); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - } - - // empty is considered a wish to sell - if(RealEstate.instance.config.cfgSellKeywords.contains(event.getLine(0).toLowerCase())) - { - if(!RealEstate.instance.config.cfgEnableSell) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignSellingDisabled); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - - String type = claim.isParentClaim() ? "claim" : "subclaim"; - String typeDisplay = claim.isParentClaim() ? - RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; - if(!RealEstate.perms.has(player, "realestate." + type + ".sell")) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNoSellPermission, typeDisplay); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - - // check for a valid price - double price; - try - { - price = getDouble(event, 1, RealEstate.instance.config.cfgPriceSellPerBlock * claim.getArea()); - } - catch (NumberFormatException e) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorInvalidNumber, event.getLine(1)); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - if(price <= 0) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorNegativePrice, event.getLine(1)); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - if((price%1)!=0 && !RealEstate.instance.config.cfgUseDecimalCurrency) //if the price has a decimal number AND Decimal currency is disabled - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorNonIntegerPrice, event.getLine(1)); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - - if(claim.isAdminClaim()) - { - if(!RealEstate.perms.has(player, "realestate.admin"))// admin may sell admin claims - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNoAdminSellPermission, typeDisplay); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - } - else if(type.equals("claim") && !player.getUniqueId().equals(claim.getOwner()))// only the owner may sell his claim - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNotOwner, typeDisplay); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - - // we should be good to sell it now - event.setCancelled(true);// need to cancel the event, so we can update the sign elsewhere - RealEstate.transactionsStore.sell(claim, claim.isAdminClaim() ? null : player, price, event.getBlock().getLocation()); - } - else if(RealEstate.instance.config.cfgRentKeywords.contains(event.getLine(0).toLowerCase()) || - RealEstate.instance.config.cfgContainerRentKeywords.contains(event.getLine(0).toLowerCase()))// we want to rent it - { - if(!RealEstate.instance.config.cfgEnableRent) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignRentingDisabled); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - String type = claim.isParentClaim() ? "claim" : "subclaim"; - String typeDisplay = claim.isParentClaim() ? - RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; - if(!RealEstate.perms.has(player, "realestate." + type + ".rent")) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNoRentPermission, typeDisplay); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - - // check for a valid price - double price; - try - { - price = getDouble(event, 1, RealEstate.instance.config.cfgPriceRentPerBlock * claim.getArea()); - } - catch (NumberFormatException e) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorInvalidNumber, event.getLine(1)); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - if(price <= 0) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorNegativePrice, event.getLine(1)); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - if((price%1)!=0 && !RealEstate.instance.config.cfgUseDecimalCurrency) //if the price has a decimal number AND Decimal currency is disabled - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorNonIntegerPrice, event.getLine(1)); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - - if(event.getLine(2).isEmpty()) - { - event.setLine(2, RealEstate.instance.config.cfgRentTime); - } - int duration = parseDuration(event.getLine(2)); - if(duration == 0) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorInvalidDuration, event.getLine(2), - "10 weeks", - "3 days", - "1 week 3 days"); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - - if(claim.isAdminClaim()) - { - if(!RealEstate.perms.has(player, "realestate.admin"))// admin may sell admin claims - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNoAdminRentPermission, typeDisplay); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - } - else if(type.equals("claim") && !player.getUniqueId().equals(claim.getOwner()))// only the owner may sell his claim - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNotOwner, typeDisplay); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - - // all should be good, we can create the rent - event.setCancelled(true); - RealEstate.transactionsStore.rent(claim, player, price, event.getBlock().getLocation(), duration, - RealEstate.instance.config.cfgRentKeywords.contains(event.getLine(0).toLowerCase())); - } - else if(RealEstate.instance.config.cfgLeaseKeywords.contains(event.getLine(0).toLowerCase()))// we want to rent it - { - if(!RealEstate.instance.config.cfgEnableLease) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignLeasingDisabled); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - String type = claim.isParentClaim() ? "claim" : "subclaim"; - String typeDisplay = claim.isParentClaim() ? - RealEstate.instance.messages.keywordClaim : - RealEstate.instance.messages.keywordSubclaim; - if(!RealEstate.perms.has(player, "realestate." + type + ".lease")) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNoLeasePermission, typeDisplay); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - - // check for a valid price - double price; - try - { - price = getDouble(event, 1, RealEstate.instance.config.cfgPriceLeasePerBlock * claim.getArea()); - } - catch (NumberFormatException e) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorInvalidNumber, event.getLine(1)); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - if(price <= 0) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorNegativePrice, event.getLine(1)); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - if((price%1)!=0 && !RealEstate.instance.config.cfgUseDecimalCurrency) //if the price has a decimal number AND Decimal currency is disabled - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorNonIntegerPrice, event.getLine(1)); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - - if(event.getLine(2).isEmpty()) - { - event.setLine(2, "" + RealEstate.instance.config.cfgLeasePayments); - } - int paymentsCount; - try - { - paymentsCount = Integer.parseInt(event.getLine(2)); - } - catch(Exception e) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorInvalidNumber, event.getLine(2)); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - - if(event.getLine(3).isEmpty()) - { - event.setLine(3, RealEstate.instance.config.cfgLeaseTime); - } - int frequency = parseDuration(event.getLine(3)); - if(frequency == 0) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorInvalidDuration, event.getLine(3), - "10 weeks", - "3 days", - "1 week 3 days"); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - - if(claim.isAdminClaim()) - { - if(!RealEstate.perms.has(player, "realestate.admin"))// admin may sell admin claims - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNoAdminLeasePermission, typeDisplay); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - } - else if(type.equals("claim") && !player.getUniqueId().equals(claim.getOwner()))// only the owner may sell his claim - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNotOwner, typeDisplay); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - - // all should be good, we can create the rent - event.setCancelled(true); - RealEstate.transactionsStore.lease(claim, player, price, event.getBlock().getLocation(), frequency, paymentsCount); - } - else if(RealEstate.instance.config.cfgAuctionKeywords.contains(event.getLine(0).toLowerCase())) - { - if(!RealEstate.instance.config.cfgEnableAuction) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignAuctionDisabled); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - String type = claim.isParentClaim() ? "claim" : "subclaim"; - String typeDisplay = claim.isParentClaim() ? - RealEstate.instance.messages.keywordClaim : - RealEstate.instance.messages.keywordSubclaim; - if(!RealEstate.perms.has(player, "realestate." + type + ".auction")) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNoAuctionPermission, typeDisplay); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - - // check for a valid price - double price; - try - { - price = getDouble(event, 1, RealEstate.instance.config.cfgPriceAuctionPerBlock * claim.getArea()); - } - catch (NumberFormatException e) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorInvalidNumber, event.getLine(1)); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - if(price <= 0) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorNegativePrice, event.getLine(1)); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - - // check for a valid bid step - double bidStep; - try - { - bidStep = getDouble(event, 2, RealEstate.instance.config.cfgPriceAuctionBidStep); - } - catch (NumberFormatException e) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorInvalidNumber, event.getLine(2)); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - if(bidStep <= 0) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorNegativeBidStep, event.getLine(2)); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - - // check for a valid duration - if(event.getLine(3).isEmpty()) - { - event.setLine(3, RealEstate.instance.config.cfgLeaseTime); - } - int duration = parseDuration(event.getLine(3)); - if(duration == 0) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorInvalidDuration, event.getLine(3), - "10 weeks", - "3 days", - "1 week 3 days"); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - - if(claim.isAdminClaim()) - { - if(!RealEstate.perms.has(player, "realestate.admin"))// admin may sell admin claims - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNoAdminAuctionPermission, typeDisplay); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - } - else if(type.equals("claim") && !player.getUniqueId().equals(claim.getOwner()))// only the owner may sell his claim - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNotOwner, typeDisplay); - event.setCancelled(true); - event.getBlock().breakNaturally(); - return; - } - - // all should be good, we can create the auction - event.setCancelled(true); - RealEstate.transactionsStore.auction(claim, player, price, event.getBlock().getLocation(), duration, bidStep); - } - } - } - - private int parseDuration(String line) - { - Pattern p = Pattern.compile("^(?:(?\\d{1,2}) ?w(?:eeks?)?)? ?(?:(?\\d{1,2}) ?d(?:ays?)?)?$", Pattern.CASE_INSENSITIVE); - Matcher m = p.matcher(line); - if(!line.isEmpty() && m.matches()) - { - int ret = 0; - if(m.group("weeks") != null) - ret += 7 * Integer.parseInt(m.group("weeks")); - if(m.group("days") != null) - ret += Integer.parseInt(m.group("days")); - return ret; - } - return 0; - } - - private double getDouble(SignChangeEvent event, int line, double defaultValue) throws NumberFormatException - { - if(event.getLine(line).isEmpty())// if no price specified, make it the default one - { - event.setLine(line, Double.toString(defaultValue)); - } - return Double.parseDouble(event.getLine(line)); - } - - @EventHandler - public void onPlayerInteract(PlayerInteractEvent event) - { - if(event.getAction().equals(Action.RIGHT_CLICK_BLOCK) && event.getHand().equals(EquipmentSlot.HAND) && - event.getClickedBlock().getState() instanceof Sign) - { - RealEstateSign s = new RealEstateSign((Sign) event.getClickedBlock().getState()); - // it is a real estate sign - if(s.isRealEstateSign()) - { - Player player = event.getPlayer(); - IClaim claim = RealEstate.claimAPI.getClaimAt(event.getClickedBlock().getLocation()); - - if(!RealEstate.transactionsStore.anyTransaction(claim)) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNoTransaction); - event.getClickedBlock().breakNaturally(); - event.setCancelled(true); - return; - } - - Transaction tr = RealEstate.transactionsStore.getTransaction(claim); - if(player.isSneaking()) - tr.preview(player); - else - tr.interact(player); - } - } - } - - @EventHandler - public void onBreakBlock(BlockBreakEvent event) - { - if(event.getBlock().getState() instanceof Sign) - { - IClaim claim = RealEstate.claimAPI.getClaimAt(event.getBlock().getLocation()); - if(claim != null && !claim.isWilderness()) - { - Transaction tr = RealEstate.transactionsStore.getTransaction(claim); - if(tr != null && event.getBlock().equals(tr.getHolder())) - { - if(event.getPlayer() != null && tr.getOwner() != null && !event.getPlayer().getUniqueId().equals(tr.getOwner()) && - !RealEstate.perms.has(event.getPlayer(), "realestate.destroysigns")) - { - Messages.sendMessage(event.getPlayer(), RealEstate.instance.messages.msgErrorSignNotAuthor); - event.setCancelled(true); - return; - } - else if(event.getPlayer() != null && tr.getOwner() == null && !RealEstate.perms.has(event.getPlayer(), "realestate.admin")) - { - Messages.sendMessage(event.getPlayer(), RealEstate.instance.messages.msgErrorSignNotAdmin); - event.setCancelled(true); - return; - } - // the sign has been destroy, we can try to cancel the transaction - if(!tr.tryCancelTransaction(event.getPlayer())) - { - event.setCancelled(true); - } - } - } - } - } +/** + * The REListener class handles various RealEstate-related events, + * such as sign changes, player interactions with signs, and block break events. + *

+ * This listener ensures that RealEstate transaction rules are enforced when players + * interact with claim-related signs. + *

+ */ +public class REListener implements Listener { + + /** + * Constructs a new REListener instance. + */ + public REListener() {} + + /** + * Registers this listener with the Bukkit PluginManager. + */ + void registerEvents() { + PluginManager pm = RealEstate.instance.getServer().getPluginManager(); + pm.registerEvents(this, RealEstate.instance); + } + + /** + * Handles sign change events. + *

+ * This method checks if the sign being changed is intended for a RealEstate transaction + * (sell, rent, lease, container rent, or auction) based on its first line. + * It validates the sign’s contents (price, duration, permissions, etc.) and, if valid, + * creates the appropriate transaction. If any check fails, the event is cancelled and + * the sign is broken. + *

+ * + * @param event the SignChangeEvent triggered when a sign is changed + */ + @EventHandler + public void onSignChange(SignChangeEvent event) { + if (RealEstate.instance.config.cfgSellKeywords.contains(event.getLine(0).toLowerCase()) || + RealEstate.instance.config.cfgLeaseKeywords.contains(event.getLine(0).toLowerCase()) || + RealEstate.instance.config.cfgRentKeywords.contains(event.getLine(0).toLowerCase()) || + RealEstate.instance.config.cfgContainerRentKeywords.contains(event.getLine(0).toLowerCase()) || + RealEstate.instance.config.cfgAuctionKeywords.contains(event.getLine(0).toLowerCase())) { + Player player = event.getPlayer(); + Location loc = event.getBlock().getLocation(); + + IClaim claim = RealEstate.claimAPI.getClaimAt(loc); + if (claim == null || claim.isWilderness()) { // must have something to sell + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNotInClaim); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + if (RealEstate.transactionsStore.anyTransaction(claim)) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignOngoingTransaction); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + if (RealEstate.transactionsStore.anyTransaction(claim.getParent())) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignParentOngoingTransaction); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + for (IClaim c : claim.getChildren()) { + if (RealEstate.transactionsStore.anyTransaction(c)) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignSubclaimOngoingTransaction); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + } + + // Empty first line is considered a wish to sell. + if (RealEstate.instance.config.cfgSellKeywords.contains(event.getLine(0).toLowerCase())) { + if (!RealEstate.instance.config.cfgEnableSell) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignSellingDisabled); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + + String type = claim.isParentClaim() ? "claim" : "subclaim"; + String typeDisplay = claim.isParentClaim() ? + RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; + if (!RealEstate.perms.has(player, "realestate." + type + ".sell")) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNoSellPermission, typeDisplay); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + + // Check for a valid price. + double price; + try { + price = getDouble(event, 1, RealEstate.instance.config.cfgPriceSellPerBlock * claim.getArea()); + } catch (NumberFormatException e) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorInvalidNumber, event.getLine(1)); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + if (price <= 0) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorNegativePrice, event.getLine(1)); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + if ((price % 1) != 0 && !RealEstate.instance.config.cfgUseDecimalCurrency) { // if the price has a decimal number AND decimal currency is disabled + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorNonIntegerPrice, event.getLine(1)); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + + if (claim.isAdminClaim()) { + if (!RealEstate.perms.has(player, "realestate.admin")) { // admin may sell admin claims + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNoAdminSellPermission, typeDisplay); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + } else if (type.equals("claim") && !player.getUniqueId().equals(claim.getOwner())) { // only the owner may sell his claim + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNotOwner, typeDisplay); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + + // All checks passed; create the sell transaction. + event.setCancelled(true); // Cancel the event so the sign can be updated elsewhere. + RealEstate.transactionsStore.sell(claim, claim.isAdminClaim() ? null : player, price, event.getBlock().getLocation()); + } + else if (RealEstate.instance.config.cfgRentKeywords.contains(event.getLine(0).toLowerCase()) || + RealEstate.instance.config.cfgContainerRentKeywords.contains(event.getLine(0).toLowerCase())) { // Rent + if (!RealEstate.instance.config.cfgEnableRent) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignRentingDisabled); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + String type = claim.isParentClaim() ? "claim" : "subclaim"; + String typeDisplay = claim.isParentClaim() ? + RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; + if (!RealEstate.perms.has(player, "realestate." + type + ".rent")) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNoRentPermission, typeDisplay); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + + // Check for a valid price. + double price; + try { + price = getDouble(event, 1, RealEstate.instance.config.cfgPriceRentPerBlock * claim.getArea()); + } catch (NumberFormatException e) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorInvalidNumber, event.getLine(1)); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + if (price <= 0) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorNegativePrice, event.getLine(1)); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + if ((price % 1) != 0 && !RealEstate.instance.config.cfgUseDecimalCurrency) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorNonIntegerPrice, event.getLine(1)); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + + if (event.getLine(2).isEmpty()) { + event.setLine(2, RealEstate.instance.config.cfgRentTime); + } + int duration = parseDuration(event.getLine(2)); + if (duration == 0) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorInvalidDuration, event.getLine(2), + "10 weeks", + "3 days", + "1 week 3 days"); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + + if (claim.isAdminClaim()) { + if (!RealEstate.perms.has(player, "realestate.admin")) { // admin may rent admin claims + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNoAdminRentPermission, typeDisplay); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + } else if (type.equals("claim") && !player.getUniqueId().equals(claim.getOwner())) { // only owner may rent his claim + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNotOwner, typeDisplay); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + + // Create the rent transaction. + event.setCancelled(true); + RealEstate.transactionsStore.rent(claim, player, price, event.getBlock().getLocation(), duration, + RealEstate.instance.config.cfgRentKeywords.contains(event.getLine(0).toLowerCase())); + } + else if (RealEstate.instance.config.cfgLeaseKeywords.contains(event.getLine(0).toLowerCase())) { // Lease + if (!RealEstate.instance.config.cfgEnableLease) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignLeasingDisabled); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + String type = claim.isParentClaim() ? "claim" : "subclaim"; + String typeDisplay = claim.isParentClaim() ? + RealEstate.instance.messages.keywordClaim : + RealEstate.instance.messages.keywordSubclaim; + if (!RealEstate.perms.has(player, "realestate." + type + ".lease")) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNoLeasePermission, typeDisplay); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + + // Check for a valid price. + double price; + try { + price = getDouble(event, 1, RealEstate.instance.config.cfgPriceLeasePerBlock * claim.getArea()); + } catch (NumberFormatException e) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorInvalidNumber, event.getLine(1)); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + if (price <= 0) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorNegativePrice, event.getLine(1)); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + if ((price % 1) != 0 && !RealEstate.instance.config.cfgUseDecimalCurrency) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorNonIntegerPrice, event.getLine(1)); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + + if (event.getLine(2).isEmpty()) { + event.setLine(2, "" + RealEstate.instance.config.cfgLeasePayments); + } + int paymentsCount; + try { + paymentsCount = Integer.parseInt(event.getLine(2)); + } catch (Exception e) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorInvalidNumber, event.getLine(2)); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + + if (event.getLine(3).isEmpty()) { + event.setLine(3, RealEstate.instance.config.cfgLeaseTime); + } + int frequency = parseDuration(event.getLine(3)); + if (frequency == 0) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorInvalidDuration, event.getLine(3), + "10 weeks", + "3 days", + "1 week 3 days"); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + + if (claim.isAdminClaim()) { + if (!RealEstate.perms.has(player, "realestate.admin")) { // admin may lease admin claims + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNoAdminLeasePermission, typeDisplay); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + } else if (type.equals("claim") && !player.getUniqueId().equals(claim.getOwner())) { // only owner may lease his claim + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNotOwner, typeDisplay); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + + // Create the lease transaction. + event.setCancelled(true); + RealEstate.transactionsStore.lease(claim, player, price, event.getBlock().getLocation(), frequency, paymentsCount); + } + else if (RealEstate.instance.config.cfgAuctionKeywords.contains(event.getLine(0).toLowerCase())) { + if (!RealEstate.instance.config.cfgEnableAuction) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignAuctionDisabled); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + String type = claim.isParentClaim() ? "claim" : "subclaim"; + String typeDisplay = claim.isParentClaim() ? + RealEstate.instance.messages.keywordClaim : + RealEstate.instance.messages.keywordSubclaim; + if (!RealEstate.perms.has(player, "realestate." + type + ".auction")) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNoAuctionPermission, typeDisplay); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + + // Check for a valid price. + double price; + try { + price = getDouble(event, 1, RealEstate.instance.config.cfgPriceAuctionPerBlock * claim.getArea()); + } catch (NumberFormatException e) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorInvalidNumber, event.getLine(1)); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + if (price <= 0) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorNegativePrice, event.getLine(1)); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + + // Check for a valid bid step. + double bidStep; + try { + bidStep = getDouble(event, 2, RealEstate.instance.config.cfgPriceAuctionBidStep); + } catch (NumberFormatException e) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorInvalidNumber, event.getLine(2)); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + if (bidStep <= 0) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorNegativeBidStep, event.getLine(2)); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + + // Check for a valid duration. + if (event.getLine(3).isEmpty()) { + event.setLine(3, RealEstate.instance.config.cfgLeaseTime); + } + int duration = parseDuration(event.getLine(3)); + if (duration == 0) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorInvalidDuration, event.getLine(3), + "10 weeks", + "3 days", + "1 week 3 days"); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + + if (claim.isAdminClaim()) { + if (!RealEstate.perms.has(player, "realestate.admin")) { // admin may auction admin claims + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNoAdminAuctionPermission, typeDisplay); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + } else if (type.equals("claim") && !player.getUniqueId().equals(claim.getOwner())) { // only owner may auction his claim + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNotOwner, typeDisplay); + event.setCancelled(true); + event.getBlock().breakNaturally(); + return; + } + + // Create the auction transaction. + event.setCancelled(true); + RealEstate.transactionsStore.auction(claim, player, price, event.getBlock().getLocation(), duration, bidStep); + } + } + } + + /** + * Parses a duration string expressed in weeks and days. + *

+ * The expected format is for example "1w 3d", where "w" represents weeks and "d" represents days. + *

+ * + * @param line the duration string from the sign + * @return the total duration in days, or 0 if the format is invalid + */ + private int parseDuration(String line) { + Pattern p = Pattern.compile("^(?:(?\\d{1,2}) ?w(?:eeks?)?)? ?(?:(?\\d{1,2}) ?d(?:ays?)?)?$", Pattern.CASE_INSENSITIVE); + Matcher m = p.matcher(line); + if (!line.isEmpty() && m.matches()) { + int ret = 0; + if (m.group("weeks") != null) + ret += 7 * Integer.parseInt(m.group("weeks")); + if (m.group("days") != null) + ret += Integer.parseInt(m.group("days")); + return ret; + } + return 0; + } + + /** + * Retrieves a double value from a specific line in a sign change event. + *

+ * If the specified line is empty, the default value is set on that line. + *

+ * + * @param event the sign change event + * @param line the line number to read + * @param defaultValue the default value to use if the line is empty + * @return the parsed double value + * @throws NumberFormatException if the value cannot be parsed as a double + */ + private double getDouble(SignChangeEvent event, int line, double defaultValue) throws NumberFormatException { + if (event.getLine(line).isEmpty()) { // if no price specified, use default + event.setLine(line, Double.toString(defaultValue)); + } + return Double.parseDouble(event.getLine(line)); + } + + /** + * Handles player interaction events with RealEstate signs. + *

+ * When a player right-clicks a block with a sign, if it is a RealEstate sign, + * this method either previews the transaction (if the player is sneaking) or + * initiates an interaction with the transaction. + *

+ * + * @param event the player interact event + */ + @EventHandler + public void onPlayerInteract(PlayerInteractEvent event) { + if (event.getAction().equals(Action.RIGHT_CLICK_BLOCK) && event.getHand().equals(EquipmentSlot.HAND) && + event.getClickedBlock().getState() instanceof Sign) { + RealEstateSign s = new RealEstateSign((Sign) event.getClickedBlock().getState()); + // Check if it is a RealEstate sign. + if (s.isRealEstateSign()) { + Player player = event.getPlayer(); + IClaim claim = RealEstate.claimAPI.getClaimAt(event.getClickedBlock().getLocation()); + + if (!RealEstate.transactionsStore.anyTransaction(claim)) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorSignNoTransaction); + event.getClickedBlock().breakNaturally(); + event.setCancelled(true); + return; + } + + Transaction tr = RealEstate.transactionsStore.getTransaction(claim); + if (player.isSneaking()) + tr.preview(player); + else + tr.interact(player); + } + } + } + + /** + * Handles block break events for RealEstate signs. + *

+ * When a RealEstate sign is broken, this method checks whether the player breaking the sign + * has the proper permissions. If the player is not authorized, the event is cancelled. + * If authorized, the method attempts to cancel the transaction associated with the sign. + *

+ * + * @param event the block break event + */ + @EventHandler + public void onBreakBlock(BlockBreakEvent event) { + if (event.getBlock().getState() instanceof Sign) { + IClaim claim = RealEstate.claimAPI.getClaimAt(event.getBlock().getLocation()); + if (claim != null && !claim.isWilderness()) { + Transaction tr = RealEstate.transactionsStore.getTransaction(claim); + if (tr != null && event.getBlock().equals(tr.getHolder())) { + if (event.getPlayer() != null && tr.getOwner() != null && !event.getPlayer().getUniqueId().equals(tr.getOwner()) && + !RealEstate.perms.has(event.getPlayer(), "realestate.destroysigns")) { + Messages.sendMessage(event.getPlayer(), RealEstate.instance.messages.msgErrorSignNotAuthor); + event.setCancelled(true); + return; + } else if (event.getPlayer() != null && tr.getOwner() == null && !RealEstate.perms.has(event.getPlayer(), "realestate.admin")) { + Messages.sendMessage(event.getPlayer(), RealEstate.instance.messages.msgErrorSignNotAdmin); + event.setCancelled(true); + return; + } + // Sign has been broken; attempt to cancel the associated transaction. + if (!tr.tryCancelTransaction(event.getPlayer())) { + event.setCancelled(true); + } + } + } + } + } } diff --git a/src/me/EtienneDx/RealEstate/RealEstate.java b/src/me/EtienneDx/RealEstate/RealEstate.java index ba2429b..3339402 100644 --- a/src/me/EtienneDx/RealEstate/RealEstate.java +++ b/src/me/EtienneDx/RealEstate/RealEstate.java @@ -40,90 +40,136 @@ import net.milkbowl.vault.economy.Economy; import net.milkbowl.vault.permission.Permission; -public class RealEstate extends JavaPlugin -{ - public Logger log; +/** + * The main class for the RealEstate plugin. + *

+ * This plugin manages claim transactions such as selling, renting, leasing, and auctioning. + * It integrates with several claim management APIs (GriefPrevention, GriefDefender, WorldGuard, Towny) + * and external plugins (Vault, Essentials) to provide its functionality. + *

+ */ +public class RealEstate extends JavaPlugin { + + /** Default constructor for the RealEstate plugin. */ + public RealEstate() {} + + /** Logger instance for the plugin. */ + public Logger log; + + /** The configuration instance for RealEstate. */ public Config config; - public Messages messages; + + /** The messages instance for customizable messages. */ + public Messages messages; + + /** Command manager used to register and manage plugin commands. */ BukkitCommandManager manager; - public final static String pluginDirPath = "plugins" + File.separator + "RealEstate" + File.separator; - final static String languagesDirectory = RealEstate.pluginDirPath + "languages"; + + /** + * The plugin directory path where RealEstate stores its files. + */ + public final static String pluginDirPath = "plugins" + File.separator + "RealEstate" + File.separator; + + /** + * The directory where language files are stored. + */ + final static String languagesDirectory = RealEstate.pluginDirPath + "languages"; + + /** Flag indicating if Vault is present. */ public static boolean vaultPresent = false; + + /** The economy provider from Vault. */ public static Economy econ = null; + + /** The permission provider from Vault. */ public static Permission perms = null; + + /** Essentials plugin instance, if available. */ public static Essentials ess = null; + /** Singleton instance of the RealEstate plugin. */ public static RealEstate instance = null; + /** The transactions store for managing claim transactions. */ public static TransactionsStore transactionsStore = null; - - public static IClaimAPI claimAPI = null; - - @SuppressWarnings("deprecation") - public void onEnable() - { - RealEstate.instance = this; + + /** The active claim management API implementation. */ + public static IClaimAPI claimAPI = null; + + /** + * Called when the plugin is enabled. + *

+ * Initializes the plugin instance, checks for required dependencies (Vault, economy, permissions), + * sets up the appropriate claim API, loads configuration files, registers command conditions and commands, + * and copies required resource files to the plugin directory. + *

+ */ + @SuppressWarnings("deprecation") + public void onEnable() { + RealEstate.instance = this; this.log = getLogger(); if (checkVault()) { this.log.info("Vault has been detected and enabled."); } - else - { - this.log.info("Vault has not been detected. RealEstate will not work without Vault."); - this.log.info("Disabling RealEstate..."); - getPluginLoader().disablePlugin(this); - return; - } - if (setupEconomy()) - { - this.log.info("Vault is using " + econ.getName() + " as the economy plugin."); - } - else - { - this.log.warning("No compatible economy plugin detected [Vault]."); - this.log.warning("Disabling RealEstate..."); - getPluginLoader().disablePlugin(this); - return; - } - if (setupPermissions()) - { - this.log.info("Vault is using " + perms.getName() + " for the permissions."); - } - else - { - this.log.warning("No compatible permissions plugin detected [Vault]."); - this.log.warning("Disabling RealEstate..."); - getPluginLoader().disablePlugin(this); - return; - } + else + { + this.log.info("Vault has not been detected. RealEstate will not work without Vault."); + this.log.info("Disabling RealEstate..."); + getPluginLoader().disablePlugin(this); + return; + } + if (setupEconomy()) + { + this.log.info("Vault is using " + econ.getName() + " as the economy plugin."); + } + else + { + this.log.warning("No compatible economy plugin detected [Vault]."); + this.log.warning("Disabling RealEstate..."); + getPluginLoader().disablePlugin(this); + return; + } + if (setupPermissions()) + { + this.log.info("Vault is using " + perms.getName() + " for the permissions."); + } + else + { + this.log.warning("No compatible permissions plugin detected [Vault]."); + this.log.warning("Disabling RealEstate..."); + getPluginLoader().disablePlugin(this); + return; + } - if(setupGriefPreventionAPI()) { - this.log.info("RealEstate is using GriefPrevention as a claim management plugin."); - } else if(setupGriefDefenderAPI()) { - this.log.info("RealEstate is using GriefDefender as a claim management plugin."); - } else if(setupWorldGuardAPI()) { - this.log.info("RealEstate is using WorldGuard as a claim management plugin."); - } else { - this.log.severe("No compatible Claim API detected. Please install GriefPrevention, GriefDefender, or WorldGuard."); - this.log.severe("Disabling RealEstate..."); - getPluginLoader().disablePlugin(this); - return; - } + if(setupGriefPreventionAPI()) { + this.log.info("RealEstate is using GriefPrevention as a claim management plugin."); + } else if(setupGriefDefenderAPI()) { + this.log.info("RealEstate is using GriefDefender as a claim management plugin."); + } else if(setupWorldGuardAPI()) { + this.log.info("RealEstate is using WorldGuard as a claim management plugin."); + } else if(setupTownyAPI()) { + this.log.info("RealEstate is using Towny as a claim management plugin."); + } else { + this.log.severe("No compatible Claim API detected. Please install GriefPrevention, GriefDefender, or WorldGuard."); + this.log.severe("Disabling RealEstate..."); + getPluginLoader().disablePlugin(this); + return; + } if((ess = (Essentials)getServer().getPluginManager().getPlugin("Essentials")) != null) { - this.log.info("Found Essentials, using version " + ess.getDescription().getVersion()); + this.log.info("Found Essentials, using version " + ess.getDescription().getVersion()); } checkForOldFiles(); this.config = new Config(); - this.config.loadConfig();// loads config or default - this.config.saveConfig();// save eventual default + this.config.loadConfig(); // loads config or default + this.config.saveConfig(); // save eventual default - this.messages = new Messages(); - this.messages.loadConfig();// loads customizable messages or defaults - this.messages.saveConfig();// save eventual default + this.messages = new Messages(); + this.messages.loadConfig(); // loads customizable messages or defaults + this.messages.saveConfig(); // save eventual default ConfigurationSerialization.registerClass(ClaimSell.class); ConfigurationSerialization.registerClass(ClaimRent.class); @@ -140,166 +186,173 @@ public void onEnable() registerConditions(); manager.registerCommand(new RECommand()); - copyResourcesIntoPluginDirectory(); - } - + copyResourcesIntoPluginDirectory(); + } + + /** + * Checks for the existence of old configuration files and renames them if necessary. + */ private void checkForOldFiles() { - File oldConfig = new File("plugins" + File.separator + "RealEstate" + File.separator + "transactions.data"); - if(oldConfig.exists()) - { - this.log.info("Found old transactions.data file, reformatting it..."); - // rename old file - oldConfig.renameTo(new File("plugins" + File.separator + "RealEstate" + File.separator + "transactions.yml")); - } - - } - - private void registerConditions() - { + File oldConfig = new File("plugins" + File.separator + "RealEstate" + File.separator + "transactions.data"); + if(oldConfig.exists()) + { + this.log.info("Found old transactions.data file, reformatting it..."); + // Rename old file to new format + oldConfig.renameTo(new File("plugins" + File.separator + "RealEstate" + File.separator + "transactions.yml")); + } + } + + /** + * Registers command conditions for use in plugin commands. + */ + private void registerConditions() { manager.getCommandConditions().addCondition("inClaim", (context) -> { - if(context.getIssuer().isPlayer() && - claimAPI.getClaimAt(context.getIssuer().getPlayer().getLocation()) != null) - { - return; - } - throw new ConditionFailedException(Messages.getMessage(messages.msgErrorOutOfClaim)); + if(context.getIssuer().isPlayer() && + claimAPI.getClaimAt(context.getIssuer().getPlayer().getLocation()) != null) + { + return; + } + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorOutOfClaim)); }); manager.getCommandConditions().addCondition("claimHasTransaction", (context) -> { - if(!context.getIssuer().isPlayer()) - { - throw new ConditionFailedException(Messages.getMessage(messages.msgErrorPlayerOnly)); - } - IClaim c = claimAPI.getClaimAt(context.getIssuer().getPlayer().getLocation()); - if(c == null || c.isWilderness()) - { - throw new ConditionFailedException(Messages.getMessage(messages.msgErrorOutOfClaim)); - } - Transaction tr = transactionsStore.getTransaction(c); - if(tr == null) - { - throw new ConditionFailedException(Messages.getMessage(messages.msgErrorNoOngoingTransaction)); - } + if(!context.getIssuer().isPlayer()) + { + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorPlayerOnly)); + } + IClaim c = claimAPI.getClaimAt(context.getIssuer().getPlayer().getLocation()); + if(c == null || c.isWilderness()) + { + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorOutOfClaim)); + } + Transaction tr = transactionsStore.getTransaction(c); + if(tr == null) + { + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorNoOngoingTransaction)); + } }); manager.getCommandConditions().addCondition("inPendingTransactionClaim", (context) -> { - if(!context.getIssuer().isPlayer()) - { - throw new ConditionFailedException(Messages.getMessage(messages.msgErrorPlayerOnly)); - } - IClaim c = claimAPI.getClaimAt(context.getIssuer().getPlayer().getLocation()); - if(c == null || c.isWilderness()) - { - throw new ConditionFailedException(Messages.getMessage(messages.msgErrorOutOfClaim)); - } - Transaction tr = transactionsStore.getTransaction(c); - if(tr == null) - { - throw new ConditionFailedException(Messages.getMessage(messages.msgErrorNotRentNorLease)); - } - else if(tr instanceof BoughtTransaction && ((BoughtTransaction)tr).getBuyer() != null) - { - throw new ConditionFailedException(Messages.getMessage(messages.msgErrorAlreadyBought)); - } + if(!context.getIssuer().isPlayer()) + { + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorPlayerOnly)); + } + IClaim c = claimAPI.getClaimAt(context.getIssuer().getPlayer().getLocation()); + if(c == null || c.isWilderness()) + { + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorOutOfClaim)); + } + Transaction tr = transactionsStore.getTransaction(c); + if(tr == null) + { + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorNotRentNorLease)); + } + else if(tr instanceof BoughtTransaction && ((BoughtTransaction)tr).getBuyer() != null) + { + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorAlreadyBought)); + } }); manager.getCommandConditions().addCondition("inBoughtClaim", (context) -> { - if(!context.getIssuer().isPlayer()) - { - throw new ConditionFailedException(Messages.getMessage(messages.msgErrorPlayerOnly)); - } - IClaim c = claimAPI.getClaimAt(context.getIssuer().getPlayer().getLocation()); - if(c == null || c.isWilderness()) - { - throw new ConditionFailedException(Messages.getMessage(messages.msgErrorOutOfClaim)); - } - Transaction tr = transactionsStore.getTransaction(c); - if(tr == null || !(tr instanceof BoughtTransaction)) - { - throw new ConditionFailedException(Messages.getMessage(messages.msgErrorNotRentNorLease)); - } + if(!context.getIssuer().isPlayer()) + { + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorPlayerOnly)); + } + IClaim c = claimAPI.getClaimAt(context.getIssuer().getPlayer().getLocation()); + if(c == null || c.isWilderness()) + { + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorOutOfClaim)); + } + Transaction tr = transactionsStore.getTransaction(c); + if(tr == null || !(tr instanceof BoughtTransaction)) + { + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorNotRentNorLease)); + } }); manager.getCommandConditions().addCondition("partOfBoughtTransaction", context -> { - if(!context.getIssuer().isPlayer()) - { - throw new ConditionFailedException(Messages.getMessage(messages.msgErrorPlayerOnly)); - } - IClaim c = claimAPI.getClaimAt(context.getIssuer().getPlayer().getLocation()); - if(c == null || c.isWilderness()) - { - throw new ConditionFailedException(Messages.getMessage(messages.msgErrorOutOfClaim)); - } - Transaction tr = transactionsStore.getTransaction(c); - if(tr == null) - { - throw new ConditionFailedException(Messages.getMessage(messages.msgErrorNoOngoingTransaction)); - } - if(!(tr instanceof BoughtTransaction)) - { - throw new ConditionFailedException(Messages.getMessage(messages.msgErrorNotRentNorLease)); - } - if((((BoughtTransaction)tr).buyer != null && ((BoughtTransaction)tr).buyer.equals(context.getIssuer().getPlayer().getUniqueId())) || - (tr.getOwner() != null && (tr.getOwner().equals(context.getIssuer().getPlayer().getUniqueId()))) || - (c.isAdminClaim() && RealEstate.perms.has(context.getIssuer().getPlayer(), "realestate.admin"))) - { - return; - } - throw new ConditionFailedException(Messages.getMessage(messages.msgErrorNotPartOfTransaction)); + if(!context.getIssuer().isPlayer()) + { + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorPlayerOnly)); + } + IClaim c = claimAPI.getClaimAt(context.getIssuer().getPlayer().getLocation()); + if(c == null || c.isWilderness()) + { + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorOutOfClaim)); + } + Transaction tr = transactionsStore.getTransaction(c); + if(tr == null) + { + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorNoOngoingTransaction)); + } + if(!(tr instanceof BoughtTransaction)) + { + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorNotRentNorLease)); + } + if((((BoughtTransaction)tr).buyer != null && ((BoughtTransaction)tr).buyer.equals(context.getIssuer().getPlayer().getUniqueId())) || + (tr.getOwner() != null && (tr.getOwner().equals(context.getIssuer().getPlayer().getUniqueId()))) || + (c.isAdminClaim() && RealEstate.perms.has(context.getIssuer().getPlayer(), "realestate.admin"))) + { + return; + } + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorNotPartOfTransaction)); }); manager.getCommandConditions().addCondition("partOfRent", context -> { - if(!context.getIssuer().isPlayer()) - { - throw new ConditionFailedException(Messages.getMessage(messages.msgErrorPlayerOnly)); - } - IClaim c = claimAPI.getClaimAt(context.getIssuer().getPlayer().getLocation()); - if(c == null || c.isWilderness()) - { - throw new ConditionFailedException(Messages.getMessage(messages.msgErrorOutOfClaim)); - } - Transaction tr = transactionsStore.getTransaction(c); - if(tr == null) - { - throw new ConditionFailedException(Messages.getMessage(messages.msgErrorNoOngoingTransaction)); - } - if(!(tr instanceof ClaimRent)) - { - throw new ConditionFailedException(Messages.getMessage(messages.msgErrorRentOnly)); - } - if((((ClaimRent)tr).buyer != null && ((ClaimRent)tr).buyer.equals(context.getIssuer().getPlayer().getUniqueId())) || - (tr.getOwner() != null && (tr.getOwner().equals(context.getIssuer().getPlayer().getUniqueId()))) || - (c.isAdminClaim() && RealEstate.perms.has(context.getIssuer().getPlayer(), "realestate.admin"))) - { - return; - } - throw new ConditionFailedException(Messages.getMessage(messages.msgErrorNotPartOfTransaction)); + if(!context.getIssuer().isPlayer()) + { + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorPlayerOnly)); + } + IClaim c = claimAPI.getClaimAt(context.getIssuer().getPlayer().getLocation()); + if(c == null || c.isWilderness()) + { + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorOutOfClaim)); + } + Transaction tr = transactionsStore.getTransaction(c); + if(tr == null) + { + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorNoOngoingTransaction)); + } + if(!(tr instanceof ClaimRent)) + { + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorRentOnly)); + } + if((((ClaimRent)tr).buyer != null && ((ClaimRent)tr).buyer.equals(context.getIssuer().getPlayer().getUniqueId())) || + (tr.getOwner() != null && (tr.getOwner().equals(context.getIssuer().getPlayer().getUniqueId()))) || + (c.isAdminClaim() && RealEstate.perms.has(context.getIssuer().getPlayer(), "realestate.admin"))) + { + return; + } + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorNotPartOfTransaction)); }); manager.getCommandConditions().addCondition(Double.class, "positiveDouble", (c, exec, value) -> { - if(value > 0) return; - throw new ConditionFailedException(Messages.getMessage(messages.msgErrorValueGreaterThanZero)); + if(value > 0) return; + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorValueGreaterThanZero)); }); - manager.getCommandConditions().addCondition("claimIsAuctioned", (context) -> { - if(!context.getIssuer().isPlayer()) - { - throw new ConditionFailedException(Messages.getMessage(messages.msgErrorPlayerOnly)); - } - IClaim c = claimAPI.getClaimAt(context.getIssuer().getPlayer().getLocation()); - if(c == null || c.isWilderness()) - { - throw new ConditionFailedException(Messages.getMessage(messages.msgErrorOutOfClaim)); - } - Transaction tr = transactionsStore.getTransaction(c); - if(tr == null) - { - throw new ConditionFailedException(Messages.getMessage(messages.msgErrorNoOngoingTransaction)); - } - if(!(tr instanceof ClaimAuction)) - { - throw new ConditionFailedException(Messages.getMessage(messages.msgErrorAuctionOnly)); - } - }); - } - - public void addLogEntry(String entry) - { - try - { + manager.getCommandConditions().addCondition("claimIsAuctioned", (context) -> { + if(!context.getIssuer().isPlayer()) + { + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorPlayerOnly)); + } + IClaim c = claimAPI.getClaimAt(context.getIssuer().getPlayer().getLocation()); + if(c == null || c.isWilderness()) + { + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorOutOfClaim)); + } + Transaction tr = transactionsStore.getTransaction(c); + if(tr == null) + { + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorNoOngoingTransaction)); + } + if(!(tr instanceof ClaimAuction)) + { + throw new ConditionFailedException(Messages.getMessage(messages.msgErrorAuctionOnly)); + } + }); + } + + /** + * Adds a log entry to the log file specified in the configuration. + * + * @param entry the log entry to add + */ + public void addLogEntry(String entry) { + try { File logFile = new File(this.config.logFilePath); if (!logFile.exists()) { logFile.createNewFile(); @@ -311,40 +364,68 @@ public void addLogEntry(String entry) pw.flush(); pw.close(); } - catch (IOException e) - { + catch (IOException e) { e.printStackTrace(); } } - - private boolean checkVault() - { + + /** + * Checks whether Vault is installed. + * + * @return true if Vault is present; false otherwise. + */ + private boolean checkVault() { vaultPresent = getServer().getPluginManager().getPlugin("Vault") != null; return vaultPresent; } - - private boolean setupGriefPreventionAPI() - { - if(getServer().getPluginManager().getPlugin("GriefPrevention") != null) - { - claimAPI = new GriefPreventionAPI(); - return true; - } - return false; - } - - private boolean setupGriefDefenderAPI() - { - if(getServer().getPluginManager().getPlugin("GriefDefender") != null) - { - claimAPI = new GriefDefenderAPI(); - return true; - } - return false; - } - - private boolean setupEconomy() - { + + /** + * Sets up the GriefPrevention API if available. + * + * @return true if GriefPrevention is installed and set up; false otherwise. + */ + private boolean setupGriefPreventionAPI() { + if(getServer().getPluginManager().getPlugin("GriefPrevention") != null) + { + claimAPI = new GriefPreventionAPI(); + return true; + } + return false; + } + + /** + * Sets up the GriefDefender API if available. + * + * @return true if GriefDefender is installed and set up; false otherwise. + */ + private boolean setupGriefDefenderAPI() { + if(getServer().getPluginManager().getPlugin("GriefDefender") != null) + { + claimAPI = new GriefDefenderAPI(); + return true; + } + return false; + } + + /** + * Sets up the Towny API if available. + * + * @return true if Towny is installed and set up; false otherwise. + */ + private boolean setupTownyAPI() { + if(getServer().getPluginManager().getPlugin("Towny") != null) { + claimAPI = new me.EtienneDx.RealEstate.ClaimAPI.Towny.TownyAPIWrapper(); + return true; + } + return false; + } + + /** + * Sets up the economy provider using Vault. + * + * @return true if an economy provider is found; false otherwise. + */ + private boolean setupEconomy() { RegisteredServiceProvider rsp = getServer().getServicesManager().getRegistration(Economy.class); if (rsp == null) { return false; @@ -352,14 +433,23 @@ private boolean setupEconomy() econ = (Economy)rsp.getProvider(); return econ != null; } - - private boolean setupPermissions() - { + + /** + * Sets up the permissions provider using Vault. + * + * @return true if a permissions provider is found; false otherwise. + */ + private boolean setupPermissions() { RegisteredServiceProvider rsp = getServer().getServicesManager().getRegistration(Permission.class); perms = (Permission)rsp.getProvider(); return perms != null; } + /** + * Sets up the WorldGuard API if available. + * + * @return true if WorldGuard is installed and set up; false otherwise. + */ private boolean setupWorldGuardAPI() { if(getServer().getPluginManager().getPlugin("WorldGuard") != null) { claimAPI = new me.EtienneDx.RealEstate.ClaimAPI.WorldGuard.WorldGuardAPI(); @@ -367,75 +457,77 @@ private boolean setupWorldGuardAPI() { } return false; } + + /** + * Copies resource files (such as language files) into the plugin directory. + */ + private void copyResourcesIntoPluginDirectory() { + this.log.info("Checking language files..."); + Path pluginPath = Paths.get(RealEstate.pluginDirPath); + File pluginDirectory = pluginPath.toFile(); + if(!pluginDirectory.exists()) + { + pluginDirectory.mkdirs(); + } + // For each file in the resource folder + FileSystem fileSystem = null; + try + { + URI uri = RealEstate.class.getResource("/resources").toURI(); + Path myPath; - private void copyResourcesIntoPluginDirectory() - { - this.log.info("Checking language files..."); - Path pluginPath = Paths.get(RealEstate.pluginDirPath); - File pluginDirectory = pluginPath.toFile(); - if(!pluginDirectory.exists()) - { - pluginDirectory.mkdirs(); - } - // for each file in the resource folder - FileSystem fileSystem = null; - try - { - URI uri = RealEstate.class.getResource("/resources").toURI(); - Path myPath; - - if (uri.getScheme().equals("jar")) - { - fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap()); - myPath = fileSystem.getPath("/resources"); - } - else - { - myPath = Paths.get(uri); - } - - try(Stream walk = Files.walk(myPath, 3)) - { - Iterator it = walk.iterator(); - it.next();// skip first - for (; it.hasNext();) - { - Path path = it.next(); - Path targetPath = pluginPath.resolve(path.toString().substring(11)); - if(!targetPath.toFile().exists()) - { - Files.createDirectories(targetPath.getParent()); - try(InputStream s = RealEstate.class.getResourceAsStream(path.toString())) - { - if(s.available() > 0) - { - Files.copy(s, targetPath); - this.log.info("Adding language file: " + targetPath.getFileName()); - } - } - catch(NoSuchFileException e) - { - // ignore - } - } - } - } - } - catch(Exception e) - { - this.log.warning("Couldn't copy resources to plugin directory..."); - e.printStackTrace(); - } - if(fileSystem != null) - { - try - { - fileSystem.close(); - } - catch(IOException e) - { - // do nothing in case of error - } - } - } + if (uri.getScheme().equals("jar")) + { + fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap()); + myPath = fileSystem.getPath("/resources"); + } + else + { + myPath = Paths.get(uri); + } + + try(Stream walk = Files.walk(myPath, 3)) + { + Iterator it = walk.iterator(); + it.next(); // Skip first + for (; it.hasNext();) + { + Path path = it.next(); + Path targetPath = pluginPath.resolve(path.toString().substring(11)); + if(!targetPath.toFile().exists()) + { + Files.createDirectories(targetPath.getParent()); + try(InputStream s = RealEstate.class.getResourceAsStream(path.toString())) + { + if(s.available() > 0) + { + Files.copy(s, targetPath); + this.log.info("Adding language file: " + targetPath.getFileName()); + } + } + catch(NoSuchFileException e) + { + // ignore + } + } + } + } + } + catch(Exception e) + { + this.log.warning("Couldn't copy resources to plugin directory..."); + e.printStackTrace(); + } + if(fileSystem != null) + { + try + { + fileSystem.close(); + } + catch(IOException e) + { + // Do nothing in case of error + } + } + } } diff --git a/src/me/EtienneDx/RealEstate/RealEstateSign.java b/src/me/EtienneDx/RealEstate/RealEstateSign.java index 3b06040..d8f66bb 100644 --- a/src/me/EtienneDx/RealEstate/RealEstateSign.java +++ b/src/me/EtienneDx/RealEstate/RealEstateSign.java @@ -5,33 +5,63 @@ import org.bukkit.block.sign.SignSide; import org.bukkit.ChatColor; +/** + * A utility wrapper for a Bukkit {@link Sign} used by the RealEstate plugin. + *

+ * This class provides methods to set sign lines, update the sign, and check whether + * a given sign is a RealEstate sign (by comparing its header). + *

+ */ public class RealEstateSign { - private Sign sign; - private SignSide front; + private Sign sign; + private SignSide front; - public RealEstateSign(Sign sign) { - this.sign = sign; - this.front = sign.getSide(Side.FRONT); - } + /** + * Constructs a new RealEstateSign wrapper for the provided sign. + * + * @param sign the sign to wrap + */ + public RealEstateSign(Sign sign) { + this.sign = sign; + this.front = sign.getSide(Side.FRONT); + } - public void setLine(int line, String value) { - front.setLine(line, value); - } + /** + * Sets the text on the specified line of the sign. + * + * @param line the line index to set (typically 0-3) + * @param value the text value to display on the line + */ + public void setLine(int line, String value) { + front.setLine(line, value); + } - public void update(boolean force) { - sign.update(force); - } + /** + * Updates the sign state. + * + * @param force if true, forces the update even if the sign is not changed + */ + public void update(boolean force) { + sign.update(force); + } - public boolean isRealEstateSign() { - String header = ChatColor.stripColor(Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); + /** + * Checks if this sign is a RealEstate sign. + *

+ * It compares the header text of the sign with the expected header defined in the configuration. + *

+ * + * @return true if the sign's header matches the RealEstate header; false otherwise + */ + public boolean isRealEstateSign() { + String header = ChatColor.stripColor(Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); - SignSide front = sign.getSide(Side.FRONT); + SignSide front = sign.getSide(Side.FRONT); - if(ChatColor.stripColor(front.getLine(0)).equalsIgnoreCase(header)) { - return true; - } - return false; - } - -} \ No newline at end of file + if(ChatColor.stripColor(front.getLine(0)).equalsIgnoreCase(header)) { + return true; + } + return false; + } +} diff --git a/src/me/EtienneDx/RealEstate/Transactions/BoughtTransaction.java b/src/me/EtienneDx/RealEstate/Transactions/BoughtTransaction.java index d2618ad..c355711 100644 --- a/src/me/EtienneDx/RealEstate/Transactions/BoughtTransaction.java +++ b/src/me/EtienneDx/RealEstate/Transactions/BoughtTransaction.java @@ -6,11 +6,35 @@ import org.bukkit.entity.Player; import me.EtienneDx.RealEstate.ClaimAPI.IClaim; +/** + * Represents a transaction for a claim that has been bought. + *

+ * This abstract class extends {@link ClaimTransaction} and adds additional properties + * specific to transactions where a claim has been purchased, including the buyer's UUID, + * an exit offer, and a flag indicating whether the transaction sign has been destroyed. + *

+ */ public abstract class BoughtTransaction extends ClaimTransaction { + /** + * The UUID of the buyer, or {@code null} if no buyer has been assigned. + */ public UUID buyer = null; + + /** + * An optional exit offer for the transaction. + */ public ExitOffer exitOffer = null; + + /** + * Flag indicating whether the sign representing this transaction has been destroyed. + */ public boolean destroyedSign = false; + /** + * Constructs a {@code BoughtTransaction} from a serialized map. + * + * @param map the map containing serialized data for this transaction + */ public BoughtTransaction(Map map) { super(map); if(map.get("buyer") != null) @@ -21,10 +45,26 @@ public BoughtTransaction(Map map) { destroyedSign = (boolean) map.get("destroyedSign"); } + /** + * Constructs a new {@code BoughtTransaction} with the specified claim, player, price, and sign location. + * + * @param claim the claim involved in this transaction + * @param player the player initiating the transaction + * @param price the price of the transaction + * @param sign the location of the transaction sign + */ public BoughtTransaction(IClaim claim, Player player, double price, Location sign) { super(claim, player, price, sign); } + /** + * Serializes this transaction to a map. + *

+ * This method includes additional properties specific to bought transactions. + *

+ * + * @return a map representing the serialized form of this transaction + */ @Override public Map serialize() { Map map = super.serialize(); @@ -36,6 +76,13 @@ public Map serialize() { return map; } + /** + * Destroys the transaction sign if it has not already been destroyed. + *

+ * This method checks whether the sign is configured to be destroyed and, + * if so, it breaks the sign naturally in the world. + *

+ */ public void destroySign() { // Example: destroy sign if configured if(!destroyedSign && getHolder() != null) @@ -43,6 +90,11 @@ public void destroySign() { destroyedSign = true; } + /** + * Returns the UUID of the buyer. + * + * @return the buyer's UUID, or {@code null} if no buyer is assigned + */ public UUID getBuyer() { return buyer; } diff --git a/src/me/EtienneDx/RealEstate/Transactions/ClaimAuction.java b/src/me/EtienneDx/RealEstate/Transactions/ClaimAuction.java index 5e62a4c..4ed6597 100644 --- a/src/me/EtienneDx/RealEstate/Transactions/ClaimAuction.java +++ b/src/me/EtienneDx/RealEstate/Transactions/ClaimAuction.java @@ -23,93 +23,124 @@ import me.EtienneDx.RealEstate.ClaimAPI.IClaim; import net.md_5.bungee.api.ChatColor; +/** + * Represents an auction transaction for a claim. + *

+ * This transaction handles the bidding process and finalizing an auction. + *

+ */ public class ClaimAuction extends ClaimTransaction { + /** + * The UUID of the current highest bidder, or {@code null} if no bid has been made. + */ public UUID buyer = null; - public LocalDateTime endDate = null; + + /** + * The time when the auction ends. + */ + public LocalDateTime endDate = null; + + /** + * The minimum bid increment required. + */ public double bidStep = 1; + /** + * Constructs a ClaimAuction from a serialized map. + * + * @param map the map containing serialized transaction data + */ public ClaimAuction(Map map) { super(map); - if(map.get("buyer") != null) - buyer = UUID.fromString((String)map.get("buyer")); - if(map.get("endDate") != null) - endDate = LocalDateTime.parse((String) map.get("endDate"), DateTimeFormatter.ISO_DATE_TIME); + if (map.get("buyer") != null) + buyer = UUID.fromString((String) map.get("buyer")); + if (map.get("endDate") != null) + endDate = LocalDateTime.parse((String) map.get("endDate"), DateTimeFormatter.ISO_DATE_TIME); bidStep = (double) map.get("bidStep"); } + /** + * Constructs a new ClaimAuction. + * + * @param claim the claim being auctioned + * @param player the player initiating the auction + * @param price the starting price of the auction + * @param sign the location of the auction sign + * @param endDate the end time of the auction + * @param bidStep the bid increment step + */ public ClaimAuction(IClaim claim, Player player, double price, Location sign, LocalDateTime endDate, double bidStep) { super(claim, player, price, sign); this.endDate = endDate; this.bidStep = bidStep; } + /** + * Serializes this ClaimAuction into a map for storage. + * + * @return a map containing the serialized properties of the auction + */ @Override public Map serialize() { Map map = super.serialize(); - if(buyer != null) - map.put("buyer", buyer.toString()); - if(endDate != null) - map.put("endDate", endDate.format(DateTimeFormatter.ISO_DATE_TIME)); + if (buyer != null) + map.put("buyer", buyer.toString()); + if (endDate != null) + map.put("endDate", endDate.format(DateTimeFormatter.ISO_DATE_TIME)); map.put("bidStep", bidStep); - return map; + return map; } + /** + * Updates the auction status. + *

+ * This method checks if the auction has ended and, if so, finalizes the auction + * by transferring claim ownership and updating the auction sign accordingly. + * Otherwise, it updates the sign with the remaining time and current bid details. + *

+ * + * @return {@code true} if the auction ended and was finalized; {@code false} otherwise + */ @Override public boolean update() { int days = Period.between(LocalDate.now(), endDate.toLocalDate()).getDays(); Duration hours = Duration.between(LocalTime.now(), endDate.toLocalTime()); - if(hours.isNegative() && !hours.isZero()) - { + if (hours.isNegative() && !hours.isZero()) { hours = hours.plusHours(24); days--; } - if(days < 0) - { - if(buyer != null) - { - IClaim claim = RealEstate.claimAPI.getClaimAt(sign);// getting by id creates errors for subclaims + if (days < 0) { + if (buyer != null) { + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); // Getting by id creates errors for subclaims OfflinePlayer buyerPlayer = Bukkit.getOfflinePlayer(buyer); OfflinePlayer ownerPlayer = owner != null ? Bukkit.getOfflinePlayer(owner) : null; - if(claim == null || claim.isWilderness()) - { - if(!Utils.makePayment(buyer, null, price, false, false)) - { + if (claim == null || claim.isWilderness()) { + if (!Utils.makePayment(buyer, null, price, false, false)) { RealEstate.instance.log.warning("Couldn't reimburse " + price + " to " + buyerPlayer.getName() + " for the auction of a deleted claim"); } - if(buyerPlayer.isOnline()) - { + if (buyerPlayer.isOnline()) { Messages.sendMessage(buyerPlayer.getPlayer(), RealEstate.instance.messages.msgErrorClaimDoesNotExistAuction); } RealEstate.transactionsStore.cancelTransaction(claim); - } - else if(!Utils.makePayment(owner, null, price, false, false)) - { + } else if (!Utils.makePayment(owner, null, price, false, false)) { RealEstate.instance.log.warning("Couldn't pay " + price + " to " + claim.getOwnerName() + " for the auction of a claim"); - if(buyerPlayer.isOnline()) - { + if (buyerPlayer.isOnline()) { Messages.sendMessage(buyerPlayer.getPlayer(), RealEstate.instance.messages.msgErrorAuctionCouldntPayOwner); - if(!Utils.makePayment(buyer, null, price, false, false)) - { + if (!Utils.makePayment(buyer, null, price, false, false)) { RealEstate.instance.log.warning("Couldn't reimburse " + price + " to " + buyerPlayer.getName() + " for the cancellation of an auction"); } - if(buyerPlayer.isOnline()) - { + if (buyerPlayer.isOnline()) { Messages.sendMessage(buyerPlayer.getPlayer(), RealEstate.instance.messages.msgInfoClaimInfoAuctionCancelled); } } - if(owner != null && ownerPlayer != null && ownerPlayer.isOnline()) - { + if (owner != null && ownerPlayer != null && ownerPlayer.isOnline()) { Messages.sendMessage(ownerPlayer.getPlayer(), RealEstate.instance.messages.msgErrorAuctionCouldntReceiveOwner); } RealEstate.transactionsStore.cancelTransaction(claim); - } - else - { - + } else { Utils.transferClaim(claim, buyer, owner); - if(getHolder().getState() instanceof Sign) - { + if (getHolder().getState() instanceof Sign) { RealEstateSign s = new RealEstateSign((Sign) getHolder().getState()); s.setLine(0, ""); s.setLine(1, Messages.getMessage(RealEstate.instance.messages.msgSignAuctionWon, false)); @@ -120,8 +151,7 @@ else if(!Utils.makePayment(owner, null, price, false, false)) return true; } } - if(getHolder().getState() instanceof Sign) - { + if (getHolder().getState() instanceof Sign) { RealEstateSign s = new RealEstateSign((Sign) getHolder().getState()); s.setLine(0, ""); s.setLine(1, Messages.getMessage(RealEstate.instance.messages.msgSignAuctionEnded, false)); @@ -130,131 +160,131 @@ else if(!Utils.makePayment(owner, null, price, false, false)) s.update(true); } return true; - } - else - { - // update sign - if(sign.getBlock().getState() instanceof Sign) - { + } else { + // Update the auction sign with current details. + if (sign.getBlock().getState() instanceof Sign) { RealEstateSign s = new RealEstateSign((Sign) sign.getBlock().getState()); s.setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); s.setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceAuction); String remaining = Utils.getTime(days, hours, false); s.setLine(2, Messages.getMessage(RealEstate.instance.messages.msgSignAuctionRemainingTime, false, remaining)); - if(buyer != null) - { + if (buyer != null) { String name = Bukkit.getOfflinePlayer(buyer).getName(); s.setLine(3, Messages.getMessage(RealEstate.instance.messages.msgSignAuctionHighestBidder, false, name, RealEstate.econ.format(price))); - } - else - { + } else { s.setLine(3, Messages.getMessage(RealEstate.instance.messages.msgSignAuctionNoBider, false)); } s.update(true); - } - else - { + } else { RealEstate.transactionsStore.cancelTransaction(this); } return false; } } + /** + * Attempts to cancel the auction transaction. + * + * @param p the player attempting the cancellation + * @param force if {@code true}, forces cancellation regardless of conditions + * @return {@code true} if the transaction was cancelled successfully; {@code false} otherwise + */ @Override public boolean tryCancelTransaction(Player p, boolean force) { - if(buyer == null || RealEstate.instance.config.cfgCancelAuction || force || p.hasPermission("realestate.admin")) - { - if(buyer != null) - { + if (buyer == null || RealEstate.instance.config.cfgCancelAuction || force || p.hasPermission("realestate.admin")) { + if (buyer != null) { OfflinePlayer buyerPlayer = Bukkit.getOfflinePlayer(buyer); - if(!Utils.makePayment(buyer, null, price, false, false)) - { + if (!Utils.makePayment(buyer, null, price, false, false)) { RealEstate.instance.log.warning("Couldn't reimburse " + price + " to " + buyerPlayer.getName() + " for the cancellation of an auction"); } - if(buyerPlayer.isOnline()) - { + if (buyerPlayer.isOnline()) { Messages.sendMessage(buyerPlayer.getPlayer(), RealEstate.instance.messages.msgInfoClaimInfoAuctionCancelled); } } RealEstate.transactionsStore.cancelTransaction(this); return true; - } - else if(p != null) - { + } else if (p != null) { p.sendMessage(Messages.getMessage(RealEstate.instance.messages.msgErrorCantCancelAuction)); } return false; } + /** + * Processes interaction with the auction sign. + *

+ * If the claim is invalid or the player is the owner, displays an error message. + * Otherwise, it initiates a bid by increasing the current price. + *

+ * + * @param player the player interacting with the auction sign + */ @Override public void interact(Player player) { - IClaim claim = RealEstate.claimAPI.getClaimAt(sign);// getting by id creates errors for subclaims - if(claim == null || claim.isWilderness()) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimDoesNotExist); + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); // Getting by id creates errors for subclaims + if (claim == null || claim.isWilderness()) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimDoesNotExist); RealEstate.transactionsStore.cancelTransaction(claim); return; - } - String claimTypeDisplay = claim.isParentClaim() ? - RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; - - if (owner != null && owner.equals(player.getUniqueId())) - { + } + String claimTypeDisplay = claim.isParentClaim() ? + RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; + + if (owner != null && owner.equals(player.getUniqueId())) { Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimAlreadyOwner, claimTypeDisplay); return; } - if(claim.isParentClaim() && owner != null && !owner.equals(claim.getOwner())) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNotAuctionedByOwner, claimTypeDisplay); + if (claim.isParentClaim() && owner != null && !owner.equals(claim.getOwner())) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNotAuctionedByOwner, claimTypeDisplay); RealEstate.transactionsStore.cancelTransaction(claim); return; - } + } bid(player, price + bidStep); } + /** + * Processes a bid from a player. + *

+ * This method validates the bid, processes payment, updates the highest bidder, + * and then updates the auction status. + *

+ * + * @param player the player placing the bid + * @param price the bid amount + */ public void bid(Player player, double price) { - IClaim claim = RealEstate.claimAPI.getClaimAt(sign);// getting by id creates errors for subclaims - if(claim == null || claim.isWilderness()) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimDoesNotExist); + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); // Getting by id creates errors for subclaims + if (claim == null || claim.isWilderness()) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimDoesNotExist); RealEstate.transactionsStore.cancelTransaction(claim); return; - } + } String claimType = claim.isParentClaim() ? "claim" : "subclaim"; - String claimTypeDisplay = claim.isParentClaim() ? - RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; + String claimTypeDisplay = claim.isParentClaim() ? + RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; - if(!player.hasPermission("realestate." + claimType + ".bid")) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNoAuctionPermission, claimTypeDisplay); + if (!player.hasPermission("realestate." + claimType + ".bid")) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNoAuctionPermission, claimTypeDisplay); return; - } - if(RealEstate.instance.config.cfgDisableOutbidSelf && player.getUniqueId().equals(buyer)) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimAlreadyHighestBidder, claimTypeDisplay); + } + if (RealEstate.instance.config.cfgDisableOutbidSelf && player.getUniqueId().equals(buyer)) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimAlreadyHighestBidder, claimTypeDisplay); return; - } - if(RealEstate.econ.getBalance(player) < price) - { + } + if (RealEstate.econ.getBalance(player) < price) { Messages.sendMessage(player, RealEstate.instance.messages.msgErrorNoMoneySelf, RealEstate.econ.format(price)); return; } - if(!Utils.makePayment(null, player.getUniqueId(), price, false, false)) - { + if (!Utils.makePayment(null, player.getUniqueId(), price, false, false)) { Messages.sendMessage(player, RealEstate.instance.messages.msgErrorNoMoneySelf, RealEstate.econ.format(price)); return; } - if(buyer != null && !Utils.makePayment(buyer, null, this.price, false, false)) - { + if (buyer != null && !Utils.makePayment(buyer, null, this.price, false, false)) { Messages.sendMessage(player, RealEstate.instance.messages.msgErrorCouldntReimburseOther, RealEstate.econ.format(this.price)); - - if(!Utils.makePayment(player.getUniqueId(), null, price, false, false)) - { + if (!Utils.makePayment(player.getUniqueId(), null, price, false, false)) { RealEstate.instance.log.warning("Couldn't reimburse " + player.getName() + " for " + RealEstate.econ.format(price) + ", the money has been lost!"); Messages.sendMessage(player, RealEstate.instance.messages.msgErrorCouldntReimburseSelf, RealEstate.econ.format(price)); Messages.sendMessage(player, RealEstate.instance.messages.msgErrorContactAdmin); } - return; } buyer = player.getUniqueId(); @@ -263,25 +293,30 @@ public void bid(Player player, double price) { RealEstate.transactionsStore.saveData(); } + /** + * Provides a preview of the auction information to a player. + *

+ * This method sends the player details about the auction, including header, current bid, + * remaining time, bid step, and owner information. + *

+ * + * @param player the player requesting the preview + */ @Override public void preview(Player player) { IClaim claim = RealEstate.claimAPI.getClaimAt(sign); - if(player.hasPermission("realestate.info")) - { - String claimType = claim.isParentClaim() ? "claim" : "subclaim"; - String claimTypeDisplay = claim.isParentClaim() ? - RealEstate.instance.messages.keywordClaim : - RealEstate.instance.messages.keywordSubclaim; - String msg; - msg = Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoAuctionHeader) + "\n"; - if(buyer == null) - { + if (player.hasPermission("realestate.info")) { + String claimType = claim.isParentClaim() ? "claim" : "subclaim"; + String claimTypeDisplay = claim.isParentClaim() ? + RealEstate.instance.messages.keywordClaim : + RealEstate.instance.messages.keywordSubclaim; + String msg; + msg = Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoAuctionHeader) + "\n"; + if (buyer == null) { msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoAuctionNoBidder, claimTypeDisplay, RealEstate.econ.format(price)) + "\n"; - } - else - { + } else { OfflinePlayer buyer = Bukkit.getOfflinePlayer(this.buyer); msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoAuctionHighestBidder, claimTypeDisplay, @@ -291,16 +326,14 @@ public void preview(Player player) { int days = Period.between(LocalDate.now(), endDate.toLocalDate()).getDays(); Duration hours = Duration.between(LocalTime.now(), endDate.toLocalTime()); - if(hours.isNegative() && !hours.isZero()) - { + if (hours.isNegative() && !hours.isZero()) { hours = hours.plusHours(24); days--; } - if(days < 0) - { + if (days < 0) { Messages.sendMessage(player, RealEstate.instance.messages.msgInfoClaimInfoAuctionEnded, claimTypeDisplay); - update();// update will end the auction properly + update(); // update will end the auction properly return; } @@ -310,13 +343,10 @@ public void preview(Player player) { msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoAuctionBidStep, RealEstate.econ.format(bidStep)) + "\n"; - if(claimType.equalsIgnoreCase("claim")) - { + if (claimType.equalsIgnoreCase("claim")) { msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoOwner, claim.getOwnerName()) + "\n"; - } - else - { + } else { msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoMainOwner, claim.getParent().getOwnerName()) + "\n"; } @@ -325,40 +355,62 @@ public void preview(Player player) { } } + /** + * Sends auction information to a CommandSender. + *

+ * This method displays an oneline summary of the auction including area, location, price, + * remaining time, and bid step. + *

+ * + * @param cs the CommandSender to send the message to + */ @Override - public void msgInfo(CommandSender cs) - { - IClaim claim = RealEstate.claimAPI.getClaimAt(sign); - String location = "[" + claim.getWorld().getName() + ", " + - "X: " + claim.getX() + ", " + - "Y: " + claim.getY() + ", " + - "Z: " + claim.getZ() + "]"; + public void msgInfo(CommandSender cs) { + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); + String location = "[" + claim.getWorld().getName() + ", " + + "X: " + claim.getX() + ", " + + "Y: " + claim.getY() + ", " + + "Z: " + claim.getZ() + "]"; int days = Period.between(LocalDate.now(), endDate.toLocalDate()).getDays(); Duration hours = Duration.between(LocalTime.now(), endDate.toLocalTime()); - if(hours.isNegative() && !hours.isZero()) - { + if (hours.isNegative() && !hours.isZero()) { hours = hours.plusHours(24); days--; } - Messages.sendMessage(cs, RealEstate.instance.messages.msgInfoClaimInfoAuctionOneline, - claim.getArea() + "", - location, - RealEstate.econ.format(price), - Utils.getTime(days, hours, true), - RealEstate.econ.format(bidStep)); + Messages.sendMessage(cs, RealEstate.instance.messages.msgInfoClaimInfoAuctionOneline, + claim.getArea() + "", + location, + RealEstate.econ.format(price), + Utils.getTime(days, hours, true), + RealEstate.econ.format(bidStep)); } + + /** + * Returns the bid increment step for the auction. + * + * @return the bid step as a double + */ public double getBidStep() { return this.bidStep; } + /** + * Returns the UUID of the current highest bidder. + * + * @return the buyer's UUID, or {@code null} if no bid has been made + */ public UUID getBuyer() { return this.buyer; } + /** + * Returns the end date and time of the auction. + * + * @return the auction's end date as a {@link LocalDateTime} + */ public LocalDateTime getEndDate() { return this.endDate; } - } diff --git a/src/me/EtienneDx/RealEstate/Transactions/ClaimLease.java b/src/me/EtienneDx/RealEstate/Transactions/ClaimLease.java index f4ae123..16967dd 100644 --- a/src/me/EtienneDx/RealEstate/Transactions/ClaimLease.java +++ b/src/me/EtienneDx/RealEstate/Transactions/ClaimLease.java @@ -7,6 +7,8 @@ import java.time.Period; import java.time.format.DateTimeFormatter; import java.util.Map; +import java.util.UUID; + import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.OfflinePlayer; @@ -18,483 +20,495 @@ import me.EtienneDx.RealEstate.Messages; import me.EtienneDx.RealEstate.RealEstate; -import me.EtienneDx.RealEstate.Utils; import me.EtienneDx.RealEstate.RealEstateSign; +import me.EtienneDx.RealEstate.Utils; import me.EtienneDx.RealEstate.ClaimAPI.ClaimPermission; import me.EtienneDx.RealEstate.ClaimAPI.IClaim; import net.md_5.bungee.api.ChatColor; -public class ClaimLease extends BoughtTransaction -{ - public LocalDateTime lastPayment = null; - public int frequency; - public int paymentsLeft; - - public ClaimLease(Map map) - { - super(map); - if(map.get("lastPayment") != null) - lastPayment = LocalDateTime.parse((String) map.get("lastPayment"), DateTimeFormatter.ISO_DATE_TIME); - frequency = (int)map.get("frequency"); - paymentsLeft = (int)map.get("paymentsLeft"); - } - - public ClaimLease(IClaim claim, Player player, double price, Location sign, int frequency, int paymentsLeft) - { - super(claim, player, price, sign); - this.frequency = frequency; - this.paymentsLeft = paymentsLeft; - } +/** + * Represents a lease transaction for a claim. + *

+ * A ClaimLease handles the process of leasing a claim by a player, + * including updating the sign with lease details, processing payments, + * and transferring claim ownership when the lease expires. + *

+ */ +public class ClaimLease extends BoughtTransaction { - @Override - public Map serialize() { - Map map = super.serialize(); + /** The time when the last lease payment was made. */ + public LocalDateTime lastPayment = null; + + /** The frequency (in days) at which lease payments are due. */ + public int frequency; + + /** The number of remaining lease payments. */ + public int paymentsLeft; + + /** + * Constructs a ClaimLease transaction from a serialized map. + * + * @param map the map containing serialized transaction data + */ + public ClaimLease(Map map) { + super(map); + if (map.get("lastPayment") != null) + lastPayment = LocalDateTime.parse((String) map.get("lastPayment"), DateTimeFormatter.ISO_DATE_TIME); + frequency = (int) map.get("frequency"); + paymentsLeft = (int) map.get("paymentsLeft"); + } + + /** + * Constructs a new ClaimLease transaction. + * + * @param claim the claim being leased + * @param player the player leasing the claim + * @param price the price per payment period + * @param sign the location of the lease sign + * @param frequency the number of days between lease payments + * @param paymentsLeft the total number of lease payments required + */ + public ClaimLease(IClaim claim, Player player, double price, Location sign, int frequency, int paymentsLeft) { + super(claim, player, price, sign); + this.frequency = frequency; + this.paymentsLeft = paymentsLeft; + } - if(lastPayment != null) - map.put("lastPayment", lastPayment.format(DateTimeFormatter.ISO_DATE_TIME)); - map.put("frequency", frequency); - map.put("paymentsLeft", paymentsLeft); - - return map; - } - - @Override - public boolean update() - { - if(buyer == null)// not yet leased - { - if(sign.getBlock().getState() instanceof Sign) - { - RealEstateSign s = new RealEstateSign((Sign) sign.getBlock().getState()); - s.setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); - s.setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceLease); - if(RealEstate.instance.config.cfgUseCurrencySymbol) - { - if(RealEstate.instance.config.cfgUseDecimalCurrency == false) - { - s.setLine(2, paymentsLeft + "x " + RealEstate.instance.config.cfgCurrencySymbol + " " + (int)Math.round(price)); - } - else - { - s.setLine(2, paymentsLeft + "x " + RealEstate.instance.config.cfgCurrencySymbol + " " + price); - } - } - else - { - if(RealEstate.instance.config.cfgUseDecimalCurrency == false) - { - s.setLine(2, paymentsLeft + "x " + (int)Math.round(price) + " " + RealEstate.econ.currencyNamePlural()); - } - else - { - s.setLine(2, paymentsLeft + "x " + price + " " + RealEstate.econ.currencyNamePlural()); - } - } - s.setLine(3, Utils.getTime(frequency, null, false)); - s.update(true); - } - else - { - return true; - } - - } - else - { - int days = Period.between(lastPayment.toLocalDate(), LocalDate.now()).getDays(); - Duration hours = Duration.between(lastPayment.toLocalTime(), LocalTime.now()); - if(hours.isNegative() && !hours.isZero()) - { - hours = hours.plusHours(24); - days--; - } - if(days >= frequency)// we exceeded the time limit! - { - payLease(); - } - } - return false; - } + /** + * Serializes the ClaimLease transaction to a Map. + * + * @return a Map containing all the relevant data for this lease transaction + */ + @Override + public Map serialize() { + Map map = super.serialize(); + if (lastPayment != null) + map.put("lastPayment", lastPayment.format(DateTimeFormatter.ISO_DATE_TIME)); + map.put("frequency", frequency); + map.put("paymentsLeft", paymentsLeft); + return map; + } + + /** + * Updates the lease transaction. + *

+ * If no buyer is assigned, it updates the lease sign with payment information. + * If a buyer exists, it checks if the lease period has expired and processes a payment. + *

+ * + * @return {@code true} if the transaction is finished and should be removed; {@code false} otherwise. + */ + @Override + public boolean update() { + if (buyer == null) { // Lease has not yet begun. + if (sign.getBlock().getState() instanceof Sign) { + RealEstateSign s = new RealEstateSign((Sign) sign.getBlock().getState()); + s.setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); + s.setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceLease); + if (RealEstate.instance.config.cfgUseCurrencySymbol) { + if (RealEstate.instance.config.cfgUseDecimalCurrency == false) { + s.setLine(2, paymentsLeft + "x " + RealEstate.instance.config.cfgCurrencySymbol + " " + (int) Math.round(price)); + } else { + s.setLine(2, paymentsLeft + "x " + RealEstate.instance.config.cfgCurrencySymbol + " " + price); + } + } else { + if (RealEstate.instance.config.cfgUseDecimalCurrency == false) { + s.setLine(2, paymentsLeft + "x " + (int) Math.round(price) + " " + RealEstate.econ.currencyNamePlural()); + } else { + s.setLine(2, paymentsLeft + "x " + price + " " + RealEstate.econ.currencyNamePlural()); + } + } + s.setLine(3, Utils.getTime(frequency, null, false)); + s.update(true); + } else { + return true; + } + } else { + int days = Period.between(lastPayment.toLocalDate(), LocalDate.now()).getDays(); + Duration hours = Duration.between(lastPayment.toLocalTime(), LocalTime.now()); + if (hours.isNegative() && !hours.isZero()) { + hours = hours.plusHours(24); + days--; + } + if (days >= frequency) { // Lease payment due. + payLease(); + } + } + return false; + } - private void payLease() - { - if(buyer == null) return; + /** + * Processes a lease payment. + *

+ * If the payment is successful, the lease payment counter is decremented, + * notifications are sent to the buyer and owner, and if all payments have been made, + * the claim is transferred to the buyer. + *

+ */ + private void payLease() { + if (buyer == null) + return; - OfflinePlayer buyerPlayer = Bukkit.getOfflinePlayer(buyer); - OfflinePlayer seller = owner == null ? null : Bukkit.getOfflinePlayer(owner); - - String claimType = RealEstate.claimAPI.getClaimAt(sign).isParentClaim() ? - RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; - String location = "[" + sign.getWorld().getName() + ", X: " + sign.getBlockX() + - ", Y: " + sign.getBlockY() + ", Z: " + sign.getBlockZ() + "]"; - - if(Utils.makePayment(owner, buyer, price, false, false)) - { - lastPayment = LocalDateTime.now(); - paymentsLeft--; - if(paymentsLeft > 0) - { - if(buyerPlayer.isOnline() && RealEstate.instance.config.cfgMessageBuyer) - { - Messages.sendMessage(buyerPlayer.getPlayer(), RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentBuyer, - claimType, - location, - RealEstate.econ.format(price), - paymentsLeft + ""); - } - else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) - { - User u = RealEstate.ess.getUser(this.buyer); - u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentBuyer, - claimType, - location, - RealEstate.econ.format(price), - paymentsLeft + "")); - } - - if(owner != null) - { - if(seller != null && seller.isOnline() && RealEstate.instance.config.cfgMessageOwner) - { - Messages.sendMessage(seller.getPlayer(), RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentOwner, - buyerPlayer.getName(), - claimType, - location, - RealEstate.econ.format(price), - paymentsLeft + ""); - } - else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) - { - User u = RealEstate.ess.getUser(this.owner); - u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentOwner, - buyerPlayer.getName(), - claimType, - location, - RealEstate.econ.format(price), - paymentsLeft + "")); - } - } - } - else - { - if(buyerPlayer.isOnline() && RealEstate.instance.config.cfgMessageBuyer) - { - Messages.sendMessage(buyerPlayer.getPlayer(), RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentBuyerFinal, - claimType, - location, - RealEstate.econ.format(price)); - } - else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) - { - User u = RealEstate.ess.getUser(this.buyer); - u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentBuyerFinal, - claimType, - location, - RealEstate.econ.format(price))); - } - - if(seller != null && seller.isOnline() && RealEstate.instance.config.cfgMessageOwner) - { - Messages.sendMessage(seller.getPlayer(), RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentOwnerFinal, - buyerPlayer.getName(), - claimType, - location, - RealEstate.econ.format(price)); - } - else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) - { - User u = RealEstate.ess.getUser(this.owner); - u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentOwnerFinal, - buyerPlayer.getName(), - claimType, - location, - RealEstate.econ.format(price))); - } - IClaim claim = RealEstate.claimAPI.getClaimAt(sign); - - Utils.transferClaim(claim, buyer, owner); - RealEstate.transactionsStore.cancelTransaction(this);// the transaction is finished - } - } - else - { - this.exitLease(); - } - // no need to re update, since there's no sign - RealEstate.transactionsStore.saveData(); - } - - private void exitLease() - { - if(buyer != null) - { - OfflinePlayer buyerPlayer = Bukkit.getOfflinePlayer(buyer); - OfflinePlayer seller = owner == null ? null : Bukkit.getOfflinePlayer(owner); - - IClaim claim = RealEstate.claimAPI.getClaimAt(sign); - - String claimType = claim.isParentClaim() ? - RealEstate.instance.messages.keywordClaim : - RealEstate.instance.messages.keywordSubclaim; - String location = "[" + sign.getWorld().getName() + ", X: " + - sign.getBlockX() + ", Y: " + - sign.getBlockY() + ", Z: " + sign.getBlockZ() + "]"; - - if(buyerPlayer.isOnline() && RealEstate.instance.config.cfgMessageBuyer) - { - Messages.sendMessage(buyerPlayer.getPlayer(), RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentBuyerCancelled, - claimType, - location, - RealEstate.econ.format(price)); - } - else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) - { - User u = RealEstate.ess.getUser(this.buyer); - u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentBuyerCancelled, - claimType, - location, - RealEstate.econ.format(price))); - } - if(seller != null && seller.isOnline() && RealEstate.instance.config.cfgMessageOwner) - { - Messages.sendMessage(seller.getPlayer(), RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentOwnerCancelled, - buyerPlayer.getName(), - claimType, - location, - RealEstate.econ.format(price)); - } - else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) - { - User u = RealEstate.ess.getUser(this.owner); - u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentOwnerCancelled, - buyerPlayer.getName(), - claimType, - location, - RealEstate.econ.format(price))); - } - - claim.removeManager(buyer); - claim.dropPlayerPermissions(buyer); - } - else - { - getHolder().breakNaturally();// the sign should still be there since the lease has netver begun - } - RealEstate.transactionsStore.cancelTransaction(this); - } + OfflinePlayer buyerPlayer = Bukkit.getOfflinePlayer(buyer); + OfflinePlayer seller = owner == null ? null : Bukkit.getOfflinePlayer(owner); + + String claimType = RealEstate.claimAPI.getClaimAt(sign).isParentClaim() + ? RealEstate.instance.messages.keywordClaim + : RealEstate.instance.messages.keywordSubclaim; + String location = "[" + sign.getWorld().getName() + ", X: " + sign.getBlockX() + + ", Y: " + sign.getBlockY() + ", Z: " + sign.getBlockZ() + "]"; + + if (Utils.makePayment(owner, buyer, price, false, false)) { + lastPayment = LocalDateTime.now(); + paymentsLeft--; + if (paymentsLeft > 0) { + if (buyerPlayer.isOnline() && RealEstate.instance.config.cfgMessageBuyer) { + Messages.sendMessage(buyerPlayer.getPlayer(), RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentBuyer, + claimType, + location, + RealEstate.econ.format(price), + paymentsLeft + ""); + } else if (RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) { + User u = RealEstate.ess.getUser(this.buyer); + u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentBuyer, + claimType, + location, + RealEstate.econ.format(price), + paymentsLeft + "")); + } + + if (owner != null) { + if (seller != null && seller.isOnline() && RealEstate.instance.config.cfgMessageOwner) { + Messages.sendMessage(seller.getPlayer(), RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentOwner, + buyerPlayer.getName(), + claimType, + location, + RealEstate.econ.format(price), + paymentsLeft + ""); + } else if (RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) { + User u = RealEstate.ess.getUser(this.owner); + u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentOwner, + buyerPlayer.getName(), + claimType, + location, + RealEstate.econ.format(price), + paymentsLeft + "")); + } + } + } else { + if (buyerPlayer.isOnline() && RealEstate.instance.config.cfgMessageBuyer) { + Messages.sendMessage(buyerPlayer.getPlayer(), RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentBuyerFinal, + claimType, + location, + RealEstate.econ.format(price)); + } else if (RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) { + User u = RealEstate.ess.getUser(this.buyer); + u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentBuyerFinal, + claimType, + location, + RealEstate.econ.format(price))); + } + + if (seller != null && seller.isOnline() && RealEstate.instance.config.cfgMessageOwner) { + Messages.sendMessage(seller.getPlayer(), RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentOwnerFinal, + buyerPlayer.getName(), + claimType, + location, + RealEstate.econ.format(price)); + } else if (RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) { + User u = RealEstate.ess.getUser(this.owner); + u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentOwnerFinal, + buyerPlayer.getName(), + claimType, + location, + RealEstate.econ.format(price))); + } + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); + Utils.transferClaim(claim, buyer, owner); + RealEstate.transactionsStore.cancelTransaction(this); // Lease is complete. + } + } else { + this.exitLease(); + } + // No further update is needed since the sign is removed after lease completion. + RealEstate.transactionsStore.saveData(); + } + + /** + * Cancels the lease and resets permissions. + *

+ * Notifies both buyer and owner about the cancellation if applicable. + *

+ */ + private void exitLease() { + if (buyer != null) { + OfflinePlayer buyerPlayer = Bukkit.getOfflinePlayer(buyer); + OfflinePlayer seller = owner == null ? null : Bukkit.getOfflinePlayer(owner); + + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); + + String claimType = claim.isParentClaim() + ? RealEstate.instance.messages.keywordClaim + : RealEstate.instance.messages.keywordSubclaim; + String location = "[" + sign.getWorld().getName() + ", X: " + + sign.getBlockX() + ", Y: " + + sign.getBlockY() + ", Z: " + sign.getBlockZ() + "]"; + + if (buyerPlayer.isOnline() && RealEstate.instance.config.cfgMessageBuyer) { + Messages.sendMessage(buyerPlayer.getPlayer(), RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentBuyerCancelled, + claimType, + location, + RealEstate.econ.format(price)); + } else if (RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) { + User u = RealEstate.ess.getUser(this.buyer); + u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentBuyerCancelled, + claimType, + location, + RealEstate.econ.format(price))); + } + if (seller != null && seller.isOnline() && RealEstate.instance.config.cfgMessageOwner) { + Messages.sendMessage(seller.getPlayer(), RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentOwnerCancelled, + buyerPlayer.getName(), + claimType, + location, + RealEstate.econ.format(price)); + } else if (RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) { + User u = RealEstate.ess.getUser(this.owner); + u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoLeasePaymentOwnerCancelled, + buyerPlayer.getName(), + claimType, + location, + RealEstate.econ.format(price))); + } + + claim.removeManager(buyer); + claim.dropPlayerPermissions(buyer); + } else { + getHolder().breakNaturally(); // Sign remains if lease never started. + } + RealEstate.transactionsStore.cancelTransaction(this); + } - @Override - public boolean tryCancelTransaction(Player p, boolean force) - { - if(buyer != null) - { - if(p.hasPermission("realestate.admin") && force == true) - { - this.exitLease(); - return true; - } - else - { - IClaim claim = RealEstate.claimAPI.getClaimAt(sign); - if(p != null) { - Messages.sendMessage(p, RealEstate.instance.messages.msgErrorCantCancelAlreadyLeased, - claim.isParentClaim() ? - RealEstate.instance.messages.keywordClaim : - RealEstate.instance.messages.keywordSubclaim - ); - } - return false; - } - } - else - { - RealEstate.transactionsStore.cancelTransaction(this); - return true; - } - } - - @Override - public void interact(Player player) - { - IClaim claim = RealEstate.claimAPI.getClaimAt(sign);// getting by id creates errors for subclaims - if(claim == null || claim.isWilderness()) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimDoesNotExist); + /** + * Attempts to cancel the lease transaction. + *

+ * If the player has administrative privileges or the cancellation is forced, + * the lease is cancelled; otherwise, an error message is sent. + *

+ * + * @param p the player attempting to cancel the lease + * @param force whether cancellation is forced + * @return {@code true} if the transaction was successfully cancelled, {@code false} otherwise + */ + @Override + public boolean tryCancelTransaction(Player p, boolean force) { + if (buyer != null) { + if (p.hasPermission("realestate.admin") && force == true) { + this.exitLease(); + return true; + } else { + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); + if (p != null) { + Messages.sendMessage(p, RealEstate.instance.messages.msgErrorCantCancelAlreadyLeased, + claim.isParentClaim() + ? RealEstate.instance.messages.keywordClaim + : RealEstate.instance.messages.keywordSubclaim); + } + return false; + } + } else { + RealEstate.transactionsStore.cancelTransaction(this); + return true; + } + } + + /** + * Processes player interaction with the lease sign. + *

+ * Handles lease purchase by validating the player's permissions, ensuring the claim is valid, + * and processing the payment. + *

+ * + * @param player the player interacting with the lease sign + */ + @Override + public void interact(Player player) { + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); // Use claim ID to avoid errors with subclaims. + if (claim == null || claim.isWilderness()) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimDoesNotExist); RealEstate.transactionsStore.cancelTransaction(claim); return; - } - String claimType = claim.isParentClaim() ? "claim" : "subclaim"; - String claimTypeDisplay = claim.isParentClaim() ? - RealEstate.instance.messages.keywordClaim : - RealEstate.instance.messages.keywordSubclaim; - - if (owner != null && owner.equals(player.getUniqueId())) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimAlreadyOwner, claimTypeDisplay); + } + String claimType = claim.isParentClaim() ? "claim" : "subclaim"; + String claimTypeDisplay = claim.isParentClaim() + ? RealEstate.instance.messages.keywordClaim + : RealEstate.instance.messages.keywordSubclaim; + + if (owner != null && owner.equals(player.getUniqueId())) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimAlreadyOwner, claimTypeDisplay); return; } - if(claim.isParentClaim() && owner != null && !owner.equals(claim.getOwner())) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNotLeasedByOwner, claimTypeDisplay); + if (claim.isParentClaim() && owner != null && !owner.equals(claim.getOwner())) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNotLeasedByOwner, claimTypeDisplay); RealEstate.transactionsStore.cancelTransaction(claim); return; - } - if(!player.hasPermission("realestate." + claimType + ".lease")) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNoLeasePermission, claimTypeDisplay); + } + if (!player.hasPermission("realestate." + claimType + ".lease")) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNoLeasePermission, claimTypeDisplay); return; - } - if(player.getUniqueId().equals(buyer) || buyer != null) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimAlreadyLeased, claimTypeDisplay); + } + if (player.getUniqueId().equals(buyer) || buyer != null) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimAlreadyLeased, claimTypeDisplay); return; - } - - if(Utils.makePayment(owner, player.getUniqueId(), price, false, true))// if payment succeed - { - buyer = player.getUniqueId(); - lastPayment = LocalDateTime.now(); - paymentsLeft--; - claim.addPlayerPermissions(buyer, ClaimPermission.BUILD); - claim.addPlayerPermissions(player.getUniqueId(), ClaimPermission.MANAGE); - RealEstate.claimAPI.saveClaim(claim); - getHolder().breakNaturally();// leases don't have signs indicating the remaining time - update(); - RealEstate.transactionsStore.saveData(); + } + + if (Utils.makePayment(owner, player.getUniqueId(), price, false, true)) { // If payment succeeds. + buyer = player.getUniqueId(); + lastPayment = LocalDateTime.now(); + paymentsLeft--; + claim.addPlayerPermissions(buyer, ClaimPermission.BUILD); + claim.addPlayerPermissions(player.getUniqueId(), ClaimPermission.MANAGE); + RealEstate.claimAPI.saveClaim(claim); + getHolder().breakNaturally(); // Leases do not show remaining time on sign. + update(); + RealEstate.transactionsStore.saveData(); - String location = "[" + player.getLocation().getWorld() + ", " + - "X: " + player.getLocation().getBlockX() + ", " + - "Y: " + player.getLocation().getBlockY() + ", " + - "Z: " + player.getLocation().getBlockZ() + "]"; - - RealEstate.instance.addLogEntry( - "[" + RealEstate.transactionsStore.dateFormat.format(RealEstate.transactionsStore.date) + "] " + player.getName() + + String location = "[" + player.getLocation().getWorld() + ", " + + "X: " + player.getLocation().getBlockX() + ", " + + "Y: " + player.getLocation().getBlockY() + ", " + + "Z: " + player.getLocation().getBlockZ() + "]"; + + RealEstate.instance.addLogEntry( + "[" + RealEstate.transactionsStore.dateFormat.format(RealEstate.transactionsStore.date) + "] " + player.getName() + " has started leasing a " + claimType + " at " + location + " Price: " + price + " " + RealEstate.econ.currencyNamePlural()); - if(owner != null) - { - OfflinePlayer seller = Bukkit.getOfflinePlayer(owner); - if(RealEstate.instance.config.cfgMessageOwner && seller.isOnline()) - { - Messages.sendMessage(seller.getPlayer(), RealEstate.instance.messages.msgInfoClaimOwnerLeaseStarted, - player.getName(), - claimTypeDisplay, - RealEstate.econ.format(price), - location, - paymentsLeft + ""); - } - else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) - { - User u = RealEstate.ess.getUser(this.owner); - u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoClaimOwnerLeaseStarted, - player.getName(), - claimTypeDisplay, - RealEstate.econ.format(price), - location, - paymentsLeft + "")); - } - } - - Messages.sendMessage(player, RealEstate.instance.messages.msgInfoClaimBuyerLeaseStarted, - claimTypeDisplay, - RealEstate.econ.format(price), - paymentsLeft + ""); - } - } - - @Override - public void preview(Player player) - { - IClaim claim = RealEstate.claimAPI.getClaimAt(sign); - if(player.hasPermission("realestate.info")) - { - String claimType = claim.isParentClaim() ? "claim" : "subclaim"; - String claimTypeDisplay = claim.isParentClaim() ? - RealEstate.instance.messages.keywordClaim : - RealEstate.instance.messages.keywordSubclaim; - String msg; - msg = Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoLeaseHeader) + "\n"; - if(buyer == null) - { - msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoGeneralLeaseNoBuyer, - claimTypeDisplay, - paymentsLeft + "", - RealEstate.econ.format(price), - Utils.getTime(frequency, null, true)) + "\n"; - - if(claimType.equalsIgnoreCase("claim")) - { - msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoOwner, - claim.getOwnerName()) + "\n"; - } - else - { - msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoMainOwner, - claim.getParent().getOwnerName()) + "\n"; - msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoNote) + "\n"; - } - } - else - { - int days = Period.between(lastPayment.toLocalDate(), LocalDate.now()).getDays(); - Duration hours = Duration.between(lastPayment.toLocalTime(), LocalTime.now()); - if(hours.isNegative() && !hours.isZero()) - { - hours = hours.plusHours(24); - days--; - } - int daysLeft = frequency - days - 1;// we need to remove the current day - Duration timeRemaining = Duration.ofHours(24).minus(hours); - - msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoGeneralLeaseBuyer, - claimTypeDisplay, - Bukkit.getOfflinePlayer(buyer).getName(), - RealEstate.econ.format(price), - paymentsLeft + "", - Utils.getTime(daysLeft, timeRemaining, true), - Utils.getTime(frequency, null, true)) + "\n"; - if(claimType.equalsIgnoreCase("claim")) - { - msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoOwner, - claim.getOwnerName()) + "\n"; - } - else - { - msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoMainOwner, - claim.getParent().getOwnerName()) + "\n"; - } - } - Messages.sendMessage(player, msg, false); - } - else - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNoInfoPermission); - } - } + if (owner != null) { + OfflinePlayer seller = Bukkit.getOfflinePlayer(owner); + if (RealEstate.instance.config.cfgMessageOwner && seller.isOnline()) { + Messages.sendMessage(seller.getPlayer(), RealEstate.instance.messages.msgInfoClaimOwnerLeaseStarted, + player.getName(), + claimTypeDisplay, + RealEstate.econ.format(price), + location, + paymentsLeft + ""); + } else if (RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) { + User u = RealEstate.ess.getUser(this.owner); + u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoClaimOwnerLeaseStarted, + player.getName(), + claimTypeDisplay, + RealEstate.econ.format(price), + location, + paymentsLeft + "")); + } + } + + Messages.sendMessage(player, RealEstate.instance.messages.msgInfoClaimBuyerLeaseStarted, + claimTypeDisplay, + RealEstate.econ.format(price), + paymentsLeft + ""); + } + } - @Override - public void msgInfo(CommandSender cs) - { - IClaim claim = RealEstate.claimAPI.getClaimAt(sign); - String location = "[" + claim.getWorld().getName() + ", " + - "X: " + claim.getX() + ", " + - "Y: " + claim.getY() + ", " + - "Z: " + claim.getZ() + "]"; + /** + * Sends a preview message to the player with lease details. + *

+ * This method constructs and sends a multi-line message to the player + * showing the lease header, lease details (number of payments, price, duration), + * and the current owner information. + *

+ * + * @param player the player receiving the preview message + */ + @Override + public void preview(Player player) { + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); + if (player.hasPermission("realestate.info")) { + String claimType = claim.isParentClaim() ? "claim" : "subclaim"; + String claimTypeDisplay = claim.isParentClaim() + ? RealEstate.instance.messages.keywordClaim + : RealEstate.instance.messages.keywordSubclaim; + String msg; + msg = Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoLeaseHeader) + "\n"; + if (buyer == null) { + msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoGeneralLeaseNoBuyer, + claimTypeDisplay, + paymentsLeft + "", + RealEstate.econ.format(price), + Utils.getTime(frequency, null, true)) + "\n"; + if (claimType.equalsIgnoreCase("claim")) { + msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoOwner, + claim.getOwnerName()) + "\n"; + } else { + msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoMainOwner, + claim.getParent().getOwnerName()) + "\n"; + msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoNote) + "\n"; + } + } else { + int days = Period.between(lastPayment.toLocalDate(), LocalDate.now()).getDays(); + Duration hours = Duration.between(lastPayment.toLocalTime(), LocalTime.now()); + if (hours.isNegative() && !hours.isZero()) { + hours = hours.plusHours(24); + days--; + } + int daysLeft = frequency - days - 1; // Remove current day. + Duration timeRemaining = Duration.ofHours(24).minus(hours); + msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoGeneralLeaseBuyer, + claimTypeDisplay, + Bukkit.getOfflinePlayer(buyer).getName(), + RealEstate.econ.format(price), + paymentsLeft + "", + Utils.getTime(daysLeft, timeRemaining, true), + Utils.getTime(frequency, null, true)) + "\n"; + if (claimType.equalsIgnoreCase("claim")) { + msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoOwner, + claim.getOwnerName()) + "\n"; + } else { + msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoMainOwner, + claim.getParent().getOwnerName()) + "\n"; + } + } + Messages.sendMessage(player, msg, false); + } else { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNoInfoPermission); + } + } - Messages.sendMessage(cs, RealEstate.instance.messages.msgInfoClaimInfoLeaseOneline, - claim.getArea() + "", - location, - RealEstate.econ.format(price), - paymentsLeft + ""); - } - public int getFrequency() { - return this.frequency; - } + /** + * Sends a one-line information message about the lease transaction to a CommandSender. + * + * @param cs the CommandSender to send the information message to + */ + @Override + public void msgInfo(CommandSender cs) { + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); + String location = "[" + claim.getWorld().getName() + ", " + + "X: " + claim.getX() + ", " + + "Y: " + claim.getY() + ", " + + "Z: " + claim.getZ() + "]"; + Messages.sendMessage(cs, RealEstate.instance.messages.msgInfoClaimInfoLeaseOneline, + claim.getArea() + "", + location, + RealEstate.econ.format(price), + paymentsLeft + ""); + } - public int getPaymentsLeft() { - return this.paymentsLeft; - } + /** + * Retrieves the frequency of lease payments. + * + * @return the number of days between lease payments + */ + public int getFrequency() { + return this.frequency; + } + /** + * Retrieves the number of remaining lease payments. + * + * @return the number of payments left + */ + public int getPaymentsLeft() { + return this.paymentsLeft; + } } diff --git a/src/me/EtienneDx/RealEstate/Transactions/ClaimRent.java b/src/me/EtienneDx/RealEstate/Transactions/ClaimRent.java index e940b3d..e02d45c 100644 --- a/src/me/EtienneDx/RealEstate/Transactions/ClaimRent.java +++ b/src/me/EtienneDx/RealEstate/Transactions/ClaimRent.java @@ -24,317 +24,336 @@ import me.EtienneDx.RealEstate.ClaimAPI.IClaim; import net.md_5.bungee.api.ChatColor; -public class ClaimRent extends BoughtTransaction -{ - LocalDateTime startDate = null; - int duration; - public boolean autoRenew = false; - public boolean buildTrust = true; - - public ClaimRent(Map map) - { - super(map); - if(map.get("startDate") != null) - startDate = LocalDateTime.parse((String) map.get("startDate"), DateTimeFormatter.ISO_DATE_TIME); - duration = (int)map.get("duration"); - autoRenew = (boolean) map.get("autoRenew"); - try { - buildTrust = (boolean) map.get("buildTrust"); - } - catch (Exception e) { - buildTrust = true; - } - } - - public ClaimRent(IClaim claim, Player player, double price, Location sign, int duration, boolean buildTrust) - { - super(claim, player, price, sign); - this.duration = duration; - this.buildTrust = buildTrust; - } +/** + * Represents a rental transaction for a claim. + *

+ * This transaction handles the leasing of claims by a renter. It supports auto-renewal, + * periodic lease payments, and updates the sign display with the current lease status. + *

+ */ +public class ClaimRent extends BoughtTransaction { - @Override - public Map serialize() { - Map map = super.serialize(); - - if(startDate != null) - map.put("startDate", startDate.format(DateTimeFormatter.ISO_DATE_TIME)); - map.put("duration", duration); - map.put("autoRenew", autoRenew); - map.put("buildTrust", buildTrust); - - return map; - } - - @Override - public boolean update() - { - if(buyer == null) - { - if(sign.getBlock().getState() instanceof Sign) - { - RealEstateSign s = new RealEstateSign((Sign) sign.getBlock().getState()); - s.setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); - s.setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceRent); - String price_line = ""; - if(RealEstate.instance.config.cfgUseCurrencySymbol) - { - if(RealEstate.instance.config.cfgUseDecimalCurrency == false) - { - price_line = RealEstate.instance.config.cfgCurrencySymbol + " " + (int)Math.round(price); - } - else - { - price_line = RealEstate.instance.config.cfgCurrencySymbol + " " + price; - } - - } - else - { - if(RealEstate.instance.config.cfgUseDecimalCurrency == false) - { - price_line = (int)Math.round(price) + " " + RealEstate.econ.currencyNamePlural(); - } - else - { - price_line = price + " " + RealEstate.econ.currencyNamePlural(); - } - } - String period = Utils.getTime(duration, null, false); - if(this.buildTrust) { - s.setLine(2, price_line); - s.setLine(3, period); - } else { - s.setLine(2, RealEstate.instance.config.cfgContainerRentLine); - s.setLine(3, price_line + " - " + period); - } - s.update(true); - } - else - { - return true; - } - } - else - { - // we want to know how much time has gone by since startDate - int days = Period.between(startDate.toLocalDate(), LocalDate.now()).getDays(); - Duration hours = Duration.between(startDate.toLocalTime(), LocalTime.now()); - if(hours.isNegative() && !hours.isZero()) - { - hours = hours.plusHours(24); - days--; - } - if(days >= duration)// we exceeded the time limit! - { - payRent(); - } - else if(sign.getBlock().getState() instanceof Sign) - { - RealEstateSign s = new RealEstateSign((Sign) sign.getBlock().getState()); - s.setLine(0, ChatColor.GOLD + RealEstate.instance.config.cfgReplaceOngoingRent); //Changed the header to "[Rented]" so that it won't waste space on the next line and allow the name of the player to show underneath. - s.setLine(1, Utils.getSignString(Bukkit.getOfflinePlayer(buyer).getName()));//remove "Rented by" - s.setLine(2, "Time remaining : "); - - int daysLeft = duration - days - 1;// we need to remove the current day - Duration timeRemaining = Duration.ofHours(24).minus(hours); - - s.setLine(3, Utils.getTime(daysLeft, timeRemaining, false)); - s.update(true); - } - } - return false; - - } + /** The date and time when the lease started or the last payment was made. */ + LocalDateTime startDate = null; + /** The duration (in days) of the lease period. */ + int duration; + /** If true, the lease is set to auto-renew once the duration expires. */ + public boolean autoRenew = false; + /** Indicates whether the lease grants building trust (if true) or container trust (if false). */ + public boolean buildTrust = true; + + /** + * Constructs a ClaimRent transaction from a serialized map. + * + * @param map the map containing serialized data for this lease transaction + */ + public ClaimRent(Map map) { + super(map); + if(map.get("startDate") != null) + startDate = LocalDateTime.parse((String) map.get("startDate"), DateTimeFormatter.ISO_DATE_TIME); + duration = (int) map.get("duration"); + autoRenew = (boolean) map.get("autoRenew"); + try { + buildTrust = (boolean) map.get("buildTrust"); + } + catch (Exception e) { + buildTrust = true; + } + } + + /** + * Constructs a new ClaimRent transaction. + * + * @param claim the claim being rented + * @param player the player initiating the rent + * @param price the rental price per period + * @param sign the location of the sign representing the rent + * @param duration the lease period (in days) + * @param buildTrust if true, grants building permissions; if false, grants container trust only + */ + public ClaimRent(IClaim claim, Player player, double price, Location sign, int duration, boolean buildTrust) { + super(claim, player, price, sign); + this.duration = duration; + this.buildTrust = buildTrust; + } - private void unRent(boolean msgBuyer) - { - IClaim claim = RealEstate.claimAPI.getClaimAt(sign); - claim.dropPlayerPermissions(buyer); - claim.removeManager(buyer); - claim.setInheritPermissions(true); - RealEstate.claimAPI.saveClaim(claim); - if(msgBuyer && Bukkit.getOfflinePlayer(buyer).isOnline() && RealEstate.instance.config.cfgMessageBuyer) - { - String location = "[" + sign.getWorld().getName() + ", X: " + sign.getBlockX() + ", Y: " + - sign.getBlockY() + ", Z: " + sign.getBlockZ() + "]"; - String claimType = claim.isParentClaim() ? - RealEstate.instance.messages.keywordClaim : - RealEstate.instance.messages.keywordSubclaim; + /** + * Serializes this ClaimRent transaction to a Map. + * + * @return a Map representation of this lease transaction + */ + @Override + public Map serialize() { + Map map = super.serialize(); + if(startDate != null) + map.put("startDate", startDate.format(DateTimeFormatter.ISO_DATE_TIME)); + map.put("duration", duration); + map.put("autoRenew", autoRenew); + map.put("buildTrust", buildTrust); + return map; + } - Messages.sendMessage(Bukkit.getPlayer(buyer), RealEstate.instance.messages.msgInfoClaimInfoRentCancelled, - claimType, - location); - } - buyer = null; - RealEstate.transactionsStore.saveData(); - update(); - } + /** + * Updates the state of the lease transaction. + *

+ * If no buyer is present, it updates the sign with rental information. Otherwise, it checks if the lease + * period has expired. If so, it processes a payment via {@link #payRent()}. If the lease is ongoing, + * the sign is updated with the remaining time. + *

+ * + * @return false to indicate the transaction remains active + */ + @Override + public boolean update() { + if(buyer == null) { + if(sign.getBlock().getState() instanceof Sign) { + RealEstateSign s = new RealEstateSign((Sign) sign.getBlock().getState()); + s.setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); + s.setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceRent); + String price_line = ""; + if(RealEstate.instance.config.cfgUseCurrencySymbol) { + if(RealEstate.instance.config.cfgUseDecimalCurrency == false) { + price_line = RealEstate.instance.config.cfgCurrencySymbol + " " + (int)Math.round(price); + } + else { + price_line = RealEstate.instance.config.cfgCurrencySymbol + " " + price; + } + } + else { + if(RealEstate.instance.config.cfgUseDecimalCurrency == false) { + price_line = (int)Math.round(price) + " " + RealEstate.econ.currencyNamePlural(); + } + else { + price_line = price + " " + RealEstate.econ.currencyNamePlural(); + } + } + String period = Utils.getTime(duration, null, false); + if(this.buildTrust) { + s.setLine(2, price_line); + s.setLine(3, period); + } else { + s.setLine(2, RealEstate.instance.config.cfgContainerRentLine); + s.setLine(3, price_line + " - " + period); + } + s.update(true); + } + else { + return true; + } + } + else { + // Calculate elapsed time since the lease started/last payment. + int days = Period.between(startDate.toLocalDate(), LocalDate.now()).getDays(); + Duration hours = Duration.between(startDate.toLocalTime(), LocalTime.now()); + if(hours.isNegative() && !hours.isZero()) { + hours = hours.plusHours(24); + days--; + } + if(days >= duration) { // Lease period exceeded + payRent(); + } + else if(sign.getBlock().getState() instanceof Sign) { + RealEstateSign s = new RealEstateSign((Sign) sign.getBlock().getState()); + s.setLine(0, ChatColor.GOLD + RealEstate.instance.config.cfgReplaceOngoingRent); + s.setLine(1, Utils.getSignString(Bukkit.getOfflinePlayer(buyer).getName())); + s.setLine(2, "Time remaining : "); + int daysLeft = duration - days - 1; // Remove current day + Duration timeRemaining = Duration.ofHours(24).minus(hours); + s.setLine(3, Utils.getTime(daysLeft, timeRemaining, false)); + s.update(true); + } + } + return false; + } - private void payRent() - { - if(buyer == null) return; + /** + * Cancels the rent by dropping player permissions and resetting the claim. + * + * @param msgBuyer if true, sends a cancellation message to the buyer + */ + private void unRent(boolean msgBuyer) { + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); + claim.dropPlayerPermissions(buyer); + claim.removeManager(buyer); + claim.setInheritPermissions(true); + RealEstate.claimAPI.saveClaim(claim); + if(msgBuyer && Bukkit.getOfflinePlayer(buyer).isOnline() && RealEstate.instance.config.cfgMessageBuyer) { + String location = "[" + sign.getWorld().getName() + ", X: " + sign.getBlockX() + ", Y: " + + sign.getBlockY() + ", Z: " + sign.getBlockZ() + "]"; + String claimType = claim.isParentClaim() ? + RealEstate.instance.messages.keywordClaim : + RealEstate.instance.messages.keywordSubclaim; + Messages.sendMessage(Bukkit.getPlayer(buyer), RealEstate.instance.messages.msgInfoClaimInfoRentCancelled, + claimType, + location); + } + buyer = null; + RealEstate.transactionsStore.saveData(); + update(); + } - OfflinePlayer buyerPlayer = Bukkit.getOfflinePlayer(this.buyer); - OfflinePlayer seller = owner == null ? null : Bukkit.getOfflinePlayer(owner); - - String claimType = RealEstate.claimAPI.getClaimAt(sign).isParentClaim() ? - RealEstate.instance.messages.keywordClaim : - RealEstate.instance.messages.keywordSubclaim; - String location = "[" + sign.getWorld().getName() + ", X: " + sign.getBlockX() + ", Y: " + - sign.getBlockY() + ", Z: " + sign.getBlockZ() + "]"; - - if(autoRenew && Utils.makePayment(owner, this.buyer, price, false, false)) - { - startDate = LocalDateTime.now(); - if(buyerPlayer.isOnline() && RealEstate.instance.config.cfgMessageBuyer) - { - Messages.sendMessage(buyerPlayer.getPlayer(), RealEstate.instance.messages.msgInfoClaimInfoRentPaymentBuyer, - claimType, - location, - RealEstate.econ.format(price)); - } - else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) - { - User u = RealEstate.ess.getUser(this.buyer); - u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoRentPaymentBuyer, - claimType, - location, - RealEstate.econ.format(price))); - } - - if(seller != null) - { - if(seller.isOnline() && RealEstate.instance.config.cfgMessageOwner) - { - Messages.sendMessage(seller.getPlayer(), RealEstate.instance.messages.msgInfoClaimInfoRentPaymentOwner, - buyerPlayer.getName(), - claimType, - location, - RealEstate.econ.format(price)); - } - else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) - { - User u = RealEstate.ess.getUser(this.owner); - u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoRentPaymentOwner, - buyerPlayer.getName(), - claimType, - location, - RealEstate.econ.format(price))); - } - } - - } - else if (autoRenew) - { - if(buyerPlayer.isOnline() && RealEstate.instance.config.cfgMessageBuyer) - { - Messages.sendMessage(buyerPlayer.getPlayer(), RealEstate.instance.messages.msgInfoClaimInfoRentPaymentBuyerCancelled, - claimType, - location, - RealEstate.econ.format(price)); - } - else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) - { - User u = RealEstate.ess.getUser(this.buyer); - u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoRentPaymentBuyerCancelled, - claimType, - location, - RealEstate.econ.format(price))); - } - unRent(false); - return; - } - else - { - unRent(true); - return; - } - update(); - RealEstate.transactionsStore.saveData(); - } - - @Override - public boolean tryCancelTransaction(Player p, boolean force) - { - if(buyer != null) - { - if(p.hasPermission("realestate.admin") || force == true) - { - this.unRent(true); - RealEstate.transactionsStore.cancelTransaction(this); - return true; - } - else - { - if(p != null) { - IClaim claim = RealEstate.claimAPI.getClaimAt(sign); - Messages.sendMessage(p, RealEstate.instance.messages.msgErrorCantCancelAlreadyRented, - claim.isParentClaim() ? - RealEstate.instance.messages.keywordClaim : - RealEstate.instance.messages.keywordSubclaim - ); - } - return false; - } - } - else - { - RealEstate.transactionsStore.cancelTransaction(this); - return true; - } - } + /** + * Processes a lease payment. + *

+ * If the payment is successful, updates the lease and decrements the number of remaining payments. + * If the payment fails, the lease is canceled. + *

+ */ + private void payRent() { + if(buyer == null) return; - @Override - public void interact(Player player) - { - IClaim claim = RealEstate.claimAPI.getClaimAt(sign);// getting by id creates errors for subclaims - if(claim == null || claim.isWilderness()) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimDoesNotExist); + OfflinePlayer buyerPlayer = Bukkit.getOfflinePlayer(this.buyer); + OfflinePlayer seller = owner == null ? null : Bukkit.getOfflinePlayer(owner); + String claimType = RealEstate.claimAPI.getClaimAt(sign).isParentClaim() ? + RealEstate.instance.messages.keywordClaim : + RealEstate.instance.messages.keywordSubclaim; + String location = "[" + sign.getWorld().getName() + ", X: " + sign.getBlockX() + ", Y: " + + sign.getBlockY() + ", Z: " + sign.getBlockZ() + "]"; + + if(autoRenew && Utils.makePayment(owner, this.buyer, price, false, false)) { + startDate = LocalDateTime.now(); + if(buyerPlayer.isOnline() && RealEstate.instance.config.cfgMessageBuyer) { + Messages.sendMessage(buyerPlayer.getPlayer(), RealEstate.instance.messages.msgInfoClaimInfoRentPaymentBuyer, + claimType, + location, + RealEstate.econ.format(price)); + } + else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) { + User u = RealEstate.ess.getUser(this.buyer); + u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoRentPaymentBuyer, + claimType, + location, + RealEstate.econ.format(price))); + } + + if(seller != null) { + if(seller.isOnline() && RealEstate.instance.config.cfgMessageOwner) { + Messages.sendMessage(seller.getPlayer(), RealEstate.instance.messages.msgInfoClaimInfoRentPaymentOwner, + buyerPlayer.getName(), + claimType, + location, + RealEstate.econ.format(price)); + } + else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) { + User u = RealEstate.ess.getUser(this.owner); + u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoRentPaymentOwner, + buyerPlayer.getName(), + claimType, + location, + RealEstate.econ.format(price))); + } + } + } + else if (autoRenew) { + if(buyerPlayer.isOnline() && RealEstate.instance.config.cfgMessageBuyer) { + Messages.sendMessage(buyerPlayer.getPlayer(), RealEstate.instance.messages.msgInfoClaimInfoRentPaymentBuyerCancelled, + claimType, + location, + RealEstate.econ.format(price)); + } + else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) { + User u = RealEstate.ess.getUser(this.buyer); + u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoRentPaymentBuyerCancelled, + claimType, + location, + RealEstate.econ.format(price))); + } + unRent(false); + return; + } + else { + unRent(true); + return; + } + update(); + RealEstate.transactionsStore.saveData(); + } + + /** + * Attempts to cancel this lease transaction. + *

+ * If the player has admin permission or cancellation is forced, the lease is cancelled. + * Otherwise, an error message is sent. + *

+ * + * @param p the player attempting cancellation + * @param force if true, forces cancellation regardless of other conditions + * @return true if the transaction is cancelled; false otherwise + */ + @Override + public boolean tryCancelTransaction(Player p, boolean force) { + if(buyer != null) { + if(p.hasPermission("realestate.admin") || force == true) { + this.unRent(true); + RealEstate.transactionsStore.cancelTransaction(this); + return true; + } + else { + if(p != null) { + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); + Messages.sendMessage(p, RealEstate.instance.messages.msgErrorCantCancelAlreadyRented, + claim.isParentClaim() ? RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim); + } + return false; + } + } + else { + RealEstate.transactionsStore.cancelTransaction(this); + return true; + } + } + + /** + * Processes player interaction with the rent sign. + *

+ * Validates the player's eligibility to rent the claim and, if payment succeeds, + * updates the claim permissions, logs the transaction, and destroys the sign. + *

+ * + * @param player the player interacting with the rent sign + */ + @Override + public void interact(Player player) { + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); // getting by id creates errors for subclaims + if(claim == null || claim.isWilderness()) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimDoesNotExist); RealEstate.transactionsStore.cancelTransaction(claim); return; - } - String claimType = claim.isParentClaim() ? "claim" : "subclaim"; - String claimTypeDisplay = claim.isParentClaim() ? - RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; - - if (owner != null && owner.equals(player.getUniqueId())) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimAlreadyOwner, claimTypeDisplay); + } + String claimType = claim.isParentClaim() ? "claim" : "subclaim"; + String claimTypeDisplay = claim.isParentClaim() ? RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; + + if (owner != null && owner.equals(player.getUniqueId())) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimAlreadyOwner, claimTypeDisplay); return; } - if(claim.isParentClaim() && owner != null && !owner.equals(claim.getOwner())) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNotRentedByOwner, claimTypeDisplay); + if(claim.isParentClaim() && owner != null && !owner.equals(claim.getOwner())) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNotRentedByOwner, claimTypeDisplay); RealEstate.transactionsStore.cancelTransaction(claim); return; - } - if(!player.hasPermission("realestate." + claimType + ".rent")) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNoRentPermission, claimTypeDisplay); + } + if(!player.hasPermission("realestate." + claimType + ".rent")) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNoRentPermission, claimTypeDisplay); return; - } - if(player.getUniqueId().equals(buyer) || buyer != null) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimAlreadyRented, claimTypeDisplay); + } + if(player.getUniqueId().equals(buyer) || buyer != null) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimAlreadyRented, claimTypeDisplay); return; - } - - if(Utils.makePayment(owner, player.getUniqueId(), price, false, true)) // if payment succeed - { - buyer = player.getUniqueId(); - startDate = LocalDateTime.now(); - autoRenew = false; - claim.addPlayerPermissions(buyer, buildTrust ? ClaimPermission.BUILD : ClaimPermission.CONTAINER); - claim.addPlayerPermissions(player.getUniqueId(), ClaimPermission.MANAGE); - claim.addManager(player.getUniqueId()); - claim.setInheritPermissions(false); - RealEstate.claimAPI.saveClaim(claim); - update(); - RealEstate.transactionsStore.saveData(); - - RealEstate.instance.addLogEntry( + } + + if(Utils.makePayment(owner, player.getUniqueId(), price, false, true)) { // if payment succeed + buyer = player.getUniqueId(); + startDate = LocalDateTime.now(); + autoRenew = false; + claim.addPlayerPermissions(buyer, buildTrust ? ClaimPermission.BUILD : ClaimPermission.CONTAINER); + claim.addPlayerPermissions(player.getUniqueId(), ClaimPermission.MANAGE); + claim.addManager(player.getUniqueId()); + claim.setInheritPermissions(false); + RealEstate.claimAPI.saveClaim(claim); + update(); + RealEstate.transactionsStore.saveData(); + + RealEstate.instance.addLogEntry( "[" + RealEstate.transactionsStore.dateFormat.format(RealEstate.transactionsStore.date) + "] " + player.getName() + " has rented a " + claimType + " at " + "[" + player.getLocation().getWorld() + ", " + @@ -342,132 +361,123 @@ public void interact(Player player) "Y: " + player.getLocation().getBlockY() + ", " + "Z: " + player.getLocation().getBlockZ() + "] " + "Price: " + price + " " + RealEstate.econ.currencyNamePlural()); - - if(owner != null) - { - OfflinePlayer seller = Bukkit.getOfflinePlayer(owner); - String location = "[" + sign.getWorld().getName() + ", " + - "X: " + sign.getBlockX() + ", " + - "Y: " + sign.getBlockY() + ", " + - "Z: " + sign.getBlockZ() + "]"; - - if(RealEstate.instance.config.cfgMessageOwner && seller.isOnline()) - { - Messages.sendMessage(seller.getPlayer(), RealEstate.instance.messages.msgInfoClaimOwnerRented, - player.getName(), - claimTypeDisplay, - RealEstate.econ.format(price), - location); - } - else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) - { - User u = RealEstate.ess.getUser(this.owner); - u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoClaimOwnerRented, - player.getName(), - claimTypeDisplay, - RealEstate.econ.format(price), - location)); - } - } - - Messages.sendMessage(player, RealEstate.instance.messages.msgInfoClaimBuyerRented, - claimTypeDisplay, - RealEstate.econ.format(price)); - - destroySign(); - } - } - - @Override - public void preview(Player player) - { - IClaim claim = RealEstate.claimAPI.getClaimAt(sign); - if(player.hasPermission("realestate.info")) - { - String claimType = claim.isParentClaim() ? "claim" : "subclaim"; - String claimTypeDisplay = claim.isParentClaim() ? - RealEstate.instance.messages.keywordClaim : - RealEstate.instance.messages.keywordSubclaim; - String msg; - msg = Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoRentHeader) + "\n"; - if(buyer == null) - { - msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoGeneralRentNoBuyer, - claimTypeDisplay, - RealEstate.econ.format(price), - Utils.getTime(duration, null, true)) + "\n"; - - if(claimType.equalsIgnoreCase("claim")) - { - msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoOwner, - claim.getOwnerName()) + "\n"; - } - else - { - msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoMainOwner, - claim.getParent().getOwnerName()) + "\n"; - msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoNote) + "\n"; - } - } - else - { - int days = Period.between(startDate.toLocalDate(), LocalDate.now()).getDays(); - Duration hours = Duration.between(startDate.toLocalTime(), LocalTime.now()); - if(hours.isNegative() && !hours.isZero()) - { - hours = hours.plusHours(24); - days--; - } - int daysLeft = duration - days - 1;// we need to remove the current day - Duration timeRemaining = Duration.ofHours(24).minus(hours); - - msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoGeneralRentBuyer, - claimTypeDisplay, - Bukkit.getOfflinePlayer(buyer).getName(), - RealEstate.econ.format(price), - Utils.getTime(daysLeft, timeRemaining, true), - Utils.getTime(duration, null, true)) + "\n"; - - if((owner != null && owner.equals(player.getUniqueId()) || buyer.equals(player.getUniqueId())) && RealEstate.instance.config.cfgEnableAutoRenew) - { - msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoRentAutoRenew, - autoRenew ? - RealEstate.instance.messages.keywordEnabled : - RealEstate.instance.messages.keywordDisabled) + "\n"; - } - if(claimType.equalsIgnoreCase("claim")) - { - msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoOwner, - claim.getOwnerName()) + "\n"; - } - else - { - msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoMainOwner, - claim.getParent().getOwnerName()) + "\n"; - } - } - Messages.sendMessage(player, msg, false); - } - else - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNoInfoPermission); - } - } - - @Override - public void msgInfo(CommandSender cs) - { - IClaim claim = RealEstate.claimAPI.getClaimAt(sign); - String location = "[" + claim.getWorld().getName() + ", " + - "X: " + claim.getX() + ", " + - "Y: " + claim.getY() + ", " + - "Z: " + claim.getZ() + "]"; - - Messages.sendMessage(cs, RealEstate.instance.messages.msgInfoClaimInfoRentOneline, - claim.getArea() + "", - location, - RealEstate.econ.format(price), - Utils.getTime(duration, Duration.ZERO, false)); - } - + + if(owner != null) { + OfflinePlayer seller = Bukkit.getOfflinePlayer(owner); + String location = "[" + sign.getWorld().getName() + ", " + + "X: " + sign.getBlockX() + ", " + + "Y: " + sign.getBlockY() + ", " + + "Z: " + sign.getBlockZ() + "]"; + + if(RealEstate.instance.config.cfgMessageOwner && seller.isOnline()) { + Messages.sendMessage(seller.getPlayer(), RealEstate.instance.messages.msgInfoClaimOwnerRented, + player.getName(), + claimTypeDisplay, + RealEstate.econ.format(price), + location); + } + else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) { + User u = RealEstate.ess.getUser(this.owner); + u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoClaimOwnerRented, + player.getName(), + claimTypeDisplay, + RealEstate.econ.format(price), + location)); + } + } + + Messages.sendMessage(player, RealEstate.instance.messages.msgInfoClaimBuyerRented, + claimTypeDisplay, + RealEstate.econ.format(price)); + + destroySign(); + } + } + + /** + * Previews detailed lease information to a player. + *

+ * This includes the lease header, current renter (if any), remaining time, and owner information. + *

+ * + * @param player the player to receive the lease preview + */ + @Override + public void preview(Player player) { + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); + if(player.hasPermission("realestate.info")) { + String claimType = claim.isParentClaim() ? "claim" : "subclaim"; + String claimTypeDisplay = claim.isParentClaim() ? + RealEstate.instance.messages.keywordClaim : + RealEstate.instance.messages.keywordSubclaim; + String msg; + msg = Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoRentHeader) + "\n"; + if(buyer == null) { + msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoGeneralRentNoBuyer, + claimTypeDisplay, + RealEstate.econ.format(price), + Utils.getTime(duration, null, true)) + "\n"; + if(claimType.equalsIgnoreCase("claim")) { + msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoOwner, + claim.getOwnerName()) + "\n"; + } + else { + msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoMainOwner, + claim.getParent().getOwnerName()) + "\n"; + msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoNote) + "\n"; + } + } + else { + int days = Period.between(startDate.toLocalDate(), LocalDate.now()).getDays(); + Duration hours = Duration.between(startDate.toLocalTime(), LocalTime.now()); + if(hours.isNegative() && !hours.isZero()) { + hours = hours.plusHours(24); + days--; + } + int daysLeft = duration - days - 1; // subtract current day + Duration timeRemaining = Duration.ofHours(24).minus(hours); + msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoGeneralRentBuyer, + claimTypeDisplay, + Bukkit.getOfflinePlayer(buyer).getName(), + RealEstate.econ.format(price), + Utils.getTime(daysLeft, timeRemaining, true), + Utils.getTime(duration, null, true)) + "\n"; + if((owner != null && owner.equals(player.getUniqueId()) || buyer.equals(player.getUniqueId())) && RealEstate.instance.config.cfgEnableAutoRenew) { + msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoRentAutoRenew, + autoRenew ? RealEstate.instance.messages.keywordEnabled : RealEstate.instance.messages.keywordDisabled) + "\n"; + } + if(claimType.equalsIgnoreCase("claim")) { + msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoOwner, + claim.getOwnerName()) + "\n"; + } + else { + msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoMainOwner, + claim.getParent().getOwnerName()) + "\n"; + } + } + Messages.sendMessage(player, msg, false); + } + else { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNoInfoPermission); + } + } + + /** + * Sends a one-line summary of the lease transaction to a command sender. + * + * @param cs the command sender (console or player) to receive the summary + */ + @Override + public void msgInfo(CommandSender cs) { + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); + String location = "[" + claim.getWorld().getName() + ", " + + "X: " + claim.getX() + ", " + + "Y: " + claim.getY() + ", " + + "Z: " + claim.getZ() + "]"; + Messages.sendMessage(cs, RealEstate.instance.messages.msgInfoClaimInfoRentOneline, + claim.getArea() + "", + location, + RealEstate.econ.format(price), + Utils.getTime(duration, Duration.ZERO, false)); + } } diff --git a/src/me/EtienneDx/RealEstate/Transactions/ClaimSell.java b/src/me/EtienneDx/RealEstate/Transactions/ClaimSell.java index 2f6a2d3..61a0db5 100644 --- a/src/me/EtienneDx/RealEstate/Transactions/ClaimSell.java +++ b/src/me/EtienneDx/RealEstate/Transactions/ClaimSell.java @@ -1,234 +1,234 @@ package me.EtienneDx.RealEstate.Transactions; import org.bukkit.entity.Player; - import com.earth2me.essentials.User; - import me.EtienneDx.RealEstate.Messages; import me.EtienneDx.RealEstate.RealEstate; import me.EtienneDx.RealEstate.Utils; import me.EtienneDx.RealEstate.RealEstateSign; import me.EtienneDx.RealEstate.ClaimAPI.IClaim; import net.md_5.bungee.api.ChatColor; - import java.util.Map; - import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.OfflinePlayer; import org.bukkit.block.Sign; import org.bukkit.command.CommandSender; -public class ClaimSell extends ClaimTransaction -{ - public ClaimSell(IClaim claim, Player player, double price, Location sign) - { - super(claim, player, price, sign); - } - - public ClaimSell(Map map) - { - super(map); - } +/** + * Represents a sell transaction for a claim. + *

+ * This transaction is used when a claim is put up for sale. It handles updating the sign display with + * sale information, processing the transaction, and transferring ownership. + *

+ */ +public class ClaimSell extends ClaimTransaction { + + /** + * Constructs a new ClaimSell transaction. + * + * @param claim the claim being sold + * @param player the player initiating the sale + * @param price the sale price + * @param sign the location of the sign representing the sale + */ + public ClaimSell(IClaim claim, Player player, double price, Location sign) { + super(claim, player, price, sign); + } + + /** + * Constructs a ClaimSell transaction from a serialized map. + * + * @param map the map containing serialized data for this sale transaction + */ + public ClaimSell(Map map) { + super(map); + } + + /** + * Updates the sign associated with this sale transaction. + *

+ * If the sign is valid, it updates each line with the sale information (header, sale type, owner, + * and price). If the sign is not valid, the transaction is canceled. + *

+ * + * @return false to indicate that the transaction remains active. + */ + @Override + public boolean update() { + if (sign.getBlock().getState() instanceof Sign) { + RealEstateSign s = new RealEstateSign((Sign) sign.getBlock().getState()); + s.setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); + s.setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceSell); + s.setLine(2, owner != null ? Utils.getSignString(Bukkit.getOfflinePlayer(owner).getName()) : "SERVER"); + if (RealEstate.instance.config.cfgUseCurrencySymbol) { + if (RealEstate.instance.config.cfgUseDecimalCurrency == false) { + s.setLine(3, RealEstate.instance.config.cfgCurrencySymbol + " " + (int) Math.round(price)); + } else { + s.setLine(3, RealEstate.instance.config.cfgCurrencySymbol + " " + price); + } + } else { + if (RealEstate.instance.config.cfgUseDecimalCurrency == false) { + s.setLine(3, (int) Math.round(price) + " " + RealEstate.econ.currencyNamePlural()); + } else { + s.setLine(3, price + " " + RealEstate.econ.currencyNamePlural()); + } + } + s.update(true); + } else { + RealEstate.transactionsStore.cancelTransaction(this); + } + return false; + } - @Override - public boolean update() - { - if(sign.getBlock().getState() instanceof Sign) - { - RealEstateSign s = new RealEstateSign((Sign) sign.getBlock().getState()); - s.setLine(0, Messages.getMessage(RealEstate.instance.config.cfgSignsHeader, false)); - s.setLine(1, ChatColor.DARK_GREEN + RealEstate.instance.config.cfgReplaceSell); - s.setLine(2, owner != null ? Utils.getSignString(Bukkit.getOfflinePlayer(owner).getName()) : "SERVER"); - if(RealEstate.instance.config.cfgUseCurrencySymbol) - { - if(RealEstate.instance.config.cfgUseDecimalCurrency == false) - { - s.setLine(3, RealEstate.instance.config.cfgCurrencySymbol + " " + (int)Math.round(price)); - } - else - { - s.setLine(3, RealEstate.instance.config.cfgCurrencySymbol + " " + price); - } - } - else - { - if(RealEstate.instance.config.cfgUseDecimalCurrency == false) - { - s.setLine(3, (int)Math.round(price) + " " + RealEstate.econ.currencyNamePlural()); - } - else - { - s.setLine(3, price + " " + RealEstate.econ.currencyNamePlural()); - } - } - s.update(true); - } - else - { - RealEstate.transactionsStore.cancelTransaction(this); - } - return false; - } - - @Override - public boolean tryCancelTransaction(Player p, boolean force) - { - // nothing special here, this transaction can only be waiting for a buyer - RealEstate.transactionsStore.cancelTransaction(this); - return true; - } + /** + * Attempts to cancel the sale transaction. + *

+ * In the case of a sale, the transaction can only be canceled by removing the sign. + *

+ * + * @param p the player attempting cancellation (unused in this context) + * @param force if true, forces cancellation + * @return true after the transaction is canceled + */ + @Override + public boolean tryCancelTransaction(Player p, boolean force) { + // Nothing special here; this transaction can only be waiting for a buyer. + RealEstate.transactionsStore.cancelTransaction(this); + return true; + } - @Override - public void interact(Player player) - { - IClaim claim = RealEstate.claimAPI.getClaimAt(sign); - if(claim == null || claim.isWilderness()) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimDoesNotExist); + /** + * Processes player interaction with the sale sign. + *

+ * Checks that the claim exists, that the player has permission to buy the claim, and that the + * player has enough claim blocks if required. If the payment succeeds, ownership is transferred + * and appropriate messages are sent. + *

+ * + * @param player the player interacting with the sale sign + */ + @Override + public void interact(Player player) { + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); + if (claim == null || claim.isWilderness()) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimDoesNotExist); RealEstate.transactionsStore.cancelTransaction(claim); return; - } - String claimType = claim.isParentClaim() ? "claim" : "subclaim"; - String claimTypeDisplay = claim.isParentClaim() ? - RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; - - if (player.getUniqueId().equals(owner)) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimAlreadyOwner, claimTypeDisplay); + } + String claimType = claim.isParentClaim() ? "claim" : "subclaim"; + String claimTypeDisplay = claim.isParentClaim() ? + RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; + + if (player.getUniqueId().equals(owner)) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimAlreadyOwner, claimTypeDisplay); return; } - if(claim.isParentClaim() && owner != null && !owner.equals(claim.getOwner())) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNotSoldByOwner, claimTypeDisplay); + if (claim.isParentClaim() && owner != null && !owner.equals(claim.getOwner())) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNotSoldByOwner, claimTypeDisplay); RealEstate.transactionsStore.cancelTransaction(claim); return; - } - if(!player.hasPermission("realestate." + claimType + ".buy")) - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNoBuyPermission, claimTypeDisplay); + } + if (!player.hasPermission("realestate." + claimType + ".buy")) { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNoBuyPermission, claimTypeDisplay); return; - } - // for real claims, you may need to have enough claim blocks in reserve to purchase it (if transferClaimBlocks is false) - if(claimType.equalsIgnoreCase("claim") && !RealEstate.instance.config.cfgTransferClaimBlocks && - RealEstate.claimAPI.getPlayerData(player.getUniqueId()).getRemainingClaimBlocks() < claim.getArea()) - { - int remaining = RealEstate.claimAPI.getPlayerData(player.getUniqueId()).getRemainingClaimBlocks(); - int area = claim.getArea(); - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNoClaimBlocks, - area + "", - remaining + "", - (area - remaining) + ""); + } + // Check for sufficient claim blocks if necessary. + if (claimType.equalsIgnoreCase("claim") && !RealEstate.instance.config.cfgTransferClaimBlocks && + RealEstate.claimAPI.getPlayerData(player.getUniqueId()).getRemainingClaimBlocks() < claim.getArea()) { + int remaining = RealEstate.claimAPI.getPlayerData(player.getUniqueId()).getRemainingClaimBlocks(); + int area = claim.getArea(); + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNoClaimBlocks, + area + "", remaining + "", (area - remaining) + ""); return; - } - // the player has the right to buy, let's make the payment - - if(Utils.makePayment(owner, player.getUniqueId(), price, false, true))// if payment succeed - { - Utils.transferClaim(claim, player.getUniqueId(), owner); - // normally, this is always the case, so it's not necessary, but until I proven my point, here - if(claim.isSubClaim() || claim.getOwner().equals(player.getUniqueId())) - { - String location = "[" + player.getLocation().getWorld() + ", " + - "X: " + player.getLocation().getBlockX() + ", " + - "Y: " + player.getLocation().getBlockY() + ", " + - "Z: " + player.getLocation().getBlockZ() + "]"; - - Messages.sendMessage(player, RealEstate.instance.messages.msgInfoClaimBuyerSold, - claimTypeDisplay, - RealEstate.econ.format(price)); - + } + // Process payment and transfer ownership. + if (Utils.makePayment(owner, player.getUniqueId(), price, false, true)) { // Payment succeeded + Utils.transferClaim(claim, player.getUniqueId(), owner); + // Log transaction if claim ownership transfer is successful. + if (claim.isSubClaim() || claim.getOwner().equals(player.getUniqueId())) { + String location = "[" + player.getLocation().getWorld() + ", " + + "X: " + player.getLocation().getBlockX() + ", " + + "Y: " + player.getLocation().getBlockY() + ", " + + "Z: " + player.getLocation().getBlockZ() + "]"; + Messages.sendMessage(player, RealEstate.instance.messages.msgInfoClaimBuyerSold, + claimTypeDisplay, RealEstate.econ.format(price)); RealEstate.instance.addLogEntry( - "[" + RealEstate.transactionsStore.dateFormat.format(RealEstate.transactionsStore.date) + "] " + player.getName() + - " has purchased a " + claimType + " at " + - "[" + player.getLocation().getWorld() + ", " + - "X: " + player.getLocation().getBlockX() + ", " + - "Y: " + player.getLocation().getBlockY() + ", " + - "Z: " + player.getLocation().getBlockZ() + "] " + - "Price: " + price + " " + RealEstate.econ.currencyNamePlural()); - - if(RealEstate.instance.config.cfgMessageOwner && owner != null) - { - OfflinePlayer oldOwner = Bukkit.getOfflinePlayer(owner); - if(oldOwner.isOnline()) - { - Messages.sendMessage(oldOwner.getPlayer(), RealEstate.instance.messages.msgInfoClaimOwnerSold, - player.getName(), - claimTypeDisplay, - RealEstate.econ.format(price), - location); - } - else if(RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) - { - User u = RealEstate.ess.getUser(owner); - u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoClaimOwnerSold, - player.getName(), - claimTypeDisplay, - RealEstate.econ.format(price), - location)); - } + "[" + RealEstate.transactionsStore.dateFormat.format(RealEstate.transactionsStore.date) + "] " + + player.getName() + " has purchased a " + claimType + " at " + + "[" + player.getLocation().getWorld() + ", " + + "X: " + player.getLocation().getBlockX() + ", " + + "Y: " + player.getLocation().getBlockY() + ", " + + "Z: " + player.getLocation().getBlockZ() + "] " + + "Price: " + price + " " + RealEstate.econ.currencyNamePlural()); + if (RealEstate.instance.config.cfgMessageOwner && owner != null) { + OfflinePlayer oldOwner = Bukkit.getOfflinePlayer(owner); + if (oldOwner.isOnline()) { + Messages.sendMessage(oldOwner.getPlayer(), RealEstate.instance.messages.msgInfoClaimOwnerSold, + player.getName(), claimTypeDisplay, RealEstate.econ.format(price), location); + } else if (RealEstate.instance.config.cfgMailOffline && RealEstate.ess != null) { + User u = RealEstate.ess.getUser(owner); + u.addMail(Messages.getMessage(RealEstate.instance.messages.msgInfoClaimOwnerSold, + player.getName(), claimTypeDisplay, RealEstate.econ.format(price), location)); + } } - } - else - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorUnexpected); + } else { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorUnexpected); return; } - RealEstate.transactionsStore.cancelTransaction(claim); - } - } - - @Override - public void preview(Player player) - { - IClaim claim = RealEstate.claimAPI.getClaimAt(sign); - if(player.hasPermission("realestate.info")) - { - String claimType = claim.isParentClaim() ? "claim" : "subclaim"; - String claimTypeDisplay = claim.isParentClaim() ? - RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; - - String msg = Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoSellHeader) + "\n"; - - msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoSellGeneral, - claimTypeDisplay, - RealEstate.econ.format(price)) + "\n"; - - if(claimType.equalsIgnoreCase("claim")) - { - msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoOwner, - claim.getOwnerName()) + "\n"; - } - else - { - msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoMainOwner, - claim.getParent().getOwnerName()) + "\n"; - msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoNote) + "\n"; - } - Messages.sendMessage(player, msg, false); - } - else - { - Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNoInfoPermission); - } - } + RealEstate.transactionsStore.cancelTransaction(claim); + } + } - @Override - public void msgInfo(CommandSender cs) - { - IClaim claim = RealEstate.claimAPI.getClaimAt(sign); - if(claim == null || claim.isWilderness()) { - tryCancelTransaction(null, true); - return; - } - String location = "[" + claim.getWorld().getName() + ", " + - "X: " + claim.getX() + ", " + - "Y: " + claim.getY() + ", " + - "Z: " + claim.getZ() + "]"; + /** + * Sends a detailed preview of the sale transaction to a player. + *

+ * The preview includes the sale header, sale details (type and price), and the owner information. + *

+ * + * @param player the player to receive the preview + */ + @Override + public void preview(Player player) { + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); + if (player.hasPermission("realestate.info")) { + String claimType = claim.isParentClaim() ? "claim" : "subclaim"; + String claimTypeDisplay = claim.isParentClaim() ? + RealEstate.instance.messages.keywordClaim : RealEstate.instance.messages.keywordSubclaim; + String msg = Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoSellHeader) + "\n"; + msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoSellGeneral, + claimTypeDisplay, RealEstate.econ.format(price)) + "\n"; + if (claimType.equalsIgnoreCase("claim")) { + msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoOwner, + claim.getOwnerName()) + "\n"; + } else { + msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoMainOwner, + claim.getParent().getOwnerName()) + "\n"; + msg += Messages.getMessage(RealEstate.instance.messages.msgInfoClaimInfoNote) + "\n"; + } + Messages.sendMessage(player, msg, false); + } else { + Messages.sendMessage(player, RealEstate.instance.messages.msgErrorClaimNoInfoPermission); + } + } - Messages.sendMessage(cs, RealEstate.instance.messages.msgInfoClaimInfoSellOneline, - claim.getArea() + "", - location, - RealEstate.econ.format(price)); - } + /** + * Sends a one-line summary of the sale transaction to a command sender. + * + * @param cs the command sender to receive the sale summary + */ + @Override + public void msgInfo(CommandSender cs) { + IClaim claim = RealEstate.claimAPI.getClaimAt(sign); + if (claim == null || claim.isWilderness()) { + tryCancelTransaction(null, true); + return; + } + String location = "[" + claim.getWorld().getName() + ", " + + "X: " + claim.getX() + ", " + + "Y: " + claim.getY() + ", " + + "Z: " + claim.getZ() + "]"; + Messages.sendMessage(cs, RealEstate.instance.messages.msgInfoClaimInfoSellOneline, + claim.getArea() + "", location, RealEstate.econ.format(price)); + } } diff --git a/src/me/EtienneDx/RealEstate/Transactions/ClaimTransaction.java b/src/me/EtienneDx/RealEstate/Transactions/ClaimTransaction.java index 3ab2330..a82c660 100644 --- a/src/me/EtienneDx/RealEstate/Transactions/ClaimTransaction.java +++ b/src/me/EtienneDx/RealEstate/Transactions/ClaimTransaction.java @@ -10,12 +10,44 @@ import org.bukkit.entity.Player; import me.EtienneDx.RealEstate.ClaimAPI.IClaim; +/** + * Represents a generic claim transaction. + *

+ * This abstract class provides the base for all claim-related transactions + * (e.g. selling, renting, leasing). It handles common data such as the claim ID, + * owner, price, and sign location, and implements serialization for storage. + *

+ */ public abstract class ClaimTransaction implements ConfigurationSerializable, Transaction { + + /** + * The unique ID of the claim. + */ public String claimId; + + /** + * The UUID of the claim owner. If null, the claim is considered an admin claim. + */ public UUID owner = null; + + /** + * The transaction price. + */ public double price; + + /** + * The location of the sign representing this transaction. + */ public Location sign = null; + /** + * Constructs a new ClaimTransaction. + * + * @param claim the claim involved in the transaction + * @param player the player initiating the transaction; may be null for admin claims + * @param price the transaction price + * @param sign the location of the sign associated with the transaction + */ public ClaimTransaction(IClaim claim, Player player, double price, Location sign) { this.claimId = claim.getId(); this.owner = player != null ? player.getUniqueId() : null; @@ -23,11 +55,16 @@ public ClaimTransaction(IClaim claim, Player player, double price, Location sign this.sign = sign; } + /** + * Constructs a ClaimTransaction from a serialized map. + * + * @param map the map containing serialized transaction data + */ public ClaimTransaction(Map map) { this.claimId = String.valueOf(map.get("claimId")); if (map.get("owner") != null) { String ownerStr = (String) map.get("owner"); - // If the owner string is "SERVER", set owner to null (i.e. admin claim) + // If the owner string is "SERVER", set owner to null (indicating an admin claim) if ("SERVER".equalsIgnoreCase(ownerStr)) { this.owner = null; } else { @@ -35,59 +72,110 @@ public ClaimTransaction(Map map) { } } this.price = (double) map.get("price"); - if(map.get("signLocation") != null) + if (map.get("signLocation") != null) this.sign = (Location) map.get("signLocation"); } - + /** + * Default constructor. + */ public ClaimTransaction() { } + /** + * Serializes this transaction into a map. + * + * @return a map containing the serialized transaction data + */ @Override public Map serialize() { Map map = new HashMap<>(); map.put("claimId", this.claimId); - if(owner != null) + if (owner != null) map.put("owner", owner.toString()); map.put("price", this.price); - if(sign != null) + if (sign != null) map.put("signLocation", sign); return map; } + /** + * Retrieves the Block that holds the sign for this transaction. + * + * @return the Block if the sign state is an instance of {@code Sign}, otherwise null + */ @Override public Block getHolder() { return sign.getBlock().getState() instanceof Sign ? sign.getBlock() : null; } + /** + * Returns the owner's UUID. + * + * @return the UUID of the owner, or null for admin claims + */ @Override public UUID getOwner() { return owner; } + /** + * Sets the owner's UUID. + * + * @param newOwner the new owner's UUID + */ @Override public void setOwner(UUID newOwner) { this.owner = newOwner; } + /** + * Attempts to cancel the transaction using the specified player. + * + * @param p the player attempting to cancel the transaction + * @return true if the cancellation was successful, false otherwise + */ @Override public boolean tryCancelTransaction(Player p) { return this.tryCancelTransaction(p, false); } // --- Added getter methods --- + + /** + * Retrieves the claim associated with this transaction. + *

+ * This method uses the RealEstate API to obtain the claim based on the sign location. + *

+ * + * @return the {@link IClaim} associated with this transaction + */ public IClaim getClaim() { - // Use your API to retrieve the claim from the sign. return me.EtienneDx.RealEstate.RealEstate.claimAPI.getClaimAt(sign); } + /** + * Returns the owner's UUID. + * + * @return the UUID of the owner, or null if not set + */ public UUID getOwnerUUID() { return owner; } + /** + * Returns the transaction price. + * + * @return the price of the transaction + */ public double getPrice() { return price; } + /** + * Returns the location of the sign associated with this transaction. + * + * @return the sign {@link Location} + */ public Location getSign() { return sign; } diff --git a/src/me/EtienneDx/RealEstate/Transactions/ExitOffer.java b/src/me/EtienneDx/RealEstate/Transactions/ExitOffer.java index 076f991..95c1d6b 100644 --- a/src/me/EtienneDx/RealEstate/Transactions/ExitOffer.java +++ b/src/me/EtienneDx/RealEstate/Transactions/ExitOffer.java @@ -5,20 +5,51 @@ import java.util.UUID; import org.bukkit.configuration.serialization.ConfigurationSerializable; +/** + * Represents an exit offer for a RealEstate transaction. + *

+ * An exit offer is used to propose an early exit from an ongoing transaction. + * It contains the UUID of the player making the offer and the offered price. + *

+ */ public class ExitOffer implements ConfigurationSerializable { + + /** + * The UUID of the player who made the exit offer. + */ public UUID offerBy; + + /** + * The price offered to exit the transaction. + */ public double price; + /** + * Constructs an ExitOffer with the specified offeror and price. + * + * @param offerBy the UUID of the player making the exit offer + * @param price the offered price for the exit + */ public ExitOffer(UUID offerBy, double price) { this.offerBy = offerBy; this.price = price; } + /** + * Constructs an ExitOffer from a serialized map. + * + * @param map a map containing the serialized exit offer data + */ public ExitOffer(Map map) { offerBy = UUID.fromString((String) map.get("offerBy")); price = (double) map.get("price"); } + /** + * Serializes this ExitOffer into a map. + * + * @return a map representation of this ExitOffer + */ @Override public Map serialize() { HashMap map = new HashMap<>(); diff --git a/src/me/EtienneDx/RealEstate/Transactions/Transaction.java b/src/me/EtienneDx/RealEstate/Transactions/Transaction.java index 04e9086..a39a2fc 100644 --- a/src/me/EtienneDx/RealEstate/Transactions/Transaction.java +++ b/src/me/EtienneDx/RealEstate/Transactions/Transaction.java @@ -5,14 +5,96 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +/** + * Represents a transaction for a real estate claim. + *

+ * Transactions may include operations such as selling, renting, leasing, or auctioning claims. + * This interface defines the core methods that any transaction type must implement. + *

+ */ public interface Transaction { + + /** + * Returns the block that acts as the physical holder (e.g., a sign) for this transaction. + * + * @return the Block representing the transaction holder, or {@code null} if none exists + */ public Block getHolder(); + + /** + * Retrieves the UUID of the owner associated with this transaction. + * + * @return the owner's UUID + */ public UUID getOwner(); + + /** + * Sets the owner of this transaction to a new UUID. + * + * @param newOwner the new owner's UUID + */ public void setOwner(UUID newOwner); + + /** + * Processes a player's interaction with the transaction. + *

+ * This method is called when a player interacts with the transaction's associated block or sign. + *

+ * + * @param player the player interacting with the transaction + */ public void interact(Player player); + + /** + * Provides a detailed preview of the transaction information to a player. + *

+ * Typically, this displays details such as price, claim location, and other relevant transaction data. + *

+ * + * @param player the player for whom the preview is generated + */ public void preview(Player player); + + /** + * Updates the current state of the transaction. + *

+ * This method is usually called periodically to update transaction details (e.g., sign text) or to finalize the transaction. + *

+ * + * @return {@code true} if the transaction has ended or been finalized; {@code false} otherwise + */ public boolean update(); + + /** + * Attempts to cancel the transaction. + *

+ * This method checks if the transaction can be cancelled under current conditions. + *

+ * + * @param p the player requesting cancellation + * @return {@code true} if the transaction was successfully cancelled; {@code false} otherwise + */ public boolean tryCancelTransaction(Player p); + + /** + * Attempts to cancel the transaction with an option to force the cancellation. + *

+ * When {@code force} is {@code true}, the cancellation is performed regardless of normal conditions. + *

+ * + * @param p the player requesting cancellation + * @param force if {@code true}, forces the cancellation of the transaction + * @return {@code true} if the transaction was successfully cancelled; {@code false} otherwise + */ public boolean tryCancelTransaction(Player p, boolean force); + + /** + * Sends transaction information to the specified command sender. + *

+ * This method is typically used to provide a summary of the transaction via command output. + *

+ * + * @param cs the CommandSender (player or console) to receive the transaction info + */ public void msgInfo(CommandSender cs); } diff --git a/src/me/EtienneDx/RealEstate/Transactions/TransactionsStore.java b/src/me/EtienneDx/RealEstate/Transactions/TransactionsStore.java index 8be7853..9caa83d 100644 --- a/src/me/EtienneDx/RealEstate/Transactions/TransactionsStore.java +++ b/src/me/EtienneDx/RealEstate/Transactions/TransactionsStore.java @@ -32,22 +32,51 @@ import me.EtienneDx.RealEstate.Utils; import me.EtienneDx.RealEstate.ClaimAPI.IClaim; +/** + * Manages real estate transactions including selling, renting, leasing, and auctions. + */ public class TransactionsStore { + /** Path to the transactions data file. */ public final String dataFilePath = RealEstate.pluginDirPath + "transactions.yml"; - // Make these public so that other classes can reference them if needed. + + /** Date format used for logging transactions. */ public DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); + + /** Current date instance used for logging. */ public Date date = new Date(); + /** Stores all claim sell transactions. */ public HashMap claimSell; + + /** Stores all claim rent transactions. */ public HashMap claimRent; + + /** Stores all claim lease transactions. */ public HashMap claimLease; + + /** Stores all claim auction transactions. */ public HashMap claimAuction; - public enum StorageType { FILE, MYSQL, SQLITE } + /** Enumeration of storage types supported by the plugin. */ + public enum StorageType { + /** File-based storage. */ + FILE, + /** MySQL database storage. */ + MYSQL, + /** SQLite database storage. */ + SQLITE } + + /** The storage type used for transaction data. */ private StorageType storageType; + + /** The database connection used for transaction data. */ private Connection dbConnection = null; + + /** + * Initializes the transaction store based on the configuration settings. + */ public TransactionsStore() { String storageStr = RealEstate.instance.config.databaseType.toLowerCase(); switch(storageStr) { @@ -101,7 +130,9 @@ public void run() { }.runTaskTimer(RealEstate.instance, 1200L, 1200L); } - /* FILE-BASED METHODS */ + /** + * Loads transaction data from a file-based storage system. + */ public void loadDataFromFile() { File file = new File(this.dataFilePath); if(file.exists()) { @@ -142,6 +173,9 @@ public void loadDataFromFile() { } } + /** + * Saves transaction data to a file-based storage system. + */ public void saveDataToFile() { YamlConfiguration config = new YamlConfiguration(); for (ClaimSell cs : claimSell.values()) @@ -511,6 +545,9 @@ private void saveDataToDatabase() { } } + /** + * Saves transaction data, either to a file or database depending on configuration. + */ public void saveData() { if(storageType == StorageType.FILE) saveDataToFile(); @@ -518,6 +555,11 @@ public void saveData() { saveDataToDatabase(); } + /** + * Checks if there is any active transaction on a given claim. + * @param claim The claim to check. + * @return True if a transaction exists, otherwise false. + */ public boolean anyTransaction(IClaim claim) { return claim != null && !claim.isWilderness() && @@ -527,6 +569,11 @@ public boolean anyTransaction(IClaim claim) { claimAuction.containsKey(claim.getId())); } + /** + * Retrieves the active transaction associated with a given claim. + * @param claim The claim to retrieve the transaction for. + * @return The transaction if found, otherwise null. + */ public Transaction getTransaction(IClaim claim) { if(claimSell.containsKey(claim.getId())) return claimSell.get(claim.getId()); @@ -539,6 +586,10 @@ public Transaction getTransaction(IClaim claim) { return null; } + /** + * Cancels an active transaction associated with a given claim. + * @param claim The claim whose transaction needs to be canceled. + */ public void cancelTransaction(IClaim claim) { if(anyTransaction(claim)) { Transaction tr = getTransaction(claim); @@ -547,6 +598,10 @@ public void cancelTransaction(IClaim claim) { saveData(); } + /** + * Cancels a specific transaction. + * @param tr The transaction to cancel. + */ public void cancelTransaction(Transaction tr) { if(tr.getHolder() != null) tr.getHolder().breakNaturally(); @@ -561,6 +616,11 @@ public void cancelTransaction(Transaction tr) { saveData(); } + /** + * Determines if a given transaction can be canceled. + * @param tr The transaction to check. + * @return True if the transaction can be canceled, otherwise false. + */ public boolean canCancelTransaction(Transaction tr) { // For auction, rent, lease we check if buyer is null (or if cancellation is forced) return tr instanceof ClaimSell || @@ -569,7 +629,13 @@ public boolean canCancelTransaction(Transaction tr) { (tr instanceof ClaimLease && ((ClaimLease)tr).getBuyer() == null); } - // Transaction creation methods: + /** + * Creates a new sell transaction for a claim. + * @param claim The claim being sold. + * @param player The player selling the claim. + * @param price The price of the claim. + * @param sign The location of the transaction sign. + */ public void sell(IClaim claim, Player player, double price, Location sign) { ClaimSell cs = new ClaimSell(claim, claim.isAdminClaim() ? null : player, price, sign); claimSell.put(claim.getId(), cs); @@ -602,7 +668,15 @@ public void sell(IClaim claim, Player player, double price, Location sign) { } - + /** + * Creates a new rent transaction for a claim. + * @param claim The claim being rented. + * @param player The player renting the claim. + * @param price The rental price. + * @param sign The location of the transaction sign. + * @param duration The rental duration in days. + * @param buildTrust Whether the renter gains build trust. + */ public void rent(IClaim claim, Player player, double price, Location sign, int duration, boolean buildTrust) { ClaimRent cr = new ClaimRent(claim, claim.isAdminClaim() ? null : player, price, sign, duration, buildTrust); claimRent.put(claim.getId(), cr); @@ -636,6 +710,16 @@ public void rent(IClaim claim, Player player, double price, Location sign, int d } } + + /** + * Creates a new lease transaction for a claim. + * @param claim The claim being leased. + * @param player The player leasing the claim. + * @param price The lease price. + * @param sign The location of the transaction sign. + * @param frequency The payment frequency. + * @param paymentsCount The number of payments required. + */ public void lease(IClaim claim, Player player, double price, Location sign, int frequency, int paymentsCount) { ClaimLease cl = new ClaimLease(claim, claim.isAdminClaim() ? null : player, price, sign, frequency, paymentsCount); claimLease.put(claim.getId(), cl); @@ -669,6 +753,15 @@ public void lease(IClaim claim, Player player, double price, Location sign, int } } + /** + * Creates a new auction transaction for a claim. + * @param claim The claim being auctioned. + * @param player The player starting the auction. + * @param price The starting price. + * @param sign The location of the transaction sign. + * @param duration The auction duration in days. + * @param bidStep The minimum bid step. + */ public void auction(IClaim claim, Player player, double price, Location sign, int duration, double bidStep) { LocalDateTime endDate = LocalDateTime.now().plusDays(duration); // Use the Auction constructor with a LocalDateTime endDate. @@ -706,6 +799,11 @@ public void auction(IClaim claim, Player player, double price, Location sign, in } } + /** + * Retrieves the active transaction associated with a player. + * @param player The player to check. + * @return The transaction if found, otherwise null. + */ public Transaction getTransaction(Player player) { if(player == null) return null; IClaim c = RealEstate.claimAPI.getClaimAt(player.getLocation()); diff --git a/src/me/EtienneDx/RealEstate/Utils.java b/src/me/EtienneDx/RealEstate/Utils.java index 339b329..38b5d75 100644 --- a/src/me/EtienneDx/RealEstate/Utils.java +++ b/src/me/EtienneDx/RealEstate/Utils.java @@ -11,145 +11,204 @@ import me.EtienneDx.RealEstate.ClaimAPI.IPlayerData; import net.milkbowl.vault.economy.EconomyResponse; +/** + * A utility class containing helper methods for the RealEstate plugin. + *

+ * This class provides methods for processing economy transactions, + * formatting time durations, transferring claim blocks between players, + * and truncating strings for sign display. + *

+ */ public class Utils { + /** + * Instantiates a new Utils object. + */ + public Utils() {} + + /** + * Processes a payment transaction between two players. + *

+ * The method attempts to withdraw a specified amount from the giver and deposit it to the receiver. + * If the giver does not have sufficient funds or if any transaction fails, + * an error message is sent (if enabled) and the transaction is aborted. + *

+ * + * @param receiver the UUID of the player who will receive money (can be null for the server) + * @param giver the UUID of the player from whom money is to be withdrawn + * @param amount the amount of money to transfer + * @param msgReceiver if {@code true} and the receiver is online, a message will be sent + * @param msgGiver if {@code true} and the giver is online, a message will be sent + * @return {@code true} if the payment was successful; {@code false} otherwise. + */ public static boolean makePayment(UUID receiver, UUID giver, double amount, boolean msgReceiver, boolean msgGiver) { - // seller might be null if it is the server - OfflinePlayer giveTo = receiver != null ? Bukkit.getOfflinePlayer(receiver) : null; - OfflinePlayer takeFrom = giver != null ? Bukkit.getOfflinePlayer(giver) : null; - if(takeFrom != null && !RealEstate.econ.has(takeFrom, amount)) - { - if(takeFrom.isOnline() && msgGiver) - { - Messages.sendMessage(takeFrom.getPlayer(), RealEstate.instance.messages.msgErrorNoMoneySelf); - } - if(giveTo != null && giveTo.isOnline() && msgReceiver) - { - Messages.sendMessage(giveTo.getPlayer(), RealEstate.instance.messages.msgErrorNoMoneyOther, takeFrom.getName()); - } - return false; - } - if(takeFrom != null) - { - EconomyResponse resp = RealEstate.econ.withdrawPlayer(takeFrom, amount); - if(!resp.transactionSuccess()) - { - if(takeFrom.isOnline() && msgGiver) - { - Messages.sendMessage(takeFrom.getPlayer(), RealEstate.instance.messages.msgErrorNoWithdrawSelf); - } - if(giveTo != null && giveTo.isOnline() && msgReceiver) - { - Messages.sendMessage(giveTo.getPlayer(), RealEstate.instance.messages.msgErrorNoWithdrawOther); - } - return false; - } - } - if(giveTo != null) - { - EconomyResponse resp = RealEstate.econ.depositPlayer(giveTo, amount); - if(!resp.transactionSuccess()) - { - if(takeFrom != null && takeFrom.isOnline() && msgGiver) - { - Messages.sendMessage(giveTo.getPlayer(), RealEstate.instance.messages.msgErrorNoDepositOther, giveTo.getName()); - } - if(takeFrom != null && giveTo != null && giveTo.isOnline() && msgReceiver) - { - Messages.sendMessage(takeFrom.getPlayer(), RealEstate.instance.messages.msgErrorNoDepositSelf, takeFrom.getName()); - } - // refund - RealEstate.econ.depositPlayer(takeFrom, amount); - return false; - } - } - - return true; + // seller might be null if it is the server + OfflinePlayer giveTo = receiver != null ? Bukkit.getOfflinePlayer(receiver) : null; + OfflinePlayer takeFrom = giver != null ? Bukkit.getOfflinePlayer(giver) : null; + if(takeFrom != null && !RealEstate.econ.has(takeFrom, amount)) + { + if(takeFrom.isOnline() && msgGiver) + { + Messages.sendMessage(takeFrom.getPlayer(), RealEstate.instance.messages.msgErrorNoMoneySelf); + } + if(giveTo != null && giveTo.isOnline() && msgReceiver) + { + Messages.sendMessage(giveTo.getPlayer(), RealEstate.instance.messages.msgErrorNoMoneyOther, takeFrom.getName()); + } + return false; + } + if(takeFrom != null) + { + EconomyResponse resp = RealEstate.econ.withdrawPlayer(takeFrom, amount); + if(!resp.transactionSuccess()) + { + if(takeFrom.isOnline() && msgGiver) + { + Messages.sendMessage(takeFrom.getPlayer(), RealEstate.instance.messages.msgErrorNoWithdrawSelf); + } + if(giveTo != null && giveTo.isOnline() && msgReceiver) + { + Messages.sendMessage(giveTo.getPlayer(), RealEstate.instance.messages.msgErrorNoWithdrawOther); + } + return false; + } + } + if(giveTo != null) + { + EconomyResponse resp = RealEstate.econ.depositPlayer(giveTo, amount); + if(!resp.transactionSuccess()) + { + if(takeFrom != null && takeFrom.isOnline() && msgGiver) + { + Messages.sendMessage(giveTo.getPlayer(), RealEstate.instance.messages.msgErrorNoDepositOther, giveTo.getName()); + } + if(takeFrom != null && giveTo != null && giveTo.isOnline() && msgReceiver) + { + Messages.sendMessage(takeFrom.getPlayer(), RealEstate.instance.messages.msgErrorNoDepositSelf, takeFrom.getName()); + } + // refund + RealEstate.econ.depositPlayer(takeFrom, amount); + return false; + } + } + + return true; + } + + /** + * Formats a time duration as a human-readable string. + *

+ * The method converts the given number of days and a Duration representing hours/minutes + * into a string such as "1 week 3 days 5 hours 30 mins". + *

+ * + * @param days the number of days + * @param hours a Duration representing additional hours and minutes; may be {@code null} + * @param details if {@code true}, include hours and minutes even when days are present + * @return a formatted string representing the total time. + */ + public static String getTime(int days, Duration hours, boolean details) + { + String time = ""; + if(days >= 7) + { + time += (days / 7) + " week" + (days >= 14 ? "s" : ""); + } + if(days % 7 > 0) + { + time += (time.isEmpty() ? "" : " ") + (days % 7) + " day" + (days % 7 > 1 ? "s" : ""); + } + if((details || days < 7) && hours != null && hours.toHours() > 0) + { + time += (time.isEmpty() ? "" : " ") + hours.toHours() + " hour" + (hours.toHours() > 1 ? "s" : ""); + } + if((details || days == 0) && hours != null && (time.isEmpty() || hours.toMinutes() % 60 > 0)) + { + time += (time.isEmpty() ? "" : " ") + (hours.toMinutes() % 60) + " min" + (hours.toMinutes() % 60 > 1 ? "s" : ""); + } + + return time; + } + + /** + * Transfers a claim from one owner to another. + *

+ * This method handles the transfer of claim blocks between the seller and buyer if claim blocks are + * being transferred, and then updates the claim's ownership using the claim API. + *

+ * + * @param claim the claim to be transferred + * @param buyer the UUID of the new owner + * @param seller the UUID of the current owner (may be {@code null} for admin claims) + */ + public static void transferClaim(IClaim claim, UUID buyer, UUID seller) + { + // blocks transfer : + // if transfert is true, the seller will lose the blocks he had + // and the buyer will get them + // (that means the buyer will keep the same amount of remaining blocks after the transaction) + if(claim.isParentClaim() && RealEstate.instance.config.cfgTransferClaimBlocks) + { + IPlayerData buyerData = RealEstate.claimAPI.getPlayerData(buyer); + if(seller != null) + { + IPlayerData sellerData = RealEstate.claimAPI.getPlayerData(seller); + + // the seller has to provide the blocks + sellerData.setBonusClaimBlocks(sellerData.getBonusClaimBlocks() - claim.getArea()); + if (sellerData.getBonusClaimBlocks() < 0)// can't have negative bonus claim blocks, so if need be, we take into the accrued + { + sellerData.setAccruedClaimBlocks(sellerData.getAccruedClaimBlocks() + sellerData.getBonusClaimBlocks()); + sellerData.setBonusClaimBlocks(0); + } + } + + // the buyer receives them + buyerData.setBonusClaimBlocks(buyerData.getBonusClaimBlocks() + claim.getArea()); + } + + // start to change owner + if(claim.isParentClaim()) + { + for(IClaim child : claim.getChildren()) + { + child.clearPlayerPermissions(); + child.clearManagers(); + } + } + claim.clearPlayerPermissions(); + + try + { + if(claim.isParentClaim()) + RealEstate.claimAPI.changeClaimOwner(claim, buyer); + else + { + claim.addPlayerPermissions(buyer, ClaimPermission.BUILD); + } + } + catch (Exception e)// error occurs when trying to change subclaim owner + { + e.printStackTrace(); + return; + } + RealEstate.claimAPI.saveClaim(claim); + } + + /** + * Truncates a given string to a maximum length of 16 characters. + *

+ * This is used for ensuring that sign text does not exceed Minecraft's character limit. + *

+ * + * @param str the input string + * @return the truncated string if its length exceeds 16 characters; otherwise, the original string. + */ + public static String getSignString(String str) + { + if(str.length() > 16) + str = str.substring(0, 16); + return str; } - - public static String getTime(int days, Duration hours, boolean details) - { - String time = ""; - if(days >= 7) - { - time += (days / 7) + " week" + (days >= 14 ? "s" : ""); - } - if(days % 7 > 0) - { - time += (time.isEmpty() ? "" : " ") + (days % 7) + " day" + (days % 7 > 1 ? "s" : ""); - } - if((details || days < 7) && hours != null && hours.toHours() > 0) - { - time += (time.isEmpty() ? "" : " ") + hours.toHours() + " hour" + (hours.toHours() > 1 ? "s" : ""); - } - if((details || days == 0) && hours != null && (time.isEmpty() || hours.toMinutes() % 60 > 0)) - { - time += (time.isEmpty() ? "" : " ") + (hours.toMinutes() % 60) + " min" + (hours.toMinutes() % 60 > 1 ? "s" : ""); - } - - return time; - } - - public static void transferClaim(IClaim claim, UUID buyer, UUID seller) - { - // blocks transfer : - // if transfert is true, the seller will lose the blocks he had - // and the buyer will get them - // (that means the buyer will keep the same amount of remaining blocks after the transaction) - if(claim.isParentClaim() && RealEstate.instance.config.cfgTransferClaimBlocks) - { - IPlayerData buyerData = RealEstate.claimAPI.getPlayerData(buyer); - if(seller != null) - { - IPlayerData sellerData = RealEstate.claimAPI.getPlayerData(seller); - - // the seller has to provide the blocks - sellerData.setBonusClaimBlocks(sellerData.getBonusClaimBlocks() - claim.getArea()); - if (sellerData.getBonusClaimBlocks() < 0)// can't have negative bonus claim blocks, so if need be, we take into the accrued - { - sellerData.setAccruedClaimBlocks(sellerData.getAccruedClaimBlocks() + sellerData.getBonusClaimBlocks()); - sellerData.setBonusClaimBlocks(0); - } - } - - // the buyer receive them - buyerData.setBonusClaimBlocks(buyerData.getBonusClaimBlocks() + claim.getArea()); - } - - // start to change owner - if(claim.isParentClaim()) - { - for(IClaim child : claim.getChildren()) - { - child.clearPlayerPermissions(); - child.clearManagers(); - } - } - claim.clearPlayerPermissions(); - - try - { - if(claim.isParentClaim()) - RealEstate.claimAPI.changeClaimOwner(claim, buyer); - else - { - claim.addPlayerPermissions(buyer, ClaimPermission.BUILD); - } - } - catch (Exception e)// error occurs when trying to change subclaim owner - { - e.printStackTrace(); - return; - } - RealEstate.claimAPI.saveClaim(claim); - - } - - public static String getSignString(String str) - { - if(str.length() > 16) - str = str.substring(0, 16); - return str; - } } From 8da038857b8bcb7eaaa9270aa59830e7c28c182f Mon Sep 17 00:00:00 2001 From: Michael Burgess Date: Tue, 18 Feb 2025 20:29:55 -0500 Subject: [PATCH 14/14] Update 1.4.4 Included bStats Updated language files --- pom.xml | 8 +- resources/languages/es.yml | 54 ++ resources/languages/pt-br.yml | 347 ++++++++ resources/languages/ru.yml | 54 ++ src/me/EtienneDx/RealEstate/Metrics.java | 906 ++++++++++++++++++++ src/me/EtienneDx/RealEstate/RealEstate.java | 14 +- 6 files changed, 1378 insertions(+), 5 deletions(-) create mode 100644 resources/languages/es.yml create mode 100644 resources/languages/pt-br.yml create mode 100644 resources/languages/ru.yml create mode 100644 src/me/EtienneDx/RealEstate/Metrics.java diff --git a/pom.xml b/pom.xml index f14ed2c..59f4d9b 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 Me.EtienneDx real-estate - 1.4.3 + 1.4.4 RealEstate A spigot plugin for selling, renting, leasing and auctioning GriefPrevention and GriefDefender claims @@ -206,5 +206,11 @@ 0.101.1.0 provided
+ + org.bstats + bstats-bukkit + 3.0.2 + compile + diff --git a/resources/languages/es.yml b/resources/languages/es.yml new file mode 100644 index 0000000..2202ed1 --- /dev/null +++ b/resources/languages/es.yml @@ -0,0 +1,54 @@ +# Usa un editor YAML como NotepadPlusPlus para editar este archivo. +# Después de editar, haz una copia de seguridad de tus cambios antes de recargar el servidor en caso de que hayas cometido un error de sintaxis. +# Usa signos de dólar ($) para los códigos de formato, que están documentados aquí: http://minecraft.gamepedia.com/Formatting_codes. +# Puedes usar {0}, {1} para incluir los diferentes valores indicados en los comentarios. +RealEstate: + Keywords: + # Palabras clave utilizadas dentro de otros mensajes pero con un texto más largo al + # final solo porque necesito probar algunas cosas. + Enabled: habilitado + Disabled: deshabilitado + Claim: reclamación + Subclaim: subreclamación + AdminClaimPrefix: un administrador + ClaimPrefix: un + TheServer: El servidor + NoTransactionFound: $c¡No se encontró ninguna transacción! + NoTransactionFoundHere: $c¡No se encontró ninguna transacción en tu ubicación! + PageMustBePositive: $cLa página debe ser una opción positiva. + PageNotExists: $c¡Esta página no existe! + # 0: habilitado/deshabilitado; 1: + # tipo de reclamación + RenewRentNow: $bLa renovación automática ahora está $a{0} $bpara esta {1} + # 0: habilitado/deshabilitado; + # 1: tipo de reclamación + RenewRentCurrently: $bLa renovación automática actualmente está $a{0} $bpara esta {1} + Errors: + OutOfClaim: $c¡Debes estar dentro de una reclamación para usar este comando! + PlayerOnlyCmd: $c¡Solo los jugadores pueden ejecutar este comando! + NoOngoingTransaction: $c¡Esta reclamación no tiene transacciones en curso! + NotRentNorLease: $c¡Esta reclamación no está en alquiler ni arrendamiento! + AlreadyBought: $c¡Esta reclamación ya tiene un comprador! + NotPartOfTransaction: $c¡No eres parte de esta transacción! + RentOnly: $c¡Este comando solo se aplica a reclamaciones alquiladas! + AuctionOnly: $c¡Este comando solo se aplica a reclamaciones subastadas! + ValueGreaterThanZero: $c¡El valor debe ser mayor que cero! + InvalidOption: $c¡Opción inválida proporcionada! + List: + # 0: Ofertas de RE|Ofertas de Venta|Ofertas de Alquiler|Ofertas de Arrendamiento; 1: Número de página; 2: + # Número total de páginas + Header: $1----= $f[ $6{0} página $2 {1} $6/ $2{2} $f] $1=---- + # 0: todo|venta|alquiler|arrendamiento; 1: + # número de la próxima página + NextPage: $6Para ver la próxima página, escribe $a/re list {0} {1} + Sign: + Auction: + # 0: nombre del jugador, 1: + # precio formateado + HighestBidder: '$b{0}: $a{1}' + NoBider: $bSin postor + # 0: tiempo restante formateado + RemainingTime: $b$a{0} + Ended: $bSubasta finalizada + # siguiente línea: ganador + Won: $bSubasta ganada por diff --git a/resources/languages/pt-br.yml b/resources/languages/pt-br.yml new file mode 100644 index 0000000..801417b --- /dev/null +++ b/resources/languages/pt-br.yml @@ -0,0 +1,347 @@ +# Use a YAML editor like NotepadPlusPlus to edit this file. +# After editing, back up your changes before reloading the server in case you made a syntax error. +# Use dollar signs ($) for formatting codes, which are documented here: http://minecraft.gamepedia.com/Formatting_codes. +# You can use {0}, {1} to include the different values indicated in the comments +RealEstate: + Keywords: + # Keywords used within other messages but with a longer text at + # the end just because i need to test some stuff + Enabled: ativado + Disabled: desativado + Claim: reivindicação + Subclaim: subreivindicação + AdminClaimPrefix: um administrador + ClaimPrefix: um + TheServer: Servidor + NoTransactionFound: $cNenhuma transação encontrada! + NoTransactionFoundHere: $cNenhuma transação encontrada em sua localização! + PageMustBePositive: $cA página deve ser uma opção positiva + PageNotExists: $cEsta página não existe! + # 0: enabled/disabled; 1: + # type of claim + RenewRentNow: $bA renovação automática é agora $a{0} $bpor esta {1} + # 0: enabled/disabled; + # 1: type of claim + RenewRentCurrently: $bA renovação automática está atualmente $a{0} $bpor esta {1} + Errors: + OutOfClaim: $cVocê deve estar dentro de uma reivindicação para usar este comando! + PlayerOnlyCmd: $cSomente jogadores podem executar este comando! + NoOngoingTransaction: $cEsta reivindicação não tem transações em andamento! + NotRentNorLease: $cEsta reivindicação não é nem para alugar nem para arrendar! + AlreadyBought: $cEsta reivindicação já tem um comprador! + NotPartOfTransaction: $cVocê não faz parte desta transação! + RentOnly: $cEste comando aplica-se apenas a sinistros alugados! + ValueGreaterThanZero: $cO valor deve ser maior que zero! + InvalidOption: $cOpção inválida fornecida! + ClaimInTransaction: + CantOwner: $cEsta reivindicação está atualmente envolvida em uma transação, você não pode modificar + isto! + CantEdit: $cEsta reivindicação está atualmente envolvida em uma transação, você não pode editar + isto! + CantAccess: $cEsta reivindicação está atualmente envolvida em uma transação, você não pode acessar + isto! + CantBuild: $cEsta reivindicação está atualmente envolvida em uma transação, você não pode criar + nele! + CantInventory: $cEsta reivindicação está atualmente envolvida em uma transação, você não pode + acesse seus contêineres! + CantManage: $cEsta reivindicação está atualmente envolvida em uma transação, você não pode gerenciar + isto! + Subclaim: $cUma sub-reivindicação está atualmente envolvida em uma transação, você não pode editar + ou gerencie a reivindicação dos pais! + Command: + # 0: command usage + Usage: '$cUso: {0}' + BuyerOnly: $cSomente o comprador pode realizar este comando! + Unexpected: $cOcorreu um erro inesperado! + # 0: number + InvalidNumber: $c{0} não é um número válido! + # 0: number + NegativeNumber: $c{0} é um número negativo! + # 0: price + NegativePrice: $cO preço deve ser maior que zero! + # 0: price + NonIntegerPrice: $cThe price must be an integer! + # 0: duration, 1: + # example of duration format, 2: example, 3: + # example + InvalidDuration: $c{0} não é uma duração válida! As durações devem estar no formato + $a{1}$c ou $a{2}$c ou $a{3}$c! + NoMoneySelf: $cVocê não tem dinheiro suficiente para fazer esta transação! + # 0: Other player + NoMoneyOther: $c{0} não tem dinheiro suficiente para fazer esta transação! + NoWithdrawSelf: $cNão foi possível retirar o dinheiro! + # 0: Other player + NoWithdrawOther: $cNão foi possível retirar o dinheiro de {0}! + # 0: Other player + NoDepositSelf: $cNão foi possível depositar o dinheiro para você, reembolsando {0}! + # 0: Other player + NoDepositOther: $cNão foi possível depositar o dinheiro para {0}, reembolsando você! + # 0: claim type + CantCancelAlreadyLeased: $cEste {0} está sendo alugado no momento, você não pode cancelar + a transação! + # 0: claim type + CantCancelAlreadyRented: $cEste {0} está sendo alugado no momento, você não pode cancelar + a transação! + AutoRenew: + Disabled: $cA renovação automática está desativada! + ExitOffer: + AlreadyExists: $cJá existe uma proposta de saída para esta transação! + NoBuyer: $cNinguém está envolvido nesta transação ainda! + None: $cNo momento, não há oferta de saída para esta reivindicação! + CantAcceptSelf: $cVocê não pode aceitar sua própria oferta de saída! + CantRefuseSelf: $cVocê não pode recusar sua própria oferta de saída! + CantCancelOther: $cApenas o jogador que criou esta proposta de saída pode cancelar + isto! + Sign: + NotInClaim: $cA placa que você colocou não está dentro de uma reivindicação! + OngoingTransaction: $cEsta reivindicação já tem uma transação em andamento! + ParentOngoingTransaction: $cO pai desta reivindicação já tem uma transação em andamento! + SubclaimOngoingTransaction: $cEsta reivindicação tem sub-reivindicações com transações em andamento! + SellingDisabled: $cA venda está desativada! + LeasingDisabled: $cA locação está desabilitada! + RentingDisabled: $cO aluguel está desativado! + # 0: claim type + NoSellPermission: $cVocê não tem permissão para vender isso {0}! + # 0: claim type + NoLeasePermission: $cVocê não tem permissão para alugar isso {0}! + # 0: claim type + NoRentPermission: $cVocê não tem permissão para alugar isso {0}! + # 0: claim type + NoAdminSellPermission: $cVocê não tem permissão para vender este administrador {0}! + # 0: claim type + NoAdminLeasePermission: $cVocê não tem permissão para alugar este administrador {0}! + # 0: claim type + NoAdminRentPermission: $cVocê não tem permissão para alugar este administrador {0}! + # 0: claim type + NotOwner: $cVocê só pode vender/alugar/arrendar {0} que possui! + NotAuthor: $cApenas o autor do sinal de venda/aluguel/arrendamento está autorizado a destruir + isto! + NotAdmin: $cApenas um administrador tem permissão para destruir este sinal! + NoTransaction: $cEsta reivindicação não é mais para alugar, vender ou arrendar, desculpe... + Claim: + DoesNotExist: $cEsta afirmação não existe! + # 0: claim type + AlreadyOwner: $cVocê já é o proprietário deste {0}! + # 0: claim type + NotSoldByOwner: $cEste {0} não é vendido pelo proprietário! + # 0: claim type + NotLeasedByOwner: $cEste {0} não é alugado por seu proprietário! + # 0: claim type + NotRentedByOwner: $cEste {0} não é alugado pelo proprietário! + # 0: claim type + NoBuyPermission: $cVocê não tem permissão para comprar este {0}! + # 0: claim type + NoLeasePermission: $cVocê não tem permissão para alugar este {0}! + # 0: claim type + NoRentPermission: $cVocê não tem permissão para alugar este {0}! + # 0: claim type + AlreadyLeased: $cEste {0} já está alugado! + # 0: claim type + AlreadyRented: $cEste {0} já está alugado! + NoInfoPermission: $cVocê não tem permissão para visualizar as informações deste imóvel! + # 0: area; 1: + # claim blocks remaining; 2: missing claim blocks + NoClaimBlocks: $cVocê não tem blocos de reivindicação suficientes! Você precisa $a{2}$c mais reivindicação + blocos para reivindicar esta área. A reivindicação exige $a{0}$c blocos de reivindicação, você só + tem $a{1}$c blocos de reivindicação restantes. + Info: + ExitOffer: + None: $bNo momento, não há oferta de saída para esta reivindicação! + # 0: formatted price + MadeByStatus: $bVocê se ofereceu para rescindir o contrato por $a{0}$b, mas sua oferta + ainda não foi aceito ou negado... + # 0: player who made the + # offer; 1: formatted price + MadeToStatus: $a{0} $bofereceu a rescisão do contrato de $a{1} + # 0: cancel command + Cancel: $bPara cancelar sua oferta, use $d{0} + # 0: accept command + Accept: $bPara aceitar esta oferta, use $d{0} + # 0: reject command + Reject: $bPara rejeitar esta oferta, use $d{0} + # 0: formatted price + CreatedBySelf: $bA oferta foi criada com sucesso para $a{0} + # 0: player name, 1: + # claim type, 2: formatted price, 3: + # claim location + CreatedByOther: $a{0} $bcriou uma oferta para sair da transação para o + {1} no $a{3} $bpara $a{2} + # 0: claim type, 1:formatted + # price + AcceptedBySelf: $bO {0} não é mais alugado ou arrendado, você foi cobrado + $a{1} + # 0: player name, 1: + # claim type, 2: formatted price, 3: + # claim location + AcceptedByOther: $a{0} $baceitou a oferta para sair da transação para + a {1} no $a{3} $bpara $a{2}. Não é mais alugado ou alugado. + RejectedBySelf: $bA oferta de saída foi recusada. + # 0: player name, 1: + # claim type, 2: claim location + RejectedByOther: $a{0} $brecusou a oferta de sair da transação para o + {1} no $a{2} + CancelledBySelf: $bA oferta de saída foi cancelada. + # 0: player name, 1: + # claim type, 2: claim location + CancelledByOther: $a{0} $bcancelou a oferta para sair da transação para + o {1} no $a{2} + Claim: + # 0: buyer name, 1: + # claim type, 2: formatted price, 3: + # claim location + OwnerSold: $a{0} $bcomprou o {1} no $a{3} $bpara $a{2} + # 0: buyer name, 1: + # claim type, 2: formatted price, 3: + # claim location, 4: payments left + OwnerLeaseStarted: $a{0} $balugou o {1} no $a{3} $bpara $a{2} com $a{4} + $bpayments left + # 0: buyer name, 1: + # claim type, 2: formatted price, 3: + # claim location + OwnerRented: $a{0} $balugou o {1} no $a{3} $bpara $a{2} + # 0: claim type, 1: + # formatted price + BuyerBought: $bVocê comprou o {0} para $a{1} + # 0: claim type, 1: + # formatted price, 2: payments left + BuyerLeaseStarted: $bVocê alugou o {0} para $a{1} com $a{2} $bpagamentos + restantes + # 0: claim type, 1: + # formatted price + BuyerRented: $bVocê alugou o {0} para $a{1} + Info: + Lease: + Header: $9-----= $f[$6RealEstate Arrendamento$f]$9 =----- + # 0: claim type, 1: + # payments left, 2: formatted price, 3: + # frequency + GeneralNoBuyer: $bEste {0} é para arrendado para $a{1} $bpagamentos de $a{2} cada. + Os pagamentos são devidos a cada $a{3} + # 0: claim type, 1: + # buyer name, 2: formatted price, 3: + # payments left, 4: next payment due, 5: + # frequency + GeneralBuyer: $bEste {0} está atualmente arrendado por $a{1}$b para $a{2}$b. + Há $a{3} $bpagamentos restantes. O próximo pagamento é em $a{4}$b. Os pagamentos são devidos + cada $a{5} + # 0: claim area, 1: + # location, 2: payments left, 3: + # period, 4: formatted price + Oneline: $2{0} $bblocos para $2arrendado $bat $2{1} $bpara $a{2} períodos de $a{3}$b, + custo de cada período $a{4} + # 0: claim type, 1: + # location, 2: formatted price, 3: + # payments left + PaymentBuyer: $bArrendamento pago para {0} no $a{1} $bpara $a{2}$b. Tem + $a{3} $bpagamentos restantes. + # 0: player name, 1: + # claim type, 2: location, 3: + # formatted price, 4: payments left + PaymentOwner: $a{0} $bArrendamento pago para {1} no $a{2} $bpara $a{3}$b. Tem + $a{4} $bpagamentos restantes. + # 0: claim type, + # 1: location, 2: + # formatted price + PaymentBuyerFinal: $bArrendamento final pago para o {0} no $a{1} $bpara $a{2}$b. + O {0} agora é sua propriedade. + # 0: player name, + # 1: claim type, 2: + # location, 3: formatted price + PaymentOwnerFinal: $a{0} $bArrendamento final pago para o {1} no $a{2} $bpara $a{3}$b. + O {1} é agora propriedade de $a{0}$b. + # 0: claim + # type, 1: location, 2: + # formatted price + PaymentBuyerCancelled: $bNão foi possível pagar o arrendamento do {0} no $a{1} $bpara + $a{2}$b. O arrendamento foi cancelada. + # 0: player + # name, 1: claim type, 2: + # location, 3: formatted price + PaymentOwnerCancelled: $a{0} $bnão conseguiu pagar o arrendamento do {1} no $a{2} + $bfor $a{3}$b. O arrendamento foi cancelada. + Rent: + Header: $9-----= $f[$6RealEstate Inf. aluguel$f]$9 =----- + # 0: claim type, 1: + # formatted price, 2: duration + GeneralNoBuyer: $bEste {0} é para alugar $a{1}$b por $a{2}$b. + # 0: claim type, 1: + # buyer name, 2: formatted price, 3: + # time left in current period, 4: duration + # of a period + GeneralBuyer: $bEste {0} atualmente é alugado por $a{1}$b para $a{2}$b. O + {0} é alugado para outro $a{3}$b. O período de aluguel é $a{4} + # 0: enabled / disabled + AutoRenew: $bA renovação automática está atualmente $a{0}$b. + # 0: claim area, 1: + # location, 2: formatted price, 3: + # duration + Oneline: $2{0} $bblocos para $2aluguel $bno $2{1} $bpara $a{2}$b por $a{3} + # 0: claim type, 1: + # location, 2: formatted price + PaymentBuyer: $bAluguel pago para {0} no $a{1} $bpara $a{2}$b. + # 0: player name, 1: + # claim type, 2: location, 3: + # formatted price + PaymentOwner: $a{0} $baluguel pago para {1} no $a{2} $bpara $a{3}$b. + # 0: claim + # type, 1: location, 2: + # formatted price + PaymentBuyerCancelled: $bNão foi possível pagar o aluguel do {0} no $a{1} $bpara + $a{2}$b. O aluguel foi cancelado. + # 0: player + # name, 1: claim type, 2: + # location, 3: formatted price + PaymentOwnerCancelled: $a{0} $bnão podia pagar o aluguel do {1} no $a{2} + $bpara $a{3}$b. O aluguel foi cancelado. + # 0: claim type, 1: + # location + RentCancelled: $bO aluguel para {0} at $a{1} $bacabou, sua permissão + foi revogado. + Sell: + Header: $9-----= $f[$6RealEstate inf. venda$f]$9 =----- + # 0: claim type, 1: + # formatted price + General: $bEste {0} está à venda para $a{1} + # 0: claim area, 1: + # location, 2: formatted price + Oneline: $2{0} $bblocos para $2Vender $bat $2{1} $bpara $a{2} + # 0: owner name + Owner: $bO atual proprietário é $a{0} + # 0: owner name + MainOwner: $bO proprietário da reivindicação principal é $a{0} + Note: '$dObs: você só terá acesso a esta sub-reivindicação' + Created: + # 0: claim prefix, 1: + # claim type, 2: formatted price + Sell: $bVocê criou com sucesso {0} {1} venda para $a{2} + # 0: claim prefix, 1: + # claim type, 2: formatted price, 3: + # payments count, 4: frequency + Lease: $bVocê criou com sucesso {0} {1} arrendamento para $a{3}$b pagamentos + do $a{2}$b cada. Os pagamentos são devidos a cada $a{4} + # 0: claim prefix, 1: + # claim type, 2: formatted price, 3: + # duration + Rent: $bVocê criou com sucesso {0} {1} aluguel para $a{2}$b por $a{3} + # 0: player name, 1: + # claim prefix, 2: claim type, 3: + # formatted price + SellBroadcast: $a{0} $bcriou {1} {2} venda em $a{3} + # 0: player name, 1: + # claim prefix, 2: claim type, 3: + # formatted price, 4: payments count, 5: + # frequency + LeaseBroadcast: $a{0} $bcriou {1} {2} arrendamento para $a{4}$b pagamentos de + $a{3}$b cada. Os pagamentos são debitado a cada $a{5} + # 0: player name, 1: + # claim prefix, 2: claim type, 3: + # formatted price, 4: duration + RentBroadcast: $a{0} $bcriou {1} {2} aluguel em $a{3}$b por $a{4} + List: + # 0: RE Offers|Sell Offers|Rent + # Offers|Lease Offers; 1: Page number; 2: + # Page count + Header: $1----= $f[ $6{0} página $2 {1} $6/ $2{2} $f] $1=---- + # 0: all|sell|rent|lease; 1: + # next page number + NextPage: $6Para ver a próxima página, digite $a/re list {0} {1} diff --git a/resources/languages/ru.yml b/resources/languages/ru.yml new file mode 100644 index 0000000..b0251c7 --- /dev/null +++ b/resources/languages/ru.yml @@ -0,0 +1,54 @@ +# Используйте редактор YAML, например NotepadPlusPlus, для редактирования этого файла. +# После редактирования сделайте резервную копию изменений перед перезапуском сервера на случай ошибки синтаксиса. +# Используйте знаки доллара ($) для кодов форматирования, которые задокументированы здесь: http://minecraft.gamepedia.com/Formatting_codes. +# Вы можете использовать {0}, {1}, чтобы включить различные значения, указанные в комментариях. +RealEstate: + Keywords: + # Ключевые слова, используемые в других сообщениях, но с более длинным текстом в конце, + # просто потому что мне нужно протестировать некоторые вещи. + Enabled: включено + Disabled: отключено + Claim: владение + Subclaim: субвладение + AdminClaimPrefix: администраторское + ClaimPrefix: обычное + TheServer: Сервер + NoTransactionFound: $cНе найдено ни одной транзакции! + NoTransactionFoundHere: $cНа вашем местоположении не найдено ни одной транзакции! + PageMustBePositive: $cСтраница должна быть положительным числом. + PageNotExists: $cТакой страницы не существует! + # 0: включено/отключено; 1: + # тип владения + RenewRentNow: $bАвтоматическое обновление теперь $a{0} $bдля этого {1} + # 0: включено/отключено; + # 1: тип владения + RenewRentCurrently: $bАвтоматическое обновление в настоящее время $a{0} $bдля этого {1} + Errors: + OutOfClaim: $cВы должны находиться внутри владения, чтобы использовать эту команду! + PlayerOnlyCmd: $cЭта команда доступна только для игроков! + NoOngoingTransaction: $cЭто владение не участвует ни в одной транзакции! + NotRentNorLease: $cЭто владение не сдается в аренду и не передается в лизинг! + AlreadyBought: $cЭто владение уже куплено! + NotPartOfTransaction: $cВы не участвуете в этой транзакции! + RentOnly: $cЭта команда применяется только к арендуемым владениям! + AuctionOnly: $cЭта команда применяется только к аукционным владениям! + ValueGreaterThanZero: $cЗначение должно быть больше нуля! + InvalidOption: $cУказан неверный параметр! + List: + # 0: Оферты RE|Продажа|Аренда|Лизинг; 1: Номер страницы; 2: + # Общее количество страниц + Header: $1----= $f[ $6{0} страница $2 {1} $6/ $2{2} $f] $1=---- + # 0: всё|продажа|аренда|лизинг; 1: + # номер следующей страницы + NextPage: $6Чтобы увидеть следующую страницу, введите $a/re list {0} {1} + Sign: + Auction: + # 0: имя игрока, 1: + # форматированная цена + HighestBidder: '$b{0}: $a{1}' + NoBider: $bНет ставок + # 0: оставшееся время в формате + RemainingTime: $b$a{0} + Ended: $bАукцион завершен + # следующая строка: победитель + Won: $bАукцион выиграл diff --git a/src/me/EtienneDx/RealEstate/Metrics.java b/src/me/EtienneDx/RealEstate/Metrics.java new file mode 100644 index 0000000..af6bfe4 --- /dev/null +++ b/src/me/EtienneDx/RealEstate/Metrics.java @@ -0,0 +1,906 @@ +/* + * This Metrics class was auto-generated and can be copied into your project if you are + * not using a build tool like Gradle or Maven for dependency management. + * + * IMPORTANT: You are not allowed to modify this class, except changing the package. + * + * Disallowed modifications include but are not limited to: + * - Remove the option for users to opt-out + * - Change the frequency for data submission + * - Obfuscate the code (every obfuscator should allow you to make an exception for specific files) + * - Reformat the code (if you use a linter, add an exception) + * + * Violations will result in a ban of your plugin and account from bStats. + */ +package me.EtienneDx.RealEstate; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.Method; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.stream.Collectors; +import java.util.zip.GZIPOutputStream; +import javax.net.ssl.HttpsURLConnection; +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +public class Metrics { + + private final Plugin plugin; + + private final MetricsBase metricsBase; + + /** + * Creates a new Metrics instance. + * + * @param plugin Your plugin instance. + * @param serviceId The id of the service. It can be found at What is my plugin id? + */ + @SuppressWarnings("deprecation") +public Metrics(Plugin plugin, int serviceId) { + this.plugin = plugin; + // Get the config file + File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); + File configFile = new File(bStatsFolder, "config.yml"); + YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); + if (!config.isSet("serverUuid")) { + config.addDefault("enabled", true); + config.addDefault("serverUuid", UUID.randomUUID().toString()); + config.addDefault("logFailedRequests", false); + config.addDefault("logSentData", false); + config.addDefault("logResponseStatusText", false); + // Inform the server owners about bStats + config + .options() + .header( + "bStats (https://bStats.org) collects some basic information for plugin authors, like how\n" + + "many people use their plugin and their total player count. It's recommended to keep bStats\n" + + "enabled, but if you're not comfortable with this, you can turn this setting off. There is no\n" + + "performance penalty associated with having metrics enabled, and data sent to bStats is fully\n" + + "anonymous.") + .copyDefaults(true); + try { + config.save(configFile); + } catch (IOException ignored) { + } + } + // Load the data + boolean enabled = config.getBoolean("enabled", true); + String serverUUID = config.getString("serverUuid"); + boolean logErrors = config.getBoolean("logFailedRequests", false); + boolean logSentData = config.getBoolean("logSentData", false); + boolean logResponseStatusText = config.getBoolean("logResponseStatusText", false); + boolean isFolia = false; + try { + isFolia = Class.forName("io.papermc.paper.threadedregions.RegionizedServer") != null; + } catch (Exception e) { + } + metricsBase = + new // See https://github.com/Bastian/bstats-metrics/pull/126 + // See https://github.com/Bastian/bstats-metrics/pull/126 + // See https://github.com/Bastian/bstats-metrics/pull/126 + // See https://github.com/Bastian/bstats-metrics/pull/126 + // See https://github.com/Bastian/bstats-metrics/pull/126 + // See https://github.com/Bastian/bstats-metrics/pull/126 + // See https://github.com/Bastian/bstats-metrics/pull/126 + MetricsBase( + "bukkit", + serverUUID, + serviceId, + enabled, + this::appendPlatformData, + this::appendServiceData, + isFolia + ? null + : submitDataTask -> Bukkit.getScheduler().runTask(plugin, submitDataTask), + plugin::isEnabled, + (message, error) -> this.plugin.getLogger().log(Level.WARNING, message, error), + (message) -> this.plugin.getLogger().log(Level.INFO, message), + logErrors, + logSentData, + logResponseStatusText, + false); + } + + /** Shuts down the underlying scheduler service. */ + public void shutdown() { + metricsBase.shutdown(); + } + + /** + * Adds a custom chart. + * + * @param chart The chart to add. + */ + public void addCustomChart(CustomChart chart) { + metricsBase.addCustomChart(chart); + } + + private void appendPlatformData(JsonObjectBuilder builder) { + builder.appendField("playerAmount", getPlayerAmount()); + builder.appendField("onlineMode", Bukkit.getOnlineMode() ? 1 : 0); + builder.appendField("bukkitVersion", Bukkit.getVersion()); + builder.appendField("bukkitName", Bukkit.getName()); + builder.appendField("javaVersion", System.getProperty("java.version")); + builder.appendField("osName", System.getProperty("os.name")); + builder.appendField("osArch", System.getProperty("os.arch")); + builder.appendField("osVersion", System.getProperty("os.version")); + builder.appendField("coreCount", Runtime.getRuntime().availableProcessors()); + } + + private void appendServiceData(JsonObjectBuilder builder) { + builder.appendField("pluginVersion", plugin.getDescription().getVersion()); + } + + private int getPlayerAmount() { + try { + // Around MC 1.8 the return type was changed from an array to a collection, + // This fixes java.lang.NoSuchMethodError: + // org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; + Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); + return onlinePlayersMethod.getReturnType().equals(Collection.class) + ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer())).size() + : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; + } catch (Exception e) { + // Just use the new method if the reflection failed + return Bukkit.getOnlinePlayers().size(); + } + } + + public static class MetricsBase { + + /** The version of the Metrics class. */ + public static final String METRICS_VERSION = "3.1.0"; + + private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s"; + + private final ScheduledExecutorService scheduler; + + private final String platform; + + private final String serverUuid; + + private final int serviceId; + + private final Consumer appendPlatformDataConsumer; + + private final Consumer appendServiceDataConsumer; + + private final Consumer submitTaskConsumer; + + private final Supplier checkServiceEnabledSupplier; + + private final BiConsumer errorLogger; + + private final Consumer infoLogger; + + private final boolean logErrors; + + private final boolean logSentData; + + private final boolean logResponseStatusText; + + private final Set customCharts = new HashSet<>(); + + private final boolean enabled; + + /** + * Creates a new MetricsBase class instance. + * + * @param platform The platform of the service. + * @param serviceId The id of the service. + * @param serverUuid The server uuid. + * @param enabled Whether or not data sending is enabled. + * @param appendPlatformDataConsumer A consumer that receives a {@code JsonObjectBuilder} and + * appends all platform-specific data. + * @param appendServiceDataConsumer A consumer that receives a {@code JsonObjectBuilder} and + * appends all service-specific data. + * @param submitTaskConsumer A consumer that takes a runnable with the submit task. This can be + * used to delegate the data collection to a another thread to prevent errors caused by + * concurrency. Can be {@code null}. + * @param checkServiceEnabledSupplier A supplier to check if the service is still enabled. + * @param errorLogger A consumer that accepts log message and an error. + * @param infoLogger A consumer that accepts info log messages. + * @param logErrors Whether or not errors should be logged. + * @param logSentData Whether or not the sent data should be logged. + * @param logResponseStatusText Whether or not the response status text should be logged. + * @param skipRelocateCheck Whether or not the relocate check should be skipped. + */ + public MetricsBase( + String platform, + String serverUuid, + int serviceId, + boolean enabled, + Consumer appendPlatformDataConsumer, + Consumer appendServiceDataConsumer, + Consumer submitTaskConsumer, + Supplier checkServiceEnabledSupplier, + BiConsumer errorLogger, + Consumer infoLogger, + boolean logErrors, + boolean logSentData, + boolean logResponseStatusText, + boolean skipRelocateCheck) { + ScheduledThreadPoolExecutor scheduler = + new ScheduledThreadPoolExecutor( + 1, + task -> { + Thread thread = new Thread(task, "bStats-Metrics"); + thread.setDaemon(true); + return thread; + }); + // We want delayed tasks (non-periodic) that will execute in the future to be + // cancelled when the scheduler is shutdown. + // Otherwise, we risk preventing the server from shutting down even when + // MetricsBase#shutdown() is called + scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); + this.scheduler = scheduler; + this.platform = platform; + this.serverUuid = serverUuid; + this.serviceId = serviceId; + this.enabled = enabled; + this.appendPlatformDataConsumer = appendPlatformDataConsumer; + this.appendServiceDataConsumer = appendServiceDataConsumer; + this.submitTaskConsumer = submitTaskConsumer; + this.checkServiceEnabledSupplier = checkServiceEnabledSupplier; + this.errorLogger = errorLogger; + this.infoLogger = infoLogger; + this.logErrors = logErrors; + this.logSentData = logSentData; + this.logResponseStatusText = logResponseStatusText; + if (!skipRelocateCheck) { + checkRelocation(); + } + if (enabled) { + // WARNING: Removing the option to opt-out will get your plugin banned from + // bStats + startSubmitting(); + } + } + + public void addCustomChart(CustomChart chart) { + this.customCharts.add(chart); + } + + public void shutdown() { + scheduler.shutdown(); + } + + private void startSubmitting() { + final Runnable submitTask = + () -> { + if (!enabled || !checkServiceEnabledSupplier.get()) { + // Submitting data or service is disabled + scheduler.shutdown(); + return; + } + if (submitTaskConsumer != null) { + submitTaskConsumer.accept(this::submitData); + } else { + this.submitData(); + } + }; + // Many servers tend to restart at a fixed time at xx:00 which causes an uneven + // distribution of requests on the + // bStats backend. To circumvent this problem, we introduce some randomness into + // the initial and second delay. + // WARNING: You must not modify and part of this Metrics class, including the + // submit delay or frequency! + // WARNING: Modifying this code will get your plugin banned on bStats. Just + // don't do it! + long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); + long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); + scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS); + scheduler.scheduleAtFixedRate( + submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); + } + + private void submitData() { + final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder(); + appendPlatformDataConsumer.accept(baseJsonBuilder); + final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder(); + appendServiceDataConsumer.accept(serviceJsonBuilder); + JsonObjectBuilder.JsonObject[] chartData = + customCharts.stream() + .map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors)) + .filter(Objects::nonNull) + .toArray(JsonObjectBuilder.JsonObject[]::new); + serviceJsonBuilder.appendField("id", serviceId); + serviceJsonBuilder.appendField("customCharts", chartData); + baseJsonBuilder.appendField("service", serviceJsonBuilder.build()); + baseJsonBuilder.appendField("serverUUID", serverUuid); + baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION); + JsonObjectBuilder.JsonObject data = baseJsonBuilder.build(); + scheduler.execute( + () -> { + try { + // Send the data + sendData(data); + } catch (Exception e) { + // Something went wrong! :( + if (logErrors) { + errorLogger.accept("Could not submit bStats metrics data", e); + } + } + }); + } + + private void sendData(JsonObjectBuilder.JsonObject data) throws Exception { + if (logSentData) { + infoLogger.accept("Sent bStats metrics data: " + data.toString()); + } + String url = String.format(REPORT_URL, platform); + HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); + // Compress the data to save bandwidth + byte[] compressedData = compress(data.toString()); + connection.setRequestMethod("POST"); + connection.addRequestProperty("Accept", "application/json"); + connection.addRequestProperty("Connection", "close"); + connection.addRequestProperty("Content-Encoding", "gzip"); + connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("User-Agent", "Metrics-Service/1"); + connection.setDoOutput(true); + try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { + outputStream.write(compressedData); + } + StringBuilder builder = new StringBuilder(); + try (BufferedReader bufferedReader = + new BufferedReader(new InputStreamReader(connection.getInputStream()))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + builder.append(line); + } + } + if (logResponseStatusText) { + infoLogger.accept("Sent data to bStats and received response: " + builder); + } + } + + /** Checks that the class was properly relocated. */ + private void checkRelocation() { + // You can use the property to disable the check in your test environment + if (System.getProperty("bstats.relocatecheck") == null + || !System.getProperty("bstats.relocatecheck").equals("false")) { + // Maven's Relocate is clever and changes strings, too. So we have to use this + // little "trick" ... :D + final String defaultPackage = + new String(new byte[] {'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's'}); + final String examplePackage = + new String(new byte[] {'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); + // We want to make sure no one just copy & pastes the example and uses the wrong + // package names + if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage) + || MetricsBase.class.getPackage().getName().startsWith(examplePackage)) { + throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); + } + } + } + + /** + * Gzips the given string. + * + * @param str The string to gzip. + * @return The gzipped string. + */ + private static byte[] compress(final String str) throws IOException { + if (str == null) { + return null; + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { + gzip.write(str.getBytes(StandardCharsets.UTF_8)); + } + return outputStream.toByteArray(); + } + } + + public static class AdvancedBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue().length == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class SimplePie extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimplePie(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + String value = callable.call(); + if (value == null || value.isEmpty()) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("value", value).build(); + } + } + + public static class DrilldownPie extends CustomChart { + + private final Callable>> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public DrilldownPie(String chartId, Callable>> callable) { + super(chartId); + this.callable = callable; + } + + @Override + public JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map> map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean reallyAllSkipped = true; + for (Map.Entry> entryValues : map.entrySet()) { + JsonObjectBuilder valueBuilder = new JsonObjectBuilder(); + boolean allSkipped = true; + for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { + valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue()); + allSkipped = false; + } + if (!allSkipped) { + reallyAllSkipped = false; + valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build()); + } + } + if (reallyAllSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class SingleLineChart extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SingleLineChart(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + int value = callable.call(); + if (value == 0) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("value", value).build(); + } + } + + public static class MultiLineChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public MultiLineChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class AdvancedPie extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedPie(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public abstract static class CustomChart { + + private final String chartId; + + protected CustomChart(String chartId) { + if (chartId == null) { + throw new IllegalArgumentException("chartId must not be null"); + } + this.chartId = chartId; + } + + public JsonObjectBuilder.JsonObject getRequestJsonObject( + BiConsumer errorLogger, boolean logErrors) { + JsonObjectBuilder builder = new JsonObjectBuilder(); + builder.appendField("chartId", chartId); + try { + JsonObjectBuilder.JsonObject data = getChartData(); + if (data == null) { + // If the data is null we don't send the chart. + return null; + } + builder.appendField("data", data); + } catch (Throwable t) { + if (logErrors) { + errorLogger.accept("Failed to get data for custom chart with id " + chartId, t); + } + return null; + } + return builder.build(); + } + + protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception; + } + + public static class SimpleBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimpleBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + for (Map.Entry entry : map.entrySet()) { + valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()}); + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + /** + * An extremely simple JSON builder. + * + *

While this class is neither feature-rich nor the most performant one, it's sufficient enough + * for its use-case. + */ + public static class JsonObjectBuilder { + + private StringBuilder builder = new StringBuilder(); + + private boolean hasAtLeastOneField = false; + + public JsonObjectBuilder() { + builder.append("{"); + } + + /** + * Appends a null field to the JSON. + * + * @param key The key of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendNull(String key) { + appendFieldUnescaped(key, "null"); + return this; + } + + /** + * Appends a string field to the JSON. + * + * @param key The key of the field. + * @param value The value of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, String value) { + if (value == null) { + throw new IllegalArgumentException("JSON value must not be null"); + } + appendFieldUnescaped(key, "\"" + escape(value) + "\""); + return this; + } + + /** + * Appends an integer field to the JSON. + * + * @param key The key of the field. + * @param value The value of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, int value) { + appendFieldUnescaped(key, String.valueOf(value)); + return this; + } + + /** + * Appends an object to the JSON. + * + * @param key The key of the field. + * @param object The object. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, JsonObject object) { + if (object == null) { + throw new IllegalArgumentException("JSON object must not be null"); + } + appendFieldUnescaped(key, object.toString()); + return this; + } + + /** + * Appends a string array to the JSON. + * + * @param key The key of the field. + * @param values The string array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, String[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values) + .map(value -> "\"" + escape(value) + "\"") + .collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends an integer array to the JSON. + * + * @param key The key of the field. + * @param values The integer array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, int[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends an object array to the JSON. + * + * @param key The key of the field. + * @param values The integer array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, JsonObject[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends a field to the object. + * + * @param key The key of the field. + * @param escapedValue The escaped value of the field. + */ + private void appendFieldUnescaped(String key, String escapedValue) { + if (builder == null) { + throw new IllegalStateException("JSON has already been built"); + } + if (key == null) { + throw new IllegalArgumentException("JSON key must not be null"); + } + if (hasAtLeastOneField) { + builder.append(","); + } + builder.append("\"").append(escape(key)).append("\":").append(escapedValue); + hasAtLeastOneField = true; + } + + /** + * Builds the JSON string and invalidates this builder. + * + * @return The built JSON string. + */ + public JsonObject build() { + if (builder == null) { + throw new IllegalStateException("JSON has already been built"); + } + JsonObject object = new JsonObject(builder.append("}").toString()); + builder = null; + return object; + } + + /** + * Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt. + * + *

This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'. + * Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n"). + * + * @param value The value to escape. + * @return The escaped value. + */ + private static String escape(String value) { + final StringBuilder builder = new StringBuilder(); + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c == '"') { + builder.append("\\\""); + } else if (c == '\\') { + builder.append("\\\\"); + } else if (c <= '\u000F') { + builder.append("\\u000").append(Integer.toHexString(c)); + } else if (c <= '\u001F') { + builder.append("\\u00").append(Integer.toHexString(c)); + } else { + builder.append(c); + } + } + return builder.toString(); + } + + /** + * A super simple representation of a JSON object. + * + *

This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not + * allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String, + * JsonObject)}. + */ + public static class JsonObject { + + private final String value; + + private JsonObject(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + } + } +} diff --git a/src/me/EtienneDx/RealEstate/RealEstate.java b/src/me/EtienneDx/RealEstate/RealEstate.java index 3339402..095df80 100644 --- a/src/me/EtienneDx/RealEstate/RealEstate.java +++ b/src/me/EtienneDx/RealEstate/RealEstate.java @@ -16,13 +16,10 @@ import java.util.Iterator; import java.util.logging.Logger; import java.util.stream.Stream; - import org.bukkit.configuration.serialization.ConfigurationSerialization; import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.java.JavaPlugin; - import com.earth2me.essentials.Essentials; - import co.aikar.commands.BukkitCommandManager; import co.aikar.commands.ConditionFailedException; import me.EtienneDx.RealEstate.ClaimAPI.IClaim; @@ -37,6 +34,7 @@ import me.EtienneDx.RealEstate.Transactions.ExitOffer; import me.EtienneDx.RealEstate.Transactions.Transaction; import me.EtienneDx.RealEstate.Transactions.TransactionsStore; +import me.EtienneDx.RealEstate.Metrics; import net.milkbowl.vault.economy.Economy; import net.milkbowl.vault.permission.Permission; @@ -187,9 +185,17 @@ public void onEnable() { manager.registerCommand(new RECommand()); copyResourcesIntoPluginDirectory(); + + activateMetrics(); } - /** + private void activateMetrics() { + int id = 24834; + new Metrics(this, id); + this.log.info("Starting Metrics. Opt-out using the global bStats config."); + } + + /** * Checks for the existence of old configuration files and renames them if necessary. */ private void checkForOldFiles() {