package net.knarcraft.playerpayouts.config; import net.knarcraft.playerpayouts.PlayerPayouts; import net.knarcraft.playerpayouts.config.payout.PayoutAction; import net.knarcraft.playerpayouts.config.payout.PayoutActionParser; import net.knarcraft.playerpayouts.config.payout.PayoutComponent; import net.knarcraft.playerpayouts.config.payout.PayoutDelimiter; import net.knarcraft.playerpayouts.config.payout.PayoutTarget; import net.knarcraft.playerpayouts.manager.PermissionManager; import org.apache.commons.lang.NotImplementedException; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.text.ParseException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.logging.Level; /** * This plugin's configuration */ public class Configuration { private FileConfiguration fileConfiguration; private Map groupPayouts; private Map playerPayouts; private double defaultPayout; private int hoursUntilBonus; private double bonusMultiplier; private int payoutDelay; private double afkPercentage; private boolean displayPaymentMessage; private PayoutComponent payoutComponent; /** * Instantiates a new configuration * * @param fileConfiguration

The file configuration to read values from

*/ public Configuration(@NotNull FileConfiguration fileConfiguration) { load(fileConfiguration); } /** * Loads the configuration specified in the given file configuration * * @param fileConfiguration

The file configuration to load

*/ public void load(@NotNull FileConfiguration fileConfiguration) { groupPayouts = new HashMap<>(); playerPayouts = new HashMap<>(); this.fileConfiguration = fileConfiguration; // Load per-group payouts ConfigurationSection groupPayoutsSection = fileConfiguration.getConfigurationSection( ConfigurationKey.GROUP_PAYOUTS.getPath()); if (groupPayoutsSection != null) { for (String key : groupPayoutsSection.getKeys(false)) { groupPayouts.put(key, groupPayoutsSection.getDouble(key)); } } // Load per-player payouts ConfigurationSection playerPayoutsSection = fileConfiguration.getConfigurationSection( ConfigurationKey.PLAYER_PAYOUTS.getPath()); if (playerPayoutsSection != null) { for (String key : playerPayoutsSection.getKeys(false)) { playerPayouts.put(UUID.fromString(key), playerPayoutsSection.getDouble(key)); } } // Load simple configuration values this.defaultPayout = fileConfiguration.getDouble(ConfigurationKey.DEFAULT_PAYOUT.getPath(), 10); this.hoursUntilBonus = fileConfiguration.getInt(ConfigurationKey.HOURS_UNTIL_BONUS.getPath(), 100); this.bonusMultiplier = fileConfiguration.getDouble(ConfigurationKey.BONUS_MULTIPLIER.getPath(), 1); this.payoutDelay = fileConfiguration.getInt(ConfigurationKey.PAYOUT_DELAY.getPath(), 60); this.afkPercentage = fileConfiguration.getDouble(ConfigurationKey.AFK_PERCENTAGE.getPath(), 0); this.displayPaymentMessage = fileConfiguration.getBoolean(ConfigurationKey.DISPLAY_PAYMENT_MESSAGE.getPath(), true); // Parse the payout rules try { this.payoutComponent = PayoutActionParser.matchPayoutComponent(fileConfiguration.getString( ConfigurationKey.PAYOUT_RULES.getPath(), "p,hg,b")); } catch (ParseException exception) { PlayerPayouts.getInstance().getLogger().log(Level.SEVERE, "Unable to parse your payout rules! Please " + "check your configuration file! Falling back to default!"); PlayerPayouts.getInstance().getLogger().log(Level.SEVERE, exception.getMessage()); try { this.payoutComponent = PayoutActionParser.matchPayoutComponent("p,hg,b"); } catch (ParseException e) { throw new RuntimeException(e); } } } /** * Gets the number of hours a player must have played to receive a payment bonus * * @return

The min. amount of hours required to get a bonus

*/ public int getHoursUntilBonus() { return hoursUntilBonus; } /** * A multiplier to apply to the bonus * *

By default, the bonus is total playtime / hours until bonus. This multiplier allows adjusting how high or low * the bonus becomes. For example, a bonus multiplier of 0.5 would halve the paid bonus, while a bonus multiplier * of 2 would double the paid bonus.

* * @return

The bonus multiplier

*/ public double getBonusMultiplier() { return bonusMultiplier; } /** * Gets the base pay for the given player * * @param player

The player to get the base pay for

* @return

The base pay for the player

*/ public double getBasePay(@NotNull Player player) { double pay = calculatePayoutFromComponent(payoutComponent, player); if (pay != 0) { return pay; } else { return getDefaultBasePay(player); } } /** * Gets the base payout to pay the given player * * @param player

The player to get the base payout for

* @return

The player's base pay

*/ public double getDefaultBasePay(@NotNull Player player) { if (playerPayouts.get(player.getUniqueId()) != null) { return playerPayouts.get(player.getUniqueId()); } double groupPayout = 0; if (PermissionManager.isInitialized()) { groupPayout = getGroupPayout(player, true); } if (groupPayout == 0) { return defaultPayout; } else { return groupPayout; } } /** * Gets the max payout of the given player's permission groups * * @param player

The player to get the group payout for

* @param highest

Whether to just get the highest value, instead of the sum

* @return

The group payout, or -1 if no groups has a set payout

*/ private double getGroupPayout(@NotNull Player player, boolean highest) { if (highest) { return getHighestGroupPayout(player); } else { return getSumGroupPayout(player); } } /** * Gets the max payout of the given player's permission groups * * @param player

The player to get the group payout for

* @return

The group payout, or -1 if no groups has a set payout

*/ private double getSumGroupPayout(@NotNull Player player) { double total = 0; for (String group : PermissionManager.getPlayerGroups(player)) { if (groupPayouts.containsKey(group)) { Double groupPayout = groupPayouts.get(group); if (groupPayout != null) { total += groupPayout; } } } return total; } /** * Gets the max payout of the given player's permission groups * * @param player

The player to get the group payout for

* @return

The group payout, or -1 if no groups has a set payout

*/ private double getHighestGroupPayout(@NotNull Player player) { double maxPay = 0; for (String group : PermissionManager.getPlayerGroups(player)) { if (groupPayouts.containsKey(group)) { Double groupPayout = groupPayouts.get(group); if (groupPayout != null) { maxPay = Math.max(maxPay, groupPayout); } } } return maxPay; } /** * The delay between each time a player is paid in minutes * * @return

The delay between payments

*/ public int getPayoutDelay() { return payoutDelay; } /** * The percentage of the payment to pay AFK players * * @return

AFK players' payment percentage

*/ public double getAfkPercentage() { return afkPercentage; } /** * Whether to display a message to players when they're paid * * @return

True if a payment message should be displayed

*/ public boolean displayPaymentMessage() { return displayPaymentMessage; } /** * Sets the payout given to a specific player * * @param playerId

The id of the player to set payout for

* @param payout

The payout to set for the player

*/ public void setPlayerPayout(@NotNull UUID playerId, @Nullable Double payout) { if (payout == null || payout < 0) { this.playerPayouts.put(playerId, null); } else { this.playerPayouts.put(playerId, payout); } this.save(); } /** * Sets the payout given to a specific group * * @param groupName

The name of the group to set payout for

* @param payout

The payout to set for the group

*/ public void setGroupPayout(@NotNull String groupName, @Nullable Double payout) { if (payout == null || payout < 0) { this.groupPayouts.put(groupName, null); } else { this.groupPayouts.put(groupName, payout); } this.save(); } /** * Gets all group payout overrides * * @return

All group payout overrides

*/ public @NotNull Map getGroupPayouts() { return groupPayouts; } /** * Gets a group payout override * * @param groupName

The group to get the override of

* @return

The overridden payout, or null if missing or cleared

*/ public @Nullable Double getGroupPayout(@NotNull String groupName) { return groupPayouts.get(groupName); } /** * Gets all player payout overrides * * @return

All player payout overrides

*/ public @NotNull Map getPlayerPayouts() { return playerPayouts; } /** * Gets a player payout override * * @param playerId

The id of the player to get the override of

* @return

The overridden payout, or null if missing or cleared

*/ public @Nullable Double getPlayerPayout(@NotNull UUID playerId) { return playerPayouts.get(playerId); } /** * Saves this configuration to disk */ public void save() { fileConfiguration.set(ConfigurationKey.PAYOUT_DELAY.getPath(), this.payoutDelay); fileConfiguration.setComments(ConfigurationKey.PAYOUT_DELAY.getPath(), List.of("The amount of minutes to wait between each payment")); fileConfiguration.set(ConfigurationKey.BONUS_MULTIPLIER.getPath(), this.bonusMultiplier); fileConfiguration.setComments(ConfigurationKey.BONUS_MULTIPLIER.getPath(), List.of("A multiplier used to increase or decrease the time bonus ((hours played / hours until " + "bonus) * bonusMultiplier) + payout")); fileConfiguration.set(ConfigurationKey.DEFAULT_PAYOUT.getPath(), this.defaultPayout); fileConfiguration.setComments(ConfigurationKey.DEFAULT_PAYOUT.getPath(), List.of("The default payout if the player has no overrides")); fileConfiguration.set(ConfigurationKey.DISPLAY_PAYMENT_MESSAGE.getPath(), this.displayPaymentMessage); fileConfiguration.setComments(ConfigurationKey.DISPLAY_PAYMENT_MESSAGE.getPath(), List.of("Whether to announce to a player that they've just been paid")); fileConfiguration.set(ConfigurationKey.AFK_PERCENTAGE.getPath(), this.afkPercentage); fileConfiguration.setComments(ConfigurationKey.AFK_PERCENTAGE.getPath(), List.of("The percentage of their normal payout to pay AFK players")); fileConfiguration.set(ConfigurationKey.PAYOUT_DELAY.getPath(), this.payoutDelay); fileConfiguration.setComments(ConfigurationKey.PAYOUT_DELAY.getPath(), List.of("The amount of minutes to wait between each payment")); fileConfiguration.set(ConfigurationKey.HOURS_UNTIL_BONUS.getPath(), this.hoursUntilBonus); fileConfiguration.setComments(ConfigurationKey.HOURS_UNTIL_BONUS.getPath(), List.of("The amount of hours until a bonus is given. Set to -1 to disable.")); for (Map.Entry playerPayout : this.playerPayouts.entrySet()) { fileConfiguration.set(ConfigurationKey.PLAYER_PAYOUTS.getPath() + "." + playerPayout.getKey().toString(), playerPayout.getValue()); } fileConfiguration.setComments(ConfigurationKey.PLAYER_PAYOUTS.getPath(), List.of("Overrides for specific players")); for (Map.Entry groupPayout : this.groupPayouts.entrySet()) { fileConfiguration.set(ConfigurationKey.GROUP_PAYOUTS.getPath() + "." + groupPayout.getKey(), groupPayout.getValue()); } fileConfiguration.setComments(ConfigurationKey.GROUP_PAYOUTS.getPath(), List.of("Overrides for specific groups")); fileConfiguration.setComments(ConfigurationKey.PAYOUT_RULES.getPath(), List.of("The rules for how the base payout " + "is calculated. \",\" = OR, \"+\" = AND. \"p\" or \"player\" is the override for a specific player. \"g\"", " or \"groups\" is the sum of all group overrides for a specific player. \"hg\" or \"HighestGroup\" " + "is the highest sum of all", " of a specific player's groups. \"b\" or \"base\" is the default" + " base payment.", "", "If you wanted to give players the sum of everything, you'd set it to " + "\"p+g+b\".", "If you wanted to give players the sum of their personal override and their " + "highest group, but fall back to the base", " pay, you'd set it to: \"p+hg,b\"", "If the payout" + " rule you set ends up giving 0 as the payment, it will fall back to the default of \"p,hg,b\"")); PlayerPayouts.getInstance().saveConfig(); // Remove any null values, as those aren't useful except for clearing values for (Map.Entry entry : groupPayouts.entrySet()) { if (entry.getValue() == null) { groupPayouts.remove(entry.getKey()); } } for (Map.Entry entry : playerPayouts.entrySet()) { if (entry.getValue() == null) { playerPayouts.remove(entry.getKey()); } } } /** * Calculates payment from the given payout component * * @param payoutComponent

The payout component to calculate from

* @param player

The player to calculate payment for

* @return

*/ private double calculatePayoutFromComponent(@NotNull PayoutComponent payoutComponent, @NotNull Player player) { if (payoutComponent instanceof PayoutTarget target) { // Get the value of the payout target return switch (target) { case BASE -> defaultPayout; case PLAYER -> playerPayouts.getOrDefault(player.getUniqueId(), 0d); case GROUPS -> getGroupPayout(player, false); case HIGHEST_GROUP -> getGroupPayout(player, true); }; } else if (payoutComponent instanceof PayoutAction action) { PayoutDelimiter delimiter = action.delimiter(); if (delimiter == PayoutDelimiter.ADD) { // Return the sum of the two components return calculatePayoutFromComponent(action.component1(), player) + calculatePayoutFromComponent(action.component2(), player); } else if (delimiter == PayoutDelimiter.OR) { // Get the result of the first component, or the second one if the first is zero double value = calculatePayoutFromComponent(action.component1(), player); if (value > 0) { return value; } else { return calculatePayoutFromComponent(action.component2(), player); } } else { throw new NotImplementedException("The encountered payout delimiter is not implemented. " + "Please inform the developer!"); } } else { throw new NotImplementedException("An unknown type of payout component was encountered." + "Please inform the developer!"); } } }