Initial commit

This commit is contained in:
Kristian Knarvik 2022-02-18 00:28:44 +01:00
commit ed5690d197
17 changed files with 1317 additions and 0 deletions

113
.gitignore vendored Normal file
View File

@ -0,0 +1,113 @@
# User-specific stuff
.idea/
*.iml
*.ipr
*.iws
# IntelliJ
out/
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
.flattened-pom.xml
# Common working directory
run/

92
pom.xml Normal file
View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.knarcraft</groupId>
<artifactId>paidsigns</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Paid Signs</name>
<description>Add costs for creating plugin signs</description>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<url>https://git.knarcraft.net</url>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>16</source>
<target>16</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
<repositories>
<repository>
<id>spigotmc-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<repository>
<id>sonatype</id>
<url>https://oss.sonatype.org/content/groups/public/</url>
</repository>
<repository>
<id>vault-repo</id>
<url>http://nexus.hc.to/content/repositories/pub_releases</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.18.1-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.milkbowl.vault</groupId>
<artifactId>VaultAPI</artifactId>
<version>1.7</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>22.0.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,147 @@
package net.knarcraft.paidsigns;
import net.knarcraft.paidsigns.command.AddCommand;
import net.knarcraft.paidsigns.command.AddTabCompleter;
import net.knarcraft.paidsigns.command.ReloadTabCommand;
import net.knarcraft.paidsigns.command.RemoveCommand;
import net.knarcraft.paidsigns.command.RemoveTabCompleter;
import net.knarcraft.paidsigns.listener.SignListener;
import net.knarcraft.paidsigns.manager.EconomyManager;
import net.knarcraft.paidsigns.manager.PaidSignManager;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.TabExecutor;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.ServicesManager;
import org.bukkit.plugin.java.JavaPlugin;
/**
* The PaidSigns plugin's main class
*/
public final class PaidSigns extends JavaPlugin {
private static PaidSigns paidSigns;
private PaidSignManager signManager;
private boolean ignoreCase;
private boolean ignoreColor;
/**
* Instantiates a new paid signs object
*/
@SuppressWarnings("unused")
public PaidSigns() {
paidSigns = this;
}
/**
* Gets an instance of this plugin
*
* @return <p>An instance of this plugin</p>
*/
public static PaidSigns getInstance() {
return paidSigns;
}
@Override
public void onEnable() {
setupVault();
signManager = new PaidSignManager(PaidSignManager.loadSigns());
loadConfig();
PluginManager pluginManager = getServer().getPluginManager();
pluginManager.registerEvents(new SignListener(), this);
registerCommands();
}
@Override
public void onDisable() {
}
/**
* Reloads this plugin
*/
public void reload() {
this.reloadConfig();
loadConfig();
signManager = new PaidSignManager(PaidSignManager.loadSigns());
}
/**
* Gets the paid sign manager used to manage paid signs
*
* @return <p>The paid sign manager</p>
*/
public PaidSignManager getSignManager() {
return signManager;
}
/**
* Gets the default setting for whether to ignore the case of paid sign ids
*
* @return <p>The default ignore case value</p>
*/
public boolean ignoreCase() {
return this.ignoreCase;
}
/**
* Gets the default setting for whether to ignore the color of paid sign ids
*
* @return <p>The default ignore color value</p>
*/
public boolean ignoreColor() {
return this.ignoreColor;
}
/**
* 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());
}
PluginCommand removeCommand = this.getCommand("removePaidSign");
if (removeCommand != null) {
removeCommand.setExecutor(new RemoveCommand());
removeCommand.setTabCompleter(new RemoveTabCompleter());
}
PluginCommand reloadCommand = this.getCommand("reload");
if (reloadCommand != null) {
TabExecutor reloadTabExecutor = new ReloadTabCommand();
reloadCommand.setExecutor(reloadTabExecutor);
reloadCommand.setTabCompleter(reloadTabExecutor);
}
}
/**
* Loads the configuration file
*/
private void loadConfig() {
FileConfiguration config = this.getConfig();
config.options().copyDefaults(true);
this.saveDefaultConfig();
ignoreCase = config.getBoolean("ignoreCase", true);
ignoreColor = config.getBoolean("ignoreColor", false);
}
/**
* Sets up Vault by getting plugins from their providers
*/
private void setupVault() {
ServicesManager servicesManager = this.getServer().getServicesManager();
RegisteredServiceProvider<Economy> economyProvider = servicesManager.getRegistration(Economy.class);
if (economyProvider != null) {
EconomyManager.initialize(economyProvider.getProvider());
} else {
throw new IllegalStateException("[PaidSigns] Error: Vault could not be loaded");
}
}
}

View File

@ -0,0 +1,76 @@
package net.knarcraft.paidsigns.command;
import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.container.PaidSign;
import net.knarcraft.paidsigns.manager.PaidSignManager;
import net.knarcraft.paidsigns.property.OptionState;
import net.knarcraft.paidsigns.utility.Tokenizer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A representation of the command for adding a new paid sign
*/
public class AddCommand implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] args) {
if (args.length < 3) {
return false;
}
PaidSignManager manager = PaidSigns.getInstance().getSignManager();
List<String> arguments = Tokenizer.tokenize(String.join(" ", args));
String id = arguments.get(0);
short line;
double cost;
try {
line = (short) (Short.parseShort(arguments.get(1)) - 1);
cost = Double.parseDouble(arguments.get(2));
} catch (NumberFormatException exception) {
sender.sendMessage("You provided an invalid number");
return false;
}
OptionState ignoreCase = OptionState.DEFAULT;
OptionState ignoreColor = OptionState.DEFAULT;
if (arguments.size() > 3) {
ignoreCase = OptionState.fromString(arguments.get(3));
}
if (arguments.size() > 4) {
ignoreColor = OptionState.fromString(arguments.get(4));
}
try {
PaidSign sign = new PaidSign(id, line, cost, ignoreCase, ignoreColor);
for (PaidSign similarSign : manager.getPaidSigns(sign.getCleanId(), sign.getLineIndex())) {
if (sign.matches(similarSign)) {
sender.sendMessage("A paid sign with the same id and line already exists");
return false;
}
}
manager.addPaidSign(sign);
sender.sendMessage("Successfully added new paid sign");
return true;
} catch (IOException e) {
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()));
sender.sendMessage("An exception occurred. Please notify the server administrator or check the server log.");
return false;
} catch (IllegalArgumentException e) {
sender.sendMessage("Invalid input: " + e.getMessage());
}
return false;
}
}

View File

@ -0,0 +1,71 @@
package net.knarcraft.paidsigns.command;
import net.knarcraft.paidsigns.utility.Tokenizer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* The tab completer for the add paid sign command
*/
public class AddTabCompleter implements TabCompleter {
private static List<String> ids;
private static List<String> lines;
private static List<String> costs;
private static List<String> options;
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] args) {
if (ids == null) {
initializeValues();
}
List<String> arguments = Tokenizer.tokenize(String.join(" ", args));
if (arguments.size() < 1) {
return ids;
} else if (arguments.size() < 2) {
return lines;
} else if (arguments.size() < 3) {
return costs;
} else if (arguments.size() < 5) {
return options;
}
return new ArrayList<>();
}
/**
* Initializes the values available for tab completion
*/
private static void initializeValues() {
ids = new ArrayList<>();
ids.add("[Gate]");
ids.add("\"[Lift Up]\"");
ids.add("\"[Lift Down]\"");
lines = new ArrayList<>();
lines.add("1");
lines.add("2");
lines.add("3");
lines.add("4");
costs = new ArrayList<>();
costs.add("1");
costs.add("5");
costs.add("10");
costs.add("15");
options = new ArrayList<>();
options.add("default");
options.add("true");
options.add("false");
}
}

View File

@ -0,0 +1,30 @@
package net.knarcraft.paidsigns.command;
import net.knarcraft.paidsigns.PaidSigns;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
/**
* A representation of the command for reloading the plugin
*/
public class ReloadTabCommand implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] args) {
PaidSigns.getInstance().reload();
return false;
}
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] args) {
return new ArrayList<>();
}
}

View File

@ -0,0 +1,55 @@
package net.knarcraft.paidsigns.command;
import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.utility.Tokenizer;
import org.apache.commons.lang.ArrayUtils;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A representation of the command for removing a paid sign
*/
public class RemoveCommand implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] args) {
if (args.length < 1) {
return false;
}
List<String> arguments = Tokenizer.tokenize(String.join(" ", args));
String[] input = arguments.get(0).split("\\|");
short line;
try {
line = (short) (Short.parseShort(input[0]) - 1);
} catch (NumberFormatException exception) {
sender.sendMessage("Invalid line number given");
return false;
}
String id = String.join("|", (String[]) ArrayUtils.remove(input, 0));
try {
if (PaidSigns.getInstance().getSignManager().removePaidSign(id, line)) {
sender.sendMessage("Successfully removed paid sign");
} else {
sender.sendMessage("No matching paid sign was found");
}
return true;
} catch (IOException e) {
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()));
sender.sendMessage("An exception occurred. Please notify the server administrator or check the server log.");
}
return false;
}
}

View File

@ -0,0 +1,31 @@
package net.knarcraft.paidsigns.command;
import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.container.PaidSign;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* The tab completer for the remove command
*/
public class RemoveTabCompleter implements TabCompleter {
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] args) {
List<PaidSign> allPaidSigns = PaidSigns.getInstance().getSignManager().getAllPaidSigns();
List<String> signIds = new ArrayList<>();
for (PaidSign sign : allPaidSigns) {
signIds.add("\"" + (sign.getLineIndex() + 1) + "|" + sign.getId() + "\"");
}
return signIds;
}
}

View File

@ -0,0 +1,157 @@
package net.knarcraft.paidsigns.container;
import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.property.OptionState;
import net.knarcraft.paidsigns.utility.ColorHelper;
import net.md_5.bungee.api.ChatColor;
/**
* A representation of a paid sign
*/
public class PaidSign {
private final String id;
private final String cleanId;
private final short lineIndex;
private final double cost;
private final OptionState ignoreCase;
private final OptionState ignoreColor;
/**
* Instantiates a new paid sign
*
* @param id <p>The string that identifies this type of paid sign</p>
* @param lineIndex <p>The line the id has to be on to trigger payment</p>
* @param cost <p>The cost of creating this paid sign</p>
* @param ignoreCase <p>Whether to ignore case when looking for this permission sign</p>
* @param ignoreColor <p>Whether to ignore color when looking for this permission sign</p>
*/
public PaidSign(String id, short lineIndex, double cost, OptionState ignoreCase, OptionState ignoreColor) {
if (id == null || id.trim().isBlank()) {
throw new IllegalArgumentException("Id cannot be empty");
}
if (cost <= 0) {
throw new IllegalArgumentException("Cost must be larger than 0");
}
if (lineIndex < 0 || lineIndex > 3) {
throw new IllegalArgumentException("Sign line must be between 0 and 3");
}
if (ignoreCase == null || ignoreColor == null) {
throw new IllegalArgumentException("Ignore case and ignore color options cannot be null");
}
this.id = id;
this.lineIndex = lineIndex;
this.cost = cost;
this.ignoreCase = ignoreCase;
this.ignoreColor = ignoreColor;
this.cleanId = getCleanString(id);
}
/**
* Gets the id string of this paid sign
*
* @return <p>The id string of this paid sign</p>
*/
public String getId() {
return id;
}
/**
* Gets the cost of creating a sign matching this paid sign
*
* @return <p>The cost of creating a sign matching this paid sign</p>
*/
public double getCost() {
return this.cost;
}
/**
* Gets the line on the sign the id must be on to trigger payment
*
* @return <p>The sign line to search for the id</p>
*/
public short getLineIndex() {
return lineIndex;
}
/**
* Gets the clean id of this paid sign
*
* @return <p>The clean id of this paid sign</p>
*/
public String getCleanId() {
return cleanId;
}
/**
* Gets the "clean" version of the given string
*
* <p>The "cleaning" removes color codes and lower-cases the string.</p>
*
* @param string <p>The string to clean</p>
* @return <p>The cleaned string</p>
*/
public static String getCleanString(String string) {
return ChatColor.stripColor(ColorHelper.translateAllColorCodes(string.toLowerCase()));
}
/**
* Checks whether this paid sign matches the given line
*
* @param lineNumber <p>The line number of the given line</p>
* @param line <p>The line to compare against this paid sign's id</p>
* @return <p>True if the line matches this sign</p>
*/
public boolean matches(short lineNumber, String line) {
if (lineNumber != this.lineIndex) {
return false;
}
String idCopy = id;
if (getIgnoreCase()) {
idCopy = idCopy.toLowerCase();
line = line.toLowerCase();
}
if (getIgnoreColor()) {
idCopy = ColorHelper.stripColorCodes(idCopy);
line = ColorHelper.stripColorCodes(line);
}
return idCopy.equals(line);
}
/**
* Checks whether this paid sign matches another paid sign
*
* @param paidSign <p>The other paid sign to compare to</p>
* @return <p>True if the signs match</p>
*/
public boolean matches(PaidSign paidSign) {
return matches(paidSign.lineIndex, paidSign.id);
}
/**
* Gets whether the text case should be ignored for this paid sign
*
* @return <p>Whether the text case should be ignored for this paid sign</p>
*/
public boolean getIgnoreCase() {
if (this.ignoreCase == OptionState.DEFAULT) {
return PaidSigns.getInstance().ignoreCase();
} else {
return OptionState.getBooleanValue(this.ignoreCase);
}
}
/**
* Gets whether the text color should be ignored for this paid sign
*
* @return <p>Whether the text color should be ignored for this paid sign</p>
*/
public boolean getIgnoreColor() {
if (this.ignoreColor == OptionState.DEFAULT) {
return PaidSigns.getInstance().ignoreColor();
} else {
return OptionState.getBooleanValue(this.ignoreColor);
}
}
}

View File

@ -0,0 +1,69 @@
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.PaidSignManager;
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.util.List;
/**
* A listener for listening to registered paid signs
*/
public class SignListener implements Listener {
@EventHandler(priority = EventPriority.LOW)
public void onSignChange(SignChangeEvent event) {
if (event.isCancelled()) {
return;
}
String[] lines = event.getLines();
PaidSignManager signManager = PaidSigns.getInstance().getSignManager();
for (short lineIndex = 0; lineIndex < lines.length; lineIndex++) {
//Get all "weak" matches (any paid sign with a clean id matching the clean line)
List<PaidSign> matchingSigns = signManager.getPaidSigns(PaidSign.getCleanString(lines[lineIndex]), lineIndex);
if (matchingSigns.isEmpty()) {
continue;
}
if (testMatchingSigns(lineIndex, lines[lineIndex], matchingSigns, event)) {
return;
}
}
}
/**
* Tests all weak matches of paid signs to check if a strong match is found
*
* @param lineIndex <p>The index of the currently managed line</p>
* @param line <p>The text on the currently managed line</p>
* @param matchingSigns <p>The signs that weakly match the </p>
* @param event <p>The triggered sign change event</p>
* @return <p>True if a match was found and thw work is finished</p>
*/
private boolean testMatchingSigns(short lineIndex, String line, List<PaidSign> matchingSigns, SignChangeEvent event) {
for (PaidSign paidSign : matchingSigns) {
if (paidSign.matches(lineIndex, line)) {
Player player = event.getPlayer();
double cost = paidSign.getCost();
boolean canAfford = EconomyManager.canAfford(player, cost);
if (!canAfford) {
player.sendMessage("[PaidSigns] You cannot afford to create this sign");
event.setCancelled(true);
} else {
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);
}
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,62 @@
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
*/
public final class EconomyManager {
private static Economy economy;
private EconomyManager() {
}
/**
* Initializes the economy manager
*
* @param economy <p>The economy object to use for everything economy-related</p>
*/
public static void initialize(Economy economy) {
EconomyManager.economy = economy;
}
/**
* Checks whether the given player can afford the given cost
*
* @param player <p>The player to pay the cost</p>
* @param cost <p>The cost the player needs to pay</p>
* @return <p>True if the player is able to afford the cost</p>
*/
public static boolean canAfford(OfflinePlayer player, double cost) {
return economy.has(player, cost);
}
/**
* Gets the name of the used currency
*
* @param plural <p>Whether to get the plural name or the singular name</p>
* @return <p>The name of the used currency</p>
*/
public static String getCurrency(boolean plural) {
if (plural) {
return economy.currencyNamePlural();
} else {
return economy.currencyNameSingular();
}
}
/**
* Withdraws the given cost from the given player's account
*
* @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) {
economy.withdrawPlayer(player, cost);
}
}

View File

@ -0,0 +1,160 @@
package net.knarcraft.paidsigns.manager;
import net.knarcraft.paidsigns.PaidSigns;
import net.knarcraft.paidsigns.container.PaidSign;
import net.knarcraft.paidsigns.property.OptionState;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.logging.Level;
/**
* A manager that keeps track of all registered paid signs
*/
public final class PaidSignManager {
private final List<PaidSign> paidSigns;
private static final File signsFile = new File(PaidSigns.getInstance().getDataFolder(), "data.yml");
private static final String signLineIdSeparator = "-,_,-";
/**
* Instantiate a new paid sign manager
*
* @param paidSigns <p>The paid signs this manager should manage</p>
*/
public PaidSignManager(List<PaidSign> paidSigns) {
this.paidSigns = paidSigns;
}
/**
* Adds a new paid sign to this paid sign manager
*
* @param paidSign <p>The paid sign to add</p>
* @throws IOException <p>If unable to write to the signs file</p>
*/
public void addPaidSign(PaidSign paidSign) throws IOException {
this.paidSigns.add(paidSign);
saveSigns(this.paidSigns);
}
/**
* Removes a paid sign from this paid sign manager
*
* @param id <p>The identifier for the paid sign to remove</p>
* @param line <p>The line the identifier has to match to be valid</p>
* @return <p>True if a sign was removed</p>
* @throws IOException <p>If unable to write to the signs file</p>
*/
public boolean removePaidSign(String id, short line) throws IOException {
boolean removed = this.paidSigns.removeIf((sign) -> sign.getId().equals(id) && sign.getLineIndex() == line);
if (!removed) {
return false;
}
saveSigns(this.paidSigns);
return true;
}
/**
* Gets the paid signs that match the given properties
*
* @param cleanId <p>The clean id to search for</p>
* @param line <p>The line number to search for</p>
* @return <p>The paid signs that matched the given properties</p>
*/
public List<PaidSign> getPaidSigns(String cleanId, short line) {
return filterPaidSigns(filterPaidSigns(paidSigns, line), cleanId);
}
/**
* Gets a copy of all registered paid signs
*
* @return <p>All registered paid signs</p>
*/
public List<PaidSign> getAllPaidSigns() {
return new ArrayList<>(paidSigns);
}
/**
* Filters a list of paid signs to match the given line number
*
* @param paidSigns <p>The list of paid signs to start with</p>
* @param line <p>The line number to filter by</p>
* @return <p>The filtered list of paid signs</p>
*/
private static List<PaidSign> filterPaidSigns(List<PaidSign> paidSigns, short line) {
return filterPaidSigns(paidSigns, (paidSign) -> paidSign.getLineIndex() == line);
}
/**
* Loads paid signs from the signs file
*
* @return <p>The loaded paid signs</p>
*/
public static List<PaidSign> loadSigns() {
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(signsFile);
ConfigurationSection signSection = configuration.getConfigurationSection("paidSigns");
if (signSection == null) {
PaidSigns.getInstance().getLogger().log(Level.WARNING, "Signs section not found in data.yml");
return new ArrayList<>();
}
List<PaidSign> paidSigns = new ArrayList<>();
for (String combinedId : signSection.getKeys(false)) {
String[] idParts = combinedId.split(signLineIdSeparator);
short lineNumber = Short.parseShort(idParts[0]);
String id = idParts[1];
double cost = signSection.getDouble(combinedId + ".cost");
OptionState ignoreCase = OptionState.getFromBoolean(signSection.getBoolean(combinedId + ".ignoreCase"));
OptionState ignoreColor = OptionState.getFromBoolean(signSection.getBoolean(combinedId + ".ignoreColor"));
paidSigns.add(new PaidSign(id, lineNumber, cost, ignoreCase, ignoreColor));
}
return paidSigns;
}
/**
* Saves the given paid signs to the signs file
*
* @param signs <p>The signs to save</p>
* @throws IOException <p>If unable to write to the signs file</p>
*/
public static void saveSigns(List<PaidSign> signs) throws IOException {
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(signsFile);
ConfigurationSection signSection = configuration.createSection("paidSigns");
for (PaidSign sign : signs) {
String signId = sign.getLineIndex() + signLineIdSeparator + sign.getId();
signSection.set(signId + ".cost", sign.getCost());
signSection.set(signId + ".ignoreCase", sign.getIgnoreCase());
signSection.set(signId + ".ignoreColor", sign.getIgnoreColor());
}
configuration.save(signsFile);
}
/**
* Filters a list of paid signs to match the given clean id
*
* @param paidSigns <p>The list of paid signs to start with</p>
* @param cleanId <p>The clean id to filter by</p>
* @return <p>The filtered list of paid signs</p>
*/
private static List<PaidSign> filterPaidSigns(List<PaidSign> paidSigns, String cleanId) {
return filterPaidSigns(paidSigns, (paidSign) -> paidSign.getCleanId().equals(cleanId));
}
/**
* Filters a list of paid signs using the given predicate
*
* @param paidSigns <p>The list of paid signs to start with</p>
* @param predicate <p>The predicate used to filter paid signs</p>
* @return <p>The filtered list of paid signs</p>
*/
private static List<PaidSign> filterPaidSigns(List<PaidSign> paidSigns, Predicate<PaidSign> predicate) {
return new ArrayList<>(paidSigns).stream().filter(predicate).toList();
}
}

View File

@ -0,0 +1,65 @@
package net.knarcraft.paidsigns.property;
/**
* A class representing the different available states for a paid sign option
*/
public enum OptionState {
/**
* The option is enabled
*/
TRUE,
/**
* The option is disabled
*/
FALSE,
/**
* The option is the same as the default value
*/
DEFAULT;
/**
* Gets the boolean value of the given option state if it's boolean compatible
*
* @param optionState <p>The option state to get the boolean from</p>
* @return <p>The boolean value, or an illegal argument exception if called on DEFAULT</p>
*/
public static boolean getBooleanValue(OptionState optionState) {
return switch (optionState) {
case TRUE -> true;
case FALSE -> false;
case DEFAULT -> throw new IllegalArgumentException("No boolean value available for DEFAULT");
};
}
/**
* Gets the corresponding option state from the given boolean
*
* @param value <p>The boolean to parse</p>
* @return <p>The corresponding option state</p>
*/
public static OptionState getFromBoolean(boolean value) {
if (value) {
return OptionState.TRUE;
} else {
return OptionState.FALSE;
}
}
/**
* Gets the option state corresponding to the given string
*
* @param string <p>The string to parse to an option state</p>
* @return <p>The option state corresponding to the given string</p>
*/
public static OptionState fromString(String string) {
if (string.equalsIgnoreCase("default") || string.equalsIgnoreCase("def")) {
return OptionState.DEFAULT;
} else {
return getFromBoolean(Boolean.parseBoolean(string));
}
}
}

View File

@ -0,0 +1,43 @@
package net.knarcraft.paidsigns.utility;
import net.md_5.bungee.api.ChatColor;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A helper class for dealing with colors
*/
public final class ColorHelper {
private ColorHelper() {
}
/**
* Strips all color codes from the given message
*
* @param message <p>The message to strip color codes from</p>
* @return <p>The message without color codes</p>
*/
public static String stripColorCodes(String message) {
return ChatColor.stripColor(translateAllColorCodes(message));
}
/**
* Translates all found color codes to formatting in a string
*
* @param message <p>The string to search for color codes</p>
* @return <p>The message with color codes translated</p>
*/
public static String translateAllColorCodes(String message) {
message = ChatColor.translateAlternateColorCodes('&', message);
Pattern pattern = Pattern.compile("(#[a-fA-F0-9]{6})");
Matcher matcher = pattern.matcher(message);
while (matcher.find()) {
message = message.replace(matcher.group(), "" + ChatColor.of(matcher.group()));
}
return message;
}
}

View File

@ -0,0 +1,99 @@
package net.knarcraft.paidsigns.utility;
import java.util.ArrayList;
import java.util.List;
/**
* A tokenizer for being able to support quotes in commands
*/
public class Tokenizer {
/**
* Tokenizes a string
*
* @param input <p>A string.</p>
* @return <p>A list of tokens.</p>
*/
public static List<String> tokenize(String input) {
List<String> tokens = new ArrayList<>();
boolean startedQuote = false;
StringBuilder currentToken = new StringBuilder();
for (int index = 0; index < input.length(); index++) {
char character = input.charAt(index);
switch (character) {
case ' ':
if (tokenizeSpace(startedQuote, currentToken, tokens)) {
currentToken = new StringBuilder();
}
break;
case '"':
if (startedQuote) {
//This quote signifies the end of the argument
if (isNotEmpty(currentToken)) {
tokens.add(currentToken.toString());
currentToken = new StringBuilder();
}
startedQuote = false;
} else {
//This quote signifies the start of the argument
startedQuote = true;
currentToken = new StringBuilder();
}
break;
default:
tokenizeNormalCharacter(currentToken, character, input.length(), index, tokens);
break;
}
}
return tokens;
}
/**
* Adds a normal character to the token. Adds the current token to tokens if at the end of the input
*
* @param currentToken <p>The string builder containing the current token.</p>
* @param character <p>The character found in the input.</p>
* @param inputLength <p>The length of the given input</p>
* @param index <p>The index of the read character.</p>
* @param tokens <p>The list of processed tokens.</p>
*/
private static void tokenizeNormalCharacter(StringBuilder currentToken, char character, int inputLength, int index,
List<String> tokens) {
currentToken.append(character);
if (index == inputLength - 1) {
tokens.add(currentToken.toString());
}
}
/**
* Tokenizes a space character
*
* @param startedQuote <p>Whether this space is inside a pair of quotes.</p>
* @param currentToken <p>The string builder containing the current token.</p>
* @param tokens <p>The list of processed tokens.</p>
* @return <p>True if the token is finished.</p>
*/
private static boolean tokenizeSpace(boolean startedQuote, StringBuilder currentToken, List<String> tokens) {
if (!startedQuote) {
//If not inside "", a space marks the end of a parameter
if (isNotEmpty(currentToken)) {
tokens.add(currentToken.toString());
}
return true;
} else {
currentToken.append(' ');
return false;
}
}
/**
* Checks whether a string builder is empty
*
* @param builder <p>The string builder to check.</p>
* @return <p>True if the string builder is non empty.</p>
*/
private static boolean isNotEmpty(StringBuilder builder) {
return !builder.toString().trim().equals("");
}
}

View File

@ -0,0 +1,8 @@
# 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.
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

View File

@ -0,0 +1,39 @@
name: PaidSigns
version: '${project.version}'
main: net.knarcraft.paidsigns.PaidSigns
api-version: 1.18
prefix: PaidSigns
depend: [ Vault ]
authors: [ EpicKnarvik97 ]
description: Add costs for creating plugin signs
website: https://git.knarcraft.net
commands:
addpaidsign:
description: Used to add a new paid sign
usage: /<command> <id (the text to look for)> <line> <cost> [ignore case] [ignore color]
permission: paidsigns.add
removepaidsign:
description: Used to remove a paid sign
usage: /<command> <id (the text to look for)> <line>
permission: paidsigns.remove
reload:
description: Reloads paid signs from disk
usage: /<command>
permision: paidsigns.reload
permissions:
paidsigns.*:
description: Grants all paid signs permissions
default: op
children:
paidsigns.create: true
paidsigns.paymentexempt: true
paidsigns.reload: true
paidsigns.add:
description: Grants the permission to add/remove a paid sign
default: false
paidsigns.reload:
description: Grants the permissions to reload the plugin
default: false
paidsigns.paymentexempt:
description: Makes this player exempt from the cost of paid signs
default: false