commit af59d5beb51a03a620cf7215207e416f469a3487 Author: Xelara Networks Date: Sat Jun 20 12:00:25 2026 -0400 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..7f0f19f --- /dev/null +++ b/pom.xml @@ -0,0 +1,59 @@ + + 4.0.0 + + com.bitnix + DirtTaxes + 1.0.0 + jar + + DirtTaxes + Offline balance tax plugin for Paper 1.21.x + + + 21 + UTF-8 + 21 + + + + + papermc + https://repo.papermc.io/repository/maven-public/ + + + jitpack.io + https://jitpack.io + + + + + + io.papermc.paper + paper-api + 1.21.1-R0.1-SNAPSHOT + provided + + + + com.github.MilkBowl + VaultAPI + 1.7 + provided + + + + + DirtTaxes + + + maven-compiler-plugin + 3.13.0 + + 21 + + + + + diff --git a/src/main/java/com/bitnix/dirttaxes/DirtTaxesPlugin.java b/src/main/java/com/bitnix/dirttaxes/DirtTaxesPlugin.java new file mode 100644 index 0000000..f03b4c6 --- /dev/null +++ b/src/main/java/com/bitnix/dirttaxes/DirtTaxesPlugin.java @@ -0,0 +1,630 @@ +package com.bitnix.dirttaxes; + +import net.milkbowl.vault.economy.Economy; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.command.TabExecutor; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.plugin.RegisteredServiceProvider; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitTask; + +import java.io.File; +import java.io.IOException; +import java.text.DecimalFormat; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Random; +import java.util.UUID; + +public class DirtTaxesPlugin extends JavaPlugin implements TabExecutor { + + private Economy economy; + private BukkitTask taxTask; + private File pendingFile; + private FileConfiguration pendingConfig; + private final Random random = new Random(); + private final DecimalFormat moneyFormat = new DecimalFormat("0.00"); + + @Override + public void onEnable() { + saveDefaultConfig(); + createDataFiles(); + + if (!setupEconomy()) { + getLogger().severe("Vault economy provider not found. Disabling DirtTaxes."); + getServer().getPluginManager().disablePlugin(this); + return; + } + + PluginCommand command = getCommand("dirttaxes"); + if (command != null) { + command.setExecutor(this); + command.setTabCompleter(this); + } + + scheduleTaxTask(); + registerJoinListener(); + + if (getConfig().getBoolean("tax.startup-tax-check", false)) { + long delayTicks = Math.max(20L, getConfig().getLong("tax.startup-tax-delay-seconds", 30L) * 20L); + Bukkit.getScheduler().runTaskLater(this, this::runOfflineTaxCycle, delayTicks); + } + + getLogger().info("DirtTaxes enabled."); + } + + @Override + public void onDisable() { + if (taxTask != null) { + taxTask.cancel(); + taxTask = null; + } + + Bukkit.getScheduler().cancelTasks(this); + savePendingMessages(); + } + + private void createDataFiles() { + if (!getDataFolder().exists()) { + getDataFolder().mkdirs(); + } + + pendingFile = new File(getDataFolder(), "pending_messages.yml"); + if (!pendingFile.exists()) { + try { + pendingFile.createNewFile(); + } catch (IOException e) { + getLogger().severe("Could not create pending_messages.yml"); + e.printStackTrace(); + } + } + + pendingConfig = YamlConfiguration.loadConfiguration(pendingFile); + } + + private boolean setupEconomy() { + RegisteredServiceProvider rsp = getServer().getServicesManager().getRegistration(Economy.class); + if (rsp == null) { + return false; + } + economy = rsp.getProvider(); + return economy != null; + } + + private void scheduleTaxTask() { + if (taxTask != null) { + taxTask.cancel(); + taxTask = null; + } + + long intervalMinutes = Math.max(1L, getConfig().getLong("tax.interval-minutes", 1440L)); + long intervalTicks = intervalMinutes * 60L * 20L; + + taxTask = Bukkit.getScheduler().runTaskTimer(this, this::runOfflineTaxCycle, intervalTicks, intervalTicks); + } + + private void registerJoinListener() { + getServer().getPluginManager().registerEvents(new TaxJoinListener(this), this); + } + + public void handleJoin(Player player) { + String path = "players." + player.getUniqueId(); + if (!pendingConfig.contains(path)) { + return; + } + + List messages = pendingConfig.getStringList(path + ".messages"); + for (String message : messages) { + player.sendMessage(color(message)); + } + + pendingConfig.set(path, null); + savePendingMessages(); + } + + private void runOfflineTaxCycle() { + if (!getConfig().getBoolean("tax.enabled", true)) { + return; + } + + long requiredOfflineMinutes = Math.max(0L, getConfig().getLong("tax.offline-minutes-required", 1440L)); + long requiredOfflineMillis = requiredOfflineMinutes * 60L * 1000L; + + for (OfflinePlayer offlinePlayer : Bukkit.getOfflinePlayers()) { + if (offlinePlayer == null) { + continue; + } + + if (offlinePlayer.isOnline()) { + continue; + } + + if (offlinePlayer.getName() == null) { + continue; + } + + long lastPlayed = offlinePlayer.getLastPlayed(); + if (lastPlayed <= 0L) { + continue; + } + + long offlineFor = Instant.now().toEpochMilli() - lastPlayed; + if (offlineFor < requiredOfflineMillis) { + continue; + } + + taxSinglePlayer(offlinePlayer, true); + } + + if (getConfig().getBoolean("storage.save-pending-messages", true)) { + savePendingMessages(); + } + } + + private boolean taxSinglePlayer(OfflinePlayer offlinePlayer, boolean automatic) { + if (offlinePlayer == null || offlinePlayer.getName() == null) { + return false; + } + + if (getConfig().getBoolean("tax.permission-exempt", true) && offlinePlayer.isOnline()) { + Player player = offlinePlayer.getPlayer(); + if (player != null && player.hasPermission("dirttaxes.exempt")) { + return false; + } + } + + if (getConfig().getBoolean("tax.op-exempt", true) && offlinePlayer.isOp()) { + return false; + } + + double balance = economy.getBalance(offlinePlayer); + double oldBalance = balance; + double newBalance = balance; + double amountTaken = 0.0; + + double maxDebt = getConfig().getDouble("tax.max-debt", -1000000.0); + if (maxDebt > 0) { + maxDebt = 0; + } + + if (balance > 0) { + double ratePercent = Math.max(0.0, getConfig().getDouble("tax.rate-percent", 5.0)); + double minimumTax = Math.max(0.0, getConfig().getDouble("tax.minimum-tax", 1.0)); + amountTaken = Math.max(minimumTax, balance * (ratePercent / 100.0)); + + if (amountTaken > balance) { + amountTaken = balance; + } + + if (getConfig().getBoolean("tax.round-to-cents", true)) { + amountTaken = roundMoney(amountTaken); + } + + if (amountTaken <= 0.0) { + return false; + } + + economy.withdrawPlayer(offlinePlayer, amountTaken); + newBalance = economy.getBalance(offlinePlayer); + + if (sameMoney(oldBalance, newBalance)) { + return false; + } + + amountTaken = Math.abs(oldBalance - newBalance); + } else if (getConfig().getBoolean("debt.enabled", true)) { + double interestPercent = Math.max(0.0, getConfig().getDouble("debt.negative-interest-percent", 2.5)); + double flatDebt = Math.max(0.0, getConfig().getDouble("debt.flat-debt-per-cycle", 500.0)); + + double debtIncrease = Math.abs(balance) * (interestPercent / 100.0) + flatDebt; + if (getConfig().getBoolean("tax.round-to-cents", true)) { + debtIncrease = roundMoney(debtIncrease); + } + + if (debtIncrease <= 0.0) { + return false; + } + + double targetBalance = balance - debtIncrease; + if (targetBalance < maxDebt) { + targetBalance = maxDebt; + } + + amountTaken = Math.abs(targetBalance - balance); + if (amountTaken <= 0.0) { + return false; + } + + boolean usedCommand = getConfig().getBoolean("debt.use-command-for-debt", true); + + if (usedCommand) { + runDebtCommand(offlinePlayer, amountTaken, balance, targetBalance); + newBalance = economy.getBalance(offlinePlayer); + + if (sameMoney(oldBalance, newBalance)) { + if (getConfig().getBoolean("debt.warn-if-command-fails", true)) { + String fail = prefixed("messages.debt-command-failed").replace("%player%", offlinePlayer.getName()); + Bukkit.getConsoleSender().sendMessage(fail); + for (Player online : Bukkit.getOnlinePlayers()) { + if (online.hasPermission("dirttaxes.notify")) { + online.sendMessage(fail); + } + } + } + return false; + } + + amountTaken = Math.abs(oldBalance - newBalance); + } else { + economy.withdrawPlayer(offlinePlayer, amountTaken); + newBalance = economy.getBalance(offlinePlayer); + + if (sameMoney(oldBalance, newBalance)) { + return false; + } + + amountTaken = Math.abs(oldBalance - newBalance); + } + } else { + return false; + } + + if (getConfig().getBoolean("announcement.enabled", true)) { + announceTax(offlinePlayer, amountTaken, newBalance); + } + + if (automatic && getConfig().getBoolean("announcement.store-offline-message", true)) { + storeJoinMessages(offlinePlayer.getUniqueId(), amountTaken, newBalance); + } + + if (newBalance < 0 && getConfig().getBoolean("debt.announce-debt", true)) { + String debtMsg = getConfig().getString("debt.debt-message", "&4IRS &8- &c%player% is now drowning in debt. Balance: $%new_balance%"); + debtMsg = debtMsg.replace("%player%", offlinePlayer.getName()) + .replace("%new_balance%", formatMoney(newBalance)); + broadcastToStaffAndConsole(color(debtMsg)); + } + + return true; + } + + private void runDebtCommand(OfflinePlayer offlinePlayer, double amountTaken, double oldBalance, double targetBalance) { + String raw = getConfig().getString("debt.command", "cmi money set %player% %new_balance%"); + if (raw == null || raw.isBlank()) { + return; + } + + String command = raw + .replace("%player%", offlinePlayer.getName()) + .replace("%amount%", formatMoney(amountTaken)) + .replace("%old_balance%", formatMoney(oldBalance)) + .replace("%new_balance%", formatMoney(targetBalance)); + + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command); + } + + private void announceTax(OfflinePlayer offlinePlayer, double amountTaken, double newBalance) { + String message = getConfig().getString("announcement.staff-message", + "&6IRS &8- &eTook &c$%amount% &efrom &b%player%&e. New balance: &a$%new_balance%"); + + message = message.replace("%amount%", formatMoney(amountTaken)) + .replace("%player%", offlinePlayer.getName()) + .replace("%new_balance%", formatMoney(newBalance)); + + broadcastToStaffAndConsole(color(message)); + + if (getConfig().getBoolean("fun.enabled", true) + && getConfig().getBoolean("fun.include-funny-line-in-staff-alerts", true) + && getConfig().getBoolean("fun.random-funny-lines", true)) { + String funny = getRandomFunnyLine(); + if (funny != null && !funny.isBlank()) { + broadcastToStaffAndConsole(color(funny)); + } + } + } + + private void broadcastToStaffAndConsole(String message) { + if (getConfig().getBoolean("announcement.notify-online-staff", true)) { + for (Player player : Bukkit.getOnlinePlayers()) { + if (player.hasPermission("dirttaxes.notify")) { + player.sendMessage(message); + } + } + } + + if (getConfig().getBoolean("announcement.log-to-console", true)) { + Bukkit.getConsoleSender().sendMessage(message); + } + } + + private void storeJoinMessages(UUID uuid, double amountTaken, double newBalance) { + String path = "players." + uuid + ".messages"; + List messages = pendingConfig.getStringList(path); + + String joinMessage = getConfig().getString("announcement.join-message", + "&6IRS &8- &eTook &c$%amount% &efrom your account while you were away. New balance: &a$%new_balance%"); + joinMessage = joinMessage.replace("%amount%", formatMoney(amountTaken)) + .replace("%new_balance%", formatMoney(newBalance)); + + messages.add(joinMessage); + + if (getConfig().getBoolean("fun.enabled", true) + && getConfig().getBoolean("fun.send-funny-line-to-player", true) + && getConfig().getBoolean("fun.random-funny-lines", true)) { + String funny = getRandomFunnyLine(); + if (funny != null && !funny.isBlank()) { + messages.add(funny); + } + } + + pendingConfig.set(path, messages); + } + + private String getRandomFunnyLine() { + List lines = getConfig().getStringList("fun.funny-lines"); + if (lines.isEmpty()) { + return null; + } + return lines.get(random.nextInt(lines.size())); + } + + private void savePendingMessages() { + if (pendingConfig == null || pendingFile == null) { + return; + } + + try { + pendingConfig.save(pendingFile); + } catch (IOException e) { + getLogger().severe("Failed to save pending_messages.yml"); + e.printStackTrace(); + } + } + + private double roundMoney(double amount) { + return Math.round(amount * 100.0D) / 100.0D; + } + + private boolean sameMoney(double a, double b) { + return Math.abs(a - b) < 0.009D; + } + + private String formatMoney(double amount) { + return moneyFormat.format(amount); + } + + private String color(String text) { + return ChatColor.translateAlternateColorCodes('&', text); + } + + private String msg(String path) { + return color(getConfig().getString(path, path)); + } + + private String prefixed(String path) { + return msg("messages.prefix") + msg(path); + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (args.length == 0) { + if (!sender.hasPermission("dirttaxes.use")) { + sender.sendMessage(prefixed("messages.no-permission")); + return true; + } + + String info = msg("messages.info") + .replace("%rate%", String.valueOf(getConfig().getDouble("tax.rate-percent", 5.0))) + .replace("%interval%", String.valueOf(getConfig().getLong("tax.interval-minutes", 1440L))) + .replace("%maxdebt%", formatMoney(getConfig().getDouble("tax.max-debt", -1000000.0))); + sender.sendMessage(msg("messages.prefix") + info); + sender.sendMessage(msg("messages.prefix") + msg("messages.usage")); + return true; + } + + String sub = args[0].toLowerCase(Locale.ROOT); + + switch (sub) { + case "reload" -> { + if (!sender.hasPermission("dirttaxes.reload")) { + sender.sendMessage(prefixed("messages.no-permission")); + return true; + } + + reloadConfig(); + scheduleTaxTask(); + sender.sendMessage(prefixed("messages.reloaded")); + return true; + } + + case "info" -> { + if (!sender.hasPermission("dirttaxes.use")) { + sender.sendMessage(prefixed("messages.no-permission")); + return true; + } + + String info = msg("messages.info") + .replace("%rate%", String.valueOf(getConfig().getDouble("tax.rate-percent", 5.0))) + .replace("%interval%", String.valueOf(getConfig().getLong("tax.interval-minutes", 1440L))) + .replace("%maxdebt%", formatMoney(getConfig().getDouble("tax.max-debt", -1000000.0))); + sender.sendMessage(msg("messages.prefix") + info); + return true; + } + + case "setrate" -> { + if (!sender.hasPermission("dirttaxes.setrate")) { + sender.sendMessage(prefixed("messages.no-permission")); + return true; + } + + if (args.length < 2) { + sender.sendMessage(prefixed("messages.usage")); + return true; + } + + Double value = parseDouble(args[1]); + if (value == null) { + sender.sendMessage(prefixed("messages.invalid-number")); + return true; + } + + if (value < 0) { + sender.sendMessage(prefixed("messages.rate-must-be-nonnegative")); + return true; + } + + getConfig().set("tax.rate-percent", value); + saveConfig(); + sender.sendMessage(prefixed("messages.set-rate-success").replace("%rate%", String.valueOf(value))); + return true; + } + + case "setmaxdebt" -> { + if (!sender.hasPermission("dirttaxes.setmaxdebt")) { + sender.sendMessage(prefixed("messages.no-permission")); + return true; + } + + if (args.length < 2) { + sender.sendMessage(prefixed("messages.usage")); + return true; + } + + Double value = parseDouble(args[1]); + if (value == null) { + sender.sendMessage(prefixed("messages.invalid-number")); + return true; + } + + if (value > 0) { + sender.sendMessage(prefixed("messages.max-debt-must-be-negative")); + return true; + } + + getConfig().set("tax.max-debt", value); + saveConfig(); + sender.sendMessage(prefixed("messages.set-max-debt-success").replace("%maxdebt%", formatMoney(value))); + return true; + } + + case "setinterval" -> { + if (!sender.hasPermission("dirttaxes.setinterval")) { + sender.sendMessage(prefixed("messages.no-permission")); + return true; + } + + if (args.length < 2) { + sender.sendMessage(prefixed("messages.usage")); + return true; + } + + Long value = parseLong(args[1]); + if (value == null) { + sender.sendMessage(prefixed("messages.invalid-number")); + return true; + } + + if (value <= 0) { + sender.sendMessage(prefixed("messages.interval-must-be-positive")); + return true; + } + + getConfig().set("tax.interval-minutes", value); + saveConfig(); + scheduleTaxTask(); + sender.sendMessage(prefixed("messages.set-interval-success").replace("%interval%", String.valueOf(value))); + return true; + } + + case "forcetax" -> { + if (!sender.hasPermission("dirttaxes.forcetax")) { + sender.sendMessage(prefixed("messages.no-permission")); + return true; + } + + if (args.length >= 2) { + OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]); + if (target == null || target.getName() == null) { + sender.sendMessage(prefixed("messages.player-not-found")); + return true; + } + + taxSinglePlayer(target, true); + sender.sendMessage(prefixed("messages.force-tax-player-success").replace("%player%", target.getName())); + return true; + } + + runOfflineTaxCycle(); + sender.sendMessage(prefixed("messages.force-tax-all-success")); + return true; + } + + default -> { + sender.sendMessage(prefixed("messages.usage")); + return true; + } + } + } + + private Double parseDouble(String input) { + try { + return Double.parseDouble(input); + } catch (NumberFormatException ignored) { + return null; + } + } + + private Long parseLong(String input) { + try { + return Long.parseLong(input); + } catch (NumberFormatException ignored) { + return null; + } + } + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + if (args.length == 1) { + List subs = new ArrayList<>(); + subs.add("reload"); + subs.add("info"); + subs.add("setrate"); + subs.add("setmaxdebt"); + subs.add("setinterval"); + subs.add("forcetax"); + return partial(args[0], subs); + } + + if (args.length == 2 && args[0].equalsIgnoreCase("forcetax")) { + List names = new ArrayList<>(); + for (OfflinePlayer player : Bukkit.getOfflinePlayers()) { + if (player.getName() != null) { + names.add(player.getName()); + } + } + return partial(args[1], names); + } + + return Collections.emptyList(); + } + + private List partial(String input, List options) { + String lower = input.toLowerCase(Locale.ROOT); + List matches = new ArrayList<>(); + for (String option : options) { + if (option.toLowerCase(Locale.ROOT).startsWith(lower)) { + matches.add(option); + } + } + return matches; + } +} diff --git a/src/main/java/com/bitnix/dirttaxes/TaxJoinListener.java b/src/main/java/com/bitnix/dirttaxes/TaxJoinListener.java new file mode 100644 index 0000000..2360510 --- /dev/null +++ b/src/main/java/com/bitnix/dirttaxes/TaxJoinListener.java @@ -0,0 +1,19 @@ +package com.bitnix.dirttaxes; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; + +public class TaxJoinListener implements Listener { + + private final DirtTaxesPlugin plugin; + + public TaxJoinListener(DirtTaxesPlugin plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onJoin(PlayerJoinEvent event) { + plugin.handleJoin(event.getPlayer()); + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..d400008 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,132 @@ +tax: + enabled: true + + # How often offline players are taxed, in minutes. + # 1440 = once per day + interval-minutes: 1440 + + # Percentage of current balance removed each tax cycle. + # 5.0 = 5% + rate-percent: 5.0 + + # Minimum tax amount if balance is above 0 + minimum-tax: 1.0 + + # Maximum debt allowed. Once a player reaches this negative value, + # no more debt is added. + max-debt: -1000000.0 + + # Players offline at least this many minutes before they can be taxed + offline-minutes-required: 1440 + + # If true, tax all offline players on plugin startup after delay + startup-tax-check: false + + # Delay in seconds before startup tax check runs + startup-tax-delay-seconds: 30 + + # If true, players with exempt permission bypass taxes + permission-exempt: true + + # If true, OP players bypass taxes + op-exempt: true + + # If true, tax amount is rounded to 2 decimals + round-to-cents: true + +announcement: + enabled: true + + # Broadcast to online players with dirttaxes.notify + notify-online-staff: true + + # Also send to console + log-to-console: true + + # Message for staff/console announcements + staff-message: "&6IRS &8- &eTook &c$%amount% &efrom &b%player%&e. New balance: &a$%new_balance%" + + # Optional message sent to the player next time they join + store-offline-message: true + join-message: "&6IRS &8- &eTook &c$%amount% &efrom your account while you were away. New balance: &a$%new_balance%" + +debt: + enabled: true + + # If player balance is 0 or below, add this flat debt per cycle + flat-debt-per-cycle: 500.0 + + # Funny extra interest percent applied to negative balances + # Example: -1000 with 2.5% becomes -1025 before flat debt + negative-interest-percent: 2.5 + + # IMPORTANT: + # When true, negative/debt taxation uses a configurable console command + # instead of Vault withdrawPlayer(). + # + # Use this for economies like CMI where Vault may not push balances below 0. + use-command-for-debt: true + + # Console command used to apply debt. + # Placeholders: + # %player% = player name + # %amount% = amount being added as debt this cycle + # %old_balance% = previous balance + # %new_balance% = expected new balance after tax + # + # Examples you can try on your server: + # command: "cmi money take %player% %amount%" + # command: "money take %player% %amount%" + # command: "cmi money set %player% %new_balance%" + command: "cmi money set %player% %new_balance%" + + # If true, announce/log if command mode fails to actually change balance + warn-if-command-fails: true + + # Broadcast when someone falls deeper into debt + announce-debt: true + debt-message: "&4IRS &8- &c%player% is now drowning in debt. Balance: $%new_balance%" + +fun: + enabled: true + + # Send a random funny IRS line to the player on join if they were taxed offline + send-funny-line-to-player: true + + # Also include funny line in staff alerts + include-funny-line-in-staff-alerts: true + + random-funny-lines: true + funny-lines: + - "&7[IRS] Asset seizure successful." + - "&7[IRS] Another loyal citizen has contributed." + - "&7[IRS] Freedom isn’t free." + - "&7[IRS] Thank you for your involuntary donation." + - "&7[IRS] The tax goblin smiles." + - "&7[IRS] Audit complete. Wallet lighter." + - "&7[IRS] Your wallet has been selected for routine maintenance." + - "&7[IRS] We noticed you looked too comfortable." + - "&7[IRS] This concludes your surprise patriotism fee." + - "&7[IRS] You were fined for excessive wealth retention." + +storage: + # Save pending join notifications to disk every tax cycle + save-pending-messages: true + +messages: + prefix: "&6[DirtTaxes] &r" + no-permission: "&cYou do not have permission." + reloaded: "&aDirtTaxes reloaded." + usage: "&e/dirttaxes reload|info|setrate |setmaxdebt |setinterval |forcetax [player]" + info: "&eTax rate: &f%rate%% &8| &eInterval: &f%interval%m &8| &eMax debt: &f$%maxdebt%" + set-rate-success: "&aTax rate set to &f%rate%%" + set-max-debt-success: "&aMax debt set to &f$%maxdebt%" + set-interval-success: "&aTax interval set to &f%interval% minutes" + force-tax-all-success: "&aForced tax cycle for all offline players." + force-tax-player-success: "&aForced tax for &f%player%" + player-not-found: "&cPlayer not found." + invalid-number: "&cInvalid number." + max-debt-must-be-negative: "&cMax debt must be 0 or less." + interval-must-be-positive: "&cInterval must be greater than 0." + rate-must-be-nonnegative: "&cTax rate must be 0 or greater." + debt-command-failed: "&cDebt command did not appear to change %player%'s balance." diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..13e26d8 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,29 @@ +name: DirtTaxes +version: 1.0.0 +main: com.bitnix.dirttaxes.DirtTaxesPlugin +api-version: '1.21' +depend: [Vault] + +commands: + dirttaxes: + description: Main DirtTaxes command + usage: /dirttaxes + aliases: [taxes, irs] + +permissions: + dirttaxes.use: + default: op + dirttaxes.reload: + default: op + dirttaxes.setrate: + default: op + dirttaxes.setmaxdebt: + default: op + dirttaxes.setinterval: + default: op + dirttaxes.forcetax: + default: op + dirttaxes.exempt: + default: false + dirttaxes.notify: + default: op diff --git a/target/DirtTaxes.jar b/target/DirtTaxes.jar new file mode 100644 index 0000000..dd5093e Binary files /dev/null and b/target/DirtTaxes.jar differ diff --git a/target/classes/com/bitnix/dirttaxes/DirtTaxesPlugin.class b/target/classes/com/bitnix/dirttaxes/DirtTaxesPlugin.class new file mode 100644 index 0000000..b7f1248 Binary files /dev/null and b/target/classes/com/bitnix/dirttaxes/DirtTaxesPlugin.class differ diff --git a/target/classes/com/bitnix/dirttaxes/TaxJoinListener.class b/target/classes/com/bitnix/dirttaxes/TaxJoinListener.class new file mode 100644 index 0000000..26661ad Binary files /dev/null and b/target/classes/com/bitnix/dirttaxes/TaxJoinListener.class differ diff --git a/target/classes/config.yml b/target/classes/config.yml new file mode 100644 index 0000000..d400008 --- /dev/null +++ b/target/classes/config.yml @@ -0,0 +1,132 @@ +tax: + enabled: true + + # How often offline players are taxed, in minutes. + # 1440 = once per day + interval-minutes: 1440 + + # Percentage of current balance removed each tax cycle. + # 5.0 = 5% + rate-percent: 5.0 + + # Minimum tax amount if balance is above 0 + minimum-tax: 1.0 + + # Maximum debt allowed. Once a player reaches this negative value, + # no more debt is added. + max-debt: -1000000.0 + + # Players offline at least this many minutes before they can be taxed + offline-minutes-required: 1440 + + # If true, tax all offline players on plugin startup after delay + startup-tax-check: false + + # Delay in seconds before startup tax check runs + startup-tax-delay-seconds: 30 + + # If true, players with exempt permission bypass taxes + permission-exempt: true + + # If true, OP players bypass taxes + op-exempt: true + + # If true, tax amount is rounded to 2 decimals + round-to-cents: true + +announcement: + enabled: true + + # Broadcast to online players with dirttaxes.notify + notify-online-staff: true + + # Also send to console + log-to-console: true + + # Message for staff/console announcements + staff-message: "&6IRS &8- &eTook &c$%amount% &efrom &b%player%&e. New balance: &a$%new_balance%" + + # Optional message sent to the player next time they join + store-offline-message: true + join-message: "&6IRS &8- &eTook &c$%amount% &efrom your account while you were away. New balance: &a$%new_balance%" + +debt: + enabled: true + + # If player balance is 0 or below, add this flat debt per cycle + flat-debt-per-cycle: 500.0 + + # Funny extra interest percent applied to negative balances + # Example: -1000 with 2.5% becomes -1025 before flat debt + negative-interest-percent: 2.5 + + # IMPORTANT: + # When true, negative/debt taxation uses a configurable console command + # instead of Vault withdrawPlayer(). + # + # Use this for economies like CMI where Vault may not push balances below 0. + use-command-for-debt: true + + # Console command used to apply debt. + # Placeholders: + # %player% = player name + # %amount% = amount being added as debt this cycle + # %old_balance% = previous balance + # %new_balance% = expected new balance after tax + # + # Examples you can try on your server: + # command: "cmi money take %player% %amount%" + # command: "money take %player% %amount%" + # command: "cmi money set %player% %new_balance%" + command: "cmi money set %player% %new_balance%" + + # If true, announce/log if command mode fails to actually change balance + warn-if-command-fails: true + + # Broadcast when someone falls deeper into debt + announce-debt: true + debt-message: "&4IRS &8- &c%player% is now drowning in debt. Balance: $%new_balance%" + +fun: + enabled: true + + # Send a random funny IRS line to the player on join if they were taxed offline + send-funny-line-to-player: true + + # Also include funny line in staff alerts + include-funny-line-in-staff-alerts: true + + random-funny-lines: true + funny-lines: + - "&7[IRS] Asset seizure successful." + - "&7[IRS] Another loyal citizen has contributed." + - "&7[IRS] Freedom isn’t free." + - "&7[IRS] Thank you for your involuntary donation." + - "&7[IRS] The tax goblin smiles." + - "&7[IRS] Audit complete. Wallet lighter." + - "&7[IRS] Your wallet has been selected for routine maintenance." + - "&7[IRS] We noticed you looked too comfortable." + - "&7[IRS] This concludes your surprise patriotism fee." + - "&7[IRS] You were fined for excessive wealth retention." + +storage: + # Save pending join notifications to disk every tax cycle + save-pending-messages: true + +messages: + prefix: "&6[DirtTaxes] &r" + no-permission: "&cYou do not have permission." + reloaded: "&aDirtTaxes reloaded." + usage: "&e/dirttaxes reload|info|setrate |setmaxdebt |setinterval |forcetax [player]" + info: "&eTax rate: &f%rate%% &8| &eInterval: &f%interval%m &8| &eMax debt: &f$%maxdebt%" + set-rate-success: "&aTax rate set to &f%rate%%" + set-max-debt-success: "&aMax debt set to &f$%maxdebt%" + set-interval-success: "&aTax interval set to &f%interval% minutes" + force-tax-all-success: "&aForced tax cycle for all offline players." + force-tax-player-success: "&aForced tax for &f%player%" + player-not-found: "&cPlayer not found." + invalid-number: "&cInvalid number." + max-debt-must-be-negative: "&cMax debt must be 0 or less." + interval-must-be-positive: "&cInterval must be greater than 0." + rate-must-be-nonnegative: "&cTax rate must be 0 or greater." + debt-command-failed: "&cDebt command did not appear to change %player%'s balance." diff --git a/target/classes/plugin.yml b/target/classes/plugin.yml new file mode 100644 index 0000000..13e26d8 --- /dev/null +++ b/target/classes/plugin.yml @@ -0,0 +1,29 @@ +name: DirtTaxes +version: 1.0.0 +main: com.bitnix.dirttaxes.DirtTaxesPlugin +api-version: '1.21' +depend: [Vault] + +commands: + dirttaxes: + description: Main DirtTaxes command + usage: /dirttaxes + aliases: [taxes, irs] + +permissions: + dirttaxes.use: + default: op + dirttaxes.reload: + default: op + dirttaxes.setrate: + default: op + dirttaxes.setmaxdebt: + default: op + dirttaxes.setinterval: + default: op + dirttaxes.forcetax: + default: op + dirttaxes.exempt: + default: false + dirttaxes.notify: + default: op diff --git a/target/maven-archiver/pom.properties b/target/maven-archiver/pom.properties new file mode 100644 index 0000000..05fe83b --- /dev/null +++ b/target/maven-archiver/pom.properties @@ -0,0 +1,5 @@ +#Generated by Maven +#Sat Jun 20 11:12:19 EDT 2026 +artifactId=DirtTaxes +groupId=com.bitnix +version=1.0.0 diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..6ba8c26 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,2 @@ +com/bitnix/dirttaxes/TaxJoinListener.class +com/bitnix/dirttaxes/DirtTaxesPlugin.class diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..e184c55 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,2 @@ +/home/bitnix/Desktop/DirtTaxes/src/main/java/com/bitnix/dirttaxes/DirtTaxesPlugin.java +/home/bitnix/Desktop/DirtTaxes/src/main/java/com/bitnix/dirttaxes/TaxJoinListener.java