first commit

This commit is contained in:
2026-06-20 12:00:25 -04:00
commit af59d5beb5
14 changed files with 1039 additions and 0 deletions
View File
+59
View File
@@ -0,0 +1,59 @@
<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>com.bitnix</groupId>
<artifactId>DirtTaxes</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>DirtTaxes</name>
<description>Offline balance tax plugin for Paper 1.21.x</description>
<properties>
<java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>21</maven.compiler.release>
</properties>
<repositories>
<repository>
<id>papermc</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.21.1-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.MilkBowl</groupId>
<artifactId>VaultAPI</artifactId>
<version>1.7</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>DirtTaxes</finalName>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>21</release>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -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<Economy> 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<String> 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<String> 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<String> 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<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
if (args.length == 1) {
List<String> 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<String> 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<String> partial(String input, List<String> options) {
String lower = input.toLowerCase(Locale.ROOT);
List<String> matches = new ArrayList<>();
for (String option : options) {
if (option.toLowerCase(Locale.ROOT).startsWith(lower)) {
matches.add(option);
}
}
return matches;
}
}
@@ -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());
}
}
+132
View File
@@ -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 isnt 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 <percent>|setmaxdebt <negative>|setinterval <minutes>|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."
+29
View File
@@ -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
Binary file not shown.
+132
View File
@@ -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 isnt 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 <percent>|setmaxdebt <negative>|setinterval <minutes>|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."
+29
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
#Generated by Maven
#Sat Jun 20 11:12:19 EDT 2026
artifactId=DirtTaxes
groupId=com.bitnix
version=1.0.0
@@ -0,0 +1,2 @@
com/bitnix/dirttaxes/TaxJoinListener.class
com/bitnix/dirttaxes/DirtTaxesPlugin.class
@@ -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