Adds sign tracking and refunds #3 #8

This commit is contained in:
Kristian Knarvik 2022-02-28 14:41:14 +01:00
parent 996d062674
commit a1b1a5d112
8 changed files with 303 additions and 65 deletions

View File

@ -13,8 +13,11 @@ import net.knarcraft.paidsigns.command.RemoveTabCommand;
import net.knarcraft.paidsigns.listener.SignListener;
import net.knarcraft.paidsigns.manager.EconomyManager;
import net.knarcraft.paidsigns.manager.PaidSignManager;
import net.knarcraft.paidsigns.manager.TrackedSignManager;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.TabCompleter;
import org.bukkit.command.TabExecutor;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.PluginManager;
@ -31,6 +34,8 @@ public final class PaidSigns extends JavaPlugin {
private PaidSignManager signManager;
private boolean ignoreCase;
private boolean ignoreColor;
private boolean enableRefunds;
private int refundPercentage;
/**
* Instantiates a new paid signs object
@ -53,6 +58,7 @@ public final class PaidSigns extends JavaPlugin {
public void onEnable() {
setupVault();
signManager = new PaidSignManager(PaidSignManager.loadSigns());
TrackedSignManager.loadTrackedSigns();
loadConfig();
PluginManager pluginManager = getServer().getPluginManager();
@ -72,6 +78,7 @@ public final class PaidSigns extends JavaPlugin {
this.reloadConfig();
loadConfig();
signManager = new PaidSignManager(PaidSignManager.loadSigns());
TrackedSignManager.loadTrackedSigns();
}
/**
@ -101,46 +108,56 @@ public final class PaidSigns extends JavaPlugin {
return this.ignoreColor;
}
/**
* Checks whether refunds are currently enabled
*
* @return <p>Whether refunds are currently enabled</p>
*/
public boolean areRefundsEnabled() {
return this.enableRefunds;
}
/**
* Gets the percentage of the initial cost to refund the sign creator
*
* @return <p>The percentage of the cost to refund</p>
*/
public int getRefundPercentage() {
if (this.refundPercentage < 0) {
return 0;
} else if (refundPercentage > 100) {
return 100;
}
return this.refundPercentage;
}
/**
* Registers the commands used by this plugin
*/
private void registerCommands() {
PluginCommand addCommand = this.getCommand("addPaidSign");
if (addCommand != null) {
addCommand.setExecutor(new AddCommand());
addCommand.setTabCompleter(new AddTabCompleter());
}
registerCommand("addPaidSign", new AddCommand(), new AddTabCompleter());
registerCommand("listPaidSigns", new ListCommand(), new ListTabCompleter());
registerCommand("addPaidSignCondition", new AddConditionCommand(), new AddConditionTabCompleter());
registerCommand("removePaidSignCondition", new RemoveConditionCommand(), new RemoveConditionTabCompleter());
PluginCommand listCommand = this.getCommand("listPaidSigns");
if (listCommand != null) {
listCommand.setExecutor(new ListCommand());
listCommand.setTabCompleter(new ListTabCompleter());
}
PluginCommand addConditionCommand = this.getCommand("addPaidSignCondition");
if (addConditionCommand != null) {
addConditionCommand.setExecutor(new AddConditionCommand());
addConditionCommand.setTabCompleter(new AddConditionTabCompleter());
}
PluginCommand removeConditionCommand = this.getCommand("removePaidSignCondition");
if (removeConditionCommand != null) {
removeConditionCommand.setExecutor(new RemoveConditionCommand());
removeConditionCommand.setTabCompleter(new RemoveConditionTabCompleter());
}
PluginCommand removeCommand = this.getCommand("removePaidSign");
if (removeCommand != null) {
TabExecutor removeTabExecutor = new RemoveTabCommand();
removeCommand.setExecutor(removeTabExecutor);
removeCommand.setTabCompleter(removeTabExecutor);
registerCommand("removePaidSign", removeTabExecutor, removeTabExecutor);
TabExecutor reloadTabExecutor = new ReloadTabCommand();
registerCommand("reload", reloadTabExecutor, reloadTabExecutor);
}
PluginCommand reloadCommand = this.getCommand("reload");
if (reloadCommand != null) {
TabExecutor reloadTabExecutor = new ReloadTabCommand();
reloadCommand.setExecutor(reloadTabExecutor);
reloadCommand.setTabCompleter(reloadTabExecutor);
/**
* Registers a command if possible
*
* @param command <p>The command to register</p>
* @param commandExecutor <p>The command executor for executing the command</p>
* @param tabCompleter <p>The tab completer for tab-completing the command</p>
*/
private void registerCommand(String command, CommandExecutor commandExecutor, TabCompleter tabCompleter) {
PluginCommand pluginCommand = this.getCommand(command);
if (pluginCommand != null) {
pluginCommand.setExecutor(commandExecutor);
pluginCommand.setTabCompleter(tabCompleter);
}
}
@ -153,6 +170,8 @@ public final class PaidSigns extends JavaPlugin {
this.saveDefaultConfig();
ignoreCase = config.getBoolean("ignoreCase", true);
ignoreColor = config.getBoolean("ignoreColor", false);
enableRefunds = config.getBoolean("enableRefunds", true);
refundPercentage = config.getInt("refundPercentage", 100);
}
/**

View File

@ -0,0 +1,42 @@
package net.knarcraft.paidsigns.container;
import java.util.UUID;
/**
* A representation of a sign placed by a player that matched a paid sign
*/
public class TrackedSign {
private final UUID playerId;
private final double cost;
/**
* Instantiates a new tracked sign
*
* @param playerId <p>The unique id of the player that created the sign</p>
* @param cost <p>The cost the player paid for creating the sign</p>
*/
public TrackedSign(UUID playerId, double cost) {
this.playerId = playerId;
this.cost = cost;
}
/**
* Gets the id of the player that created this tracked sign
*
* @return <p>The player that created this tracked sign</p>
*/
public UUID getPlayerId() {
return this.playerId;
}
/**
* Gets the cost the player paid for creating this paid sign
*
* @return <p>The cost paid for creating this sign</p>
*/
public double getCost() {
return this.cost;
}
}

View File

@ -0,0 +1,35 @@
package net.knarcraft.paidsigns.listener;
import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.manager.TrackedSignManager;
import org.bukkit.block.Sign;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import java.io.IOException;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A listener that listens for any tracked signs being broken
*/
public class BlockBreakListener implements Listener {
@EventHandler(priority = EventPriority.MONITOR)
public void onBlockBreak(BlockBreakEvent event) {
if (event.getBlock().getState() instanceof Sign) {
try {
TrackedSignManager.removeTrackedSign(event.getBlock().getLocation());
} catch (IOException e) {
e.printStackTrace();
Logger logger = PaidSigns.getInstance().getLogger();
logger.log(Level.SEVERE, "Exception encountered while trying to write to the data file");
logger.log(Level.SEVERE, Arrays.toString(e.getStackTrace()));
}
}
}
}

View File

@ -3,13 +3,18 @@ package net.knarcraft.paidsigns.listener;
import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.container.PaidSign;
import net.knarcraft.paidsigns.manager.EconomyManager;
import net.knarcraft.paidsigns.manager.TrackedSignManager;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.SignChangeEvent;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A listener for listening to registered paid signs
@ -50,6 +55,20 @@ public class SignListener implements Listener {
return true;
}
performPaidSignTransaction(paidSign, player, event);
return true;
}
return false;
}
/**
* Performs the transaction to pay for the paid sign
*
* @param paidSign <p>The paid sign a match has been found for</p>
* @param player <p>The player that created the sign</p>
* @param event <p>The sign change event that caused the sign to be created</p>
*/
private void performPaidSignTransaction(PaidSign paidSign, Player player, SignChangeEvent event) {
double cost = paidSign.getCost();
boolean canAfford = EconomyManager.canAfford(player, cost);
if (!canAfford) {
@ -59,10 +78,15 @@ public class SignListener implements Listener {
String unit = EconomyManager.getCurrency(cost != 1);
player.sendMessage(String.format("[PaidSigns] You paid %.2f %s to create the sign", cost, unit));
EconomyManager.withdraw(player, cost);
try {
TrackedSignManager.addTrackedSign(event.getBlock().getLocation(), player.getUniqueId(), cost);
} catch (IOException e) {
e.printStackTrace();
Logger logger = PaidSigns.getInstance().getLogger();
logger.log(Level.SEVERE, "Exception encountered while trying to write to the data file");
logger.log(Level.SEVERE, Arrays.toString(e.getStackTrace()));
}
return true;
}
return false;
}
}

View File

@ -2,7 +2,6 @@ package net.knarcraft.paidsigns.manager;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
/**
* A manager that performs all Economy tasks
@ -55,7 +54,7 @@ public final class EconomyManager {
* @param player <p>The player to withdraw money from</p>
* @param cost <p>The amount of money to withdraw</p>
*/
public static void withdraw(Player player, double cost) {
public static void withdraw(OfflinePlayer player, double cost) {
economy.withdrawPlayer(player, cost);
}

View File

@ -0,0 +1,128 @@
package net.knarcraft.paidsigns.manager;
import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.container.TrackedSign;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.logging.Level;
/**
* A manager for keeping track of plugin-signs created by players
*/
public class TrackedSignManager {
private static Map<Location, TrackedSign> trackedSigns = new HashMap<>();
private static final File signsFile = new File(PaidSigns.getInstance().getDataFolder(), "data.yml");
/**
* Adds a tracked sign to the manager
*
* @param signLocation <p>The location the sign was created at</p>
* @param playerId <p>The unique id of the player that created the sign</p>
* @throws IOException <p>If unable to save the tracked signs</p>
*/
public static void addTrackedSign(Location signLocation, UUID playerId, double cost) throws IOException {
trackedSigns.put(signLocation, new TrackedSign(playerId, cost));
saveTrackedSigns();
}
/**
* Removes a tracked sign from the manager
*
* @param signLocation <p>The location the sign was removed from</p>
* @throws IOException <p>If unable to save the tracked signs</p>
*/
public static void removeTrackedSign(Location signLocation) throws IOException {
if (!trackedSigns.containsKey(signLocation)) {
return;
}
trackedSigns.remove(signLocation);
saveTrackedSigns();
if (!PaidSigns.getInstance().areRefundsEnabled()) {
return;
}
TrackedSign trackedSign = trackedSigns.get(signLocation);
OfflinePlayer player = Bukkit.getOfflinePlayer(trackedSign.getPlayerId());
double refundSum = trackedSign.getCost() / 100 * PaidSigns.getInstance().getRefundPercentage();
EconomyManager.withdraw(player, refundSum);
}
/**
* Loads all tracked signs from the data file
*/
public static void loadTrackedSigns() {
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(signsFile);
ConfigurationSection signSection = configuration.getConfigurationSection("trackedSigns");
trackedSigns = new HashMap<>();
if (signSection == null) {
PaidSigns.getInstance().getLogger().log(Level.WARNING, "Signs section not found in data.yml");
return;
}
for (String key : signSection.getKeys(false)) {
try {
loadSign(signSection, key);
} catch (InvalidConfigurationException e) {
PaidSigns.getInstance().getLogger().log(Level.SEVERE, "Unable to load sign " + key + ": " +
e.getMessage());
}
}
}
/**
* Loads a sign from the save file
*
* @param signSection <p>The configuration section containing signs</p>
* @param key <p>The sign key which is also the sign's location</p>
* @throws InvalidConfigurationException <p>If unable to load the sign</p>
*/
private static void loadSign(ConfigurationSection signSection, String key) throws InvalidConfigurationException {
String[] locationParts = key.split(",");
Location signLocation;
try {
signLocation = new Location(Bukkit.getWorld(UUID.fromString(locationParts[0])),
Double.parseDouble(locationParts[1]), Double.parseDouble(locationParts[2]),
Double.parseDouble(locationParts[3]));
} catch (NumberFormatException exception) {
throw new InvalidConfigurationException("Invalid sign coordinates");
}
double cost = signSection.getDouble(key + ".cost");
UUID playerId = UUID.fromString(Objects.requireNonNull(signSection.getString(key + ".playerId")));
TrackedSign trackedSign = new TrackedSign(playerId, cost);
trackedSigns.put(signLocation, trackedSign);
}
/**
* Saves the managed tracked signs to the data file
*
* @throws IOException <p>If unable to write to the data file</p>
*/
private static void saveTrackedSigns() throws IOException {
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(signsFile);
ConfigurationSection signSection = configuration.createSection("trackedSigns");
for (Location signLocation : trackedSigns.keySet()) {
TrackedSign sign = trackedSigns.get(signLocation);
String locationString = Objects.requireNonNull(signLocation.getWorld()).getUID() + "," +
signLocation.getBlockX() + "," + signLocation.getBlockY() + "," + signLocation.getBlockZ();
signSection.set(locationString + ".cost", sign.getCost());
signSection.set(locationString + ".playerId", sign.getPlayerId());
}
configuration.save(signsFile);
}
}

View File

@ -15,23 +15,6 @@ public final class TabCompleteHelper {
}
/**
* Finds tab complete values that contain the typed text
*
* @param values <p>The values to filter</p>
* @param typedText <p>The text the player has started typing</p>
* @return <p>The given string values that contain the player's typed text</p>
*/
public static List<String> filterMatchingContains(List<String> values, String typedText) {
List<String> configValues = new ArrayList<>();
for (String value : values) {
if (value.toLowerCase().contains(typedText.toLowerCase())) {
configValues.add(value);
}
}
return configValues;
}
/**
* Finds tab complete values that match the start of the typed text
*

View File

@ -1,8 +1,16 @@
# Whether to ignore the case (lowercase/uppercase) of the paid sign text. The option can be set on a per-sign basis, but
# this value is used if not specified. The correct value depends on whether the plugin signs it should match are case-sensitive or not.
# this value is used if not specified. The correct value depends on whether the plugin signs it should match are
# case-sensitive or not.
ignoreCase: true
# Whether to ignore any color or formatting applied to the text when trying to match a paid sign's text. The option can
# be set on a per-sign basis, but this value is used if not specified. The correct value depends on whether the plugin
# signs it should match allow coloring or not.
ignoreColor: false
# Whether to enable refunds to the sign creator when a sign detected as a paid sign is broken (payment will always go
# to the original creator)
enableRefunds: true
# The percentage of the paid sign cost to refund (0-100)
refundPercentage: 100