first commit
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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 <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."
|
||||
@@ -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.
Binary file not shown.
Binary file not shown.
@@ -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 <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."
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user