Add Coal Cents economy
This commit is contained in:
251
CoalGov/src/main/java/com/librewiki/coalgov/CoalGovPlugin.java
Normal file
251
CoalGov/src/main/java/com/librewiki/coalgov/CoalGovPlugin.java
Normal file
@@ -0,0 +1,251 @@
|
||||
package com.librewiki.coalgov;
|
||||
|
||||
import com.librewiki.coalgov.command.AdminCommand;
|
||||
import com.librewiki.coalgov.command.ClaimCommand;
|
||||
import com.librewiki.coalgov.command.CoalCommand;
|
||||
import com.librewiki.coalgov.command.DiviningRodCommand;
|
||||
import com.librewiki.coalgov.command.LandCommand;
|
||||
import com.librewiki.coalgov.command.MarketCommand;
|
||||
import com.librewiki.coalgov.command.NpcCommand;
|
||||
import com.librewiki.coalgov.command.PermitCommand;
|
||||
import com.librewiki.coalgov.command.PoliceCommand;
|
||||
import com.librewiki.coalgov.listener.BlockBreakListener;
|
||||
import com.librewiki.coalgov.listener.BlockPlaceListener;
|
||||
import com.librewiki.coalgov.listener.ClaimWandListener;
|
||||
import com.librewiki.coalgov.listener.DiviningRodListener;
|
||||
import com.librewiki.coalgov.listener.MarketListener;
|
||||
import com.librewiki.coalgov.listener.NpcListener;
|
||||
import com.librewiki.coalgov.listener.PlayerJoinListener;
|
||||
import com.librewiki.coalgov.listener.SuperFurnaceListener;
|
||||
import com.librewiki.coalgov.service.BuildProtectionService;
|
||||
import com.librewiki.coalgov.service.ClaimMarkerService;
|
||||
import com.librewiki.coalgov.service.ClaimSelectionService;
|
||||
import com.librewiki.coalgov.service.ClaimService;
|
||||
import com.librewiki.coalgov.service.ClaimVisualService;
|
||||
import com.librewiki.coalgov.service.DiviningRodService;
|
||||
import com.librewiki.coalgov.service.EconomyService;
|
||||
import com.librewiki.coalgov.service.GovernmentService;
|
||||
import com.librewiki.coalgov.service.LandService;
|
||||
import com.librewiki.coalgov.service.LandAppraisalService;
|
||||
import com.librewiki.coalgov.service.MarketService;
|
||||
import com.librewiki.coalgov.service.MiningService;
|
||||
import com.librewiki.coalgov.service.NpcService;
|
||||
import com.librewiki.coalgov.service.OpenRouterService;
|
||||
import com.librewiki.coalgov.service.PermitService;
|
||||
import com.librewiki.coalgov.service.SpawnGuideService;
|
||||
import com.librewiki.coalgov.service.SuperFurnaceService;
|
||||
import com.librewiki.coalgov.service.TaxService;
|
||||
import com.librewiki.coalgov.storage.ClaimRepository;
|
||||
import com.librewiki.coalgov.storage.Database;
|
||||
import com.librewiki.coalgov.storage.EconomyRepository;
|
||||
import com.librewiki.coalgov.storage.GovernmentRepository;
|
||||
import com.librewiki.coalgov.storage.LandRepository;
|
||||
import com.librewiki.coalgov.storage.MarketRepository;
|
||||
import com.librewiki.coalgov.storage.NpcRepository;
|
||||
import com.librewiki.coalgov.storage.PermitRepository;
|
||||
import com.librewiki.coalgov.storage.SuperFurnaceRepository;
|
||||
import com.librewiki.coalgov.util.CoalMoney;
|
||||
import com.librewiki.coalgov.util.Messages;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class CoalGovPlugin extends JavaPlugin {
|
||||
private Database database;
|
||||
private EconomyService economyService;
|
||||
private GovernmentService governmentService;
|
||||
private ClaimService claimService;
|
||||
private ClaimMarkerService claimMarkerService;
|
||||
private ClaimSelectionService claimSelectionService;
|
||||
private ClaimVisualService claimVisualService;
|
||||
private BuildProtectionService buildProtectionService;
|
||||
private LandAppraisalService landAppraisalService;
|
||||
private MarketService marketService;
|
||||
private NpcService npcService;
|
||||
private OpenRouterService openRouterService;
|
||||
private LandService landService;
|
||||
private PermitService permitService;
|
||||
private MiningService miningService;
|
||||
private DiviningRodService diviningRodService;
|
||||
private SuperFurnaceService superFurnaceService;
|
||||
private SpawnGuideService spawnGuideService;
|
||||
private TaxService taxService;
|
||||
private Messages messages;
|
||||
private final Set<UUID> adminBypassPlayers = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
saveDefaultConfig();
|
||||
messages = new Messages(getConfig());
|
||||
database = new Database(getDataFolder());
|
||||
try {
|
||||
database.open();
|
||||
} catch (SQLException exception) {
|
||||
getLogger().severe("Failed to initialize SQLite database: " + exception.getMessage());
|
||||
getServer().getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
|
||||
EconomyRepository economyRepository = new EconomyRepository(database);
|
||||
LandRepository landRepository = new LandRepository(database);
|
||||
ClaimRepository claimRepository = new ClaimRepository(database);
|
||||
PermitRepository permitRepository = new PermitRepository(database);
|
||||
MarketRepository marketRepository = new MarketRepository(database);
|
||||
GovernmentRepository governmentRepository = new GovernmentRepository(database);
|
||||
SuperFurnaceRepository superFurnaceRepository = new SuperFurnaceRepository(database);
|
||||
NpcRepository npcRepository = new NpcRepository(database);
|
||||
|
||||
economyService = new EconomyService(economyRepository, CoalMoney.fromCoalConfig(getConfig().getDouble("economy.starting_balance", 0.0D)));
|
||||
governmentService = new GovernmentService(governmentRepository, economyService, getConfig());
|
||||
landService = new LandService(landRepository, getConfig());
|
||||
claimService = new ClaimService(claimRepository, landService, economyService, getConfig());
|
||||
claimMarkerService = new ClaimMarkerService(this, claimService);
|
||||
claimSelectionService = new ClaimSelectionService(this);
|
||||
claimVisualService = new ClaimVisualService(this);
|
||||
buildProtectionService = new BuildProtectionService(this, claimService);
|
||||
landAppraisalService = new LandAppraisalService(this, claimService);
|
||||
marketService = new MarketService(this, marketRepository);
|
||||
npcService = new NpcService(this, npcRepository);
|
||||
openRouterService = new OpenRouterService(this);
|
||||
permitService = new PermitService(permitRepository, landService, economyService, getConfig());
|
||||
miningService = new MiningService(this, landService, claimService, permitService);
|
||||
diviningRodService = new DiviningRodService(this);
|
||||
superFurnaceService = new SuperFurnaceService(this, superFurnaceRepository);
|
||||
spawnGuideService = new SpawnGuideService(this);
|
||||
taxService = new TaxService();
|
||||
|
||||
Objects.requireNonNull(getCommand("coal")).setExecutor(new CoalCommand(this));
|
||||
Objects.requireNonNull(getCommand("claim")).setExecutor(new ClaimCommand(this));
|
||||
Objects.requireNonNull(getCommand("permit")).setExecutor(new PermitCommand(this));
|
||||
Objects.requireNonNull(getCommand("land")).setExecutor(new LandCommand(this));
|
||||
Objects.requireNonNull(getCommand("market")).setExecutor(new MarketCommand(this));
|
||||
Objects.requireNonNull(getCommand("diviningrod")).setExecutor(new DiviningRodCommand(this));
|
||||
Objects.requireNonNull(getCommand("police")).setExecutor(new PoliceCommand(this));
|
||||
Objects.requireNonNull(getCommand("cgnpc")).setExecutor(new NpcCommand(this));
|
||||
Objects.requireNonNull(getCommand("coalgov")).setExecutor(new AdminCommand(this));
|
||||
|
||||
getServer().getPluginManager().registerEvents(new BlockBreakListener(this), this);
|
||||
getServer().getPluginManager().registerEvents(new BlockPlaceListener(this), this);
|
||||
getServer().getPluginManager().registerEvents(new ClaimWandListener(this), this);
|
||||
getServer().getPluginManager().registerEvents(new DiviningRodListener(this), this);
|
||||
getServer().getPluginManager().registerEvents(new MarketListener(this), this);
|
||||
getServer().getPluginManager().registerEvents(new NpcListener(this), this);
|
||||
getServer().getPluginManager().registerEvents(new PlayerJoinListener(this), this);
|
||||
getServer().getPluginManager().registerEvents(new SuperFurnaceListener(this), this);
|
||||
superFurnaceService.registerRecipe();
|
||||
getServer().getScheduler().runTaskLater(this, () -> claimMarkerService.placeAllLoadedMarkers(), 40L);
|
||||
long npcTicks = Math.max(20L, getConfig().getLong("npc.worker.tick_interval_seconds", 20L) * 20L);
|
||||
getServer().getScheduler().runTaskTimer(this, () -> npcService.tickWorkers(), 80L, npcTicks);
|
||||
getLogger().info("CoalGov enabled.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (database != null) {
|
||||
database.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void reloadCoalGovConfig() {
|
||||
reloadConfig();
|
||||
messages = new Messages(getConfig());
|
||||
governmentService.setConfig(getConfig());
|
||||
landService.setConfig(getConfig());
|
||||
claimService.setConfig(getConfig());
|
||||
permitService.setConfig(getConfig());
|
||||
superFurnaceService.registerRecipe();
|
||||
}
|
||||
|
||||
public boolean hasAdminBypass(Player player) {
|
||||
return player.hasPermission("coalgov.admin") && adminBypassPlayers.contains(player.getUniqueId());
|
||||
}
|
||||
|
||||
public void setAdminBypass(Player player, boolean enabled) {
|
||||
if (enabled) {
|
||||
adminBypassPlayers.add(player.getUniqueId());
|
||||
} else {
|
||||
adminBypassPlayers.remove(player.getUniqueId());
|
||||
}
|
||||
}
|
||||
|
||||
public EconomyService economyService() {
|
||||
return economyService;
|
||||
}
|
||||
|
||||
public GovernmentService governmentService() {
|
||||
return governmentService;
|
||||
}
|
||||
|
||||
public ClaimService claimService() {
|
||||
return claimService;
|
||||
}
|
||||
|
||||
public ClaimVisualService claimVisualService() {
|
||||
return claimVisualService;
|
||||
}
|
||||
|
||||
public ClaimMarkerService claimMarkerService() {
|
||||
return claimMarkerService;
|
||||
}
|
||||
|
||||
public ClaimSelectionService claimSelectionService() {
|
||||
return claimSelectionService;
|
||||
}
|
||||
|
||||
public BuildProtectionService buildProtectionService() {
|
||||
return buildProtectionService;
|
||||
}
|
||||
|
||||
public LandAppraisalService landAppraisalService() {
|
||||
return landAppraisalService;
|
||||
}
|
||||
|
||||
public MarketService marketService() {
|
||||
return marketService;
|
||||
}
|
||||
|
||||
public NpcService npcService() {
|
||||
return npcService;
|
||||
}
|
||||
|
||||
public OpenRouterService openRouterService() {
|
||||
return openRouterService;
|
||||
}
|
||||
|
||||
public LandService landService() {
|
||||
return landService;
|
||||
}
|
||||
|
||||
public PermitService permitService() {
|
||||
return permitService;
|
||||
}
|
||||
|
||||
public MiningService miningService() {
|
||||
return miningService;
|
||||
}
|
||||
|
||||
public DiviningRodService diviningRodService() {
|
||||
return diviningRodService;
|
||||
}
|
||||
|
||||
public SuperFurnaceService superFurnaceService() {
|
||||
return superFurnaceService;
|
||||
}
|
||||
|
||||
public SpawnGuideService spawnGuideService() {
|
||||
return spawnGuideService;
|
||||
}
|
||||
|
||||
public TaxService taxService() {
|
||||
return taxService;
|
||||
}
|
||||
|
||||
public Messages messages() {
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
package com.librewiki.coalgov.command;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.util.CoalMoney;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
public final class AdminCommand implements CommandExecutor {
|
||||
private final CoalGovPlugin plugin;
|
||||
|
||||
public AdminCommand(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (args.length == 0 || !args[0].equalsIgnoreCase("admin")) {
|
||||
usage(sender);
|
||||
return true;
|
||||
}
|
||||
if (!sender.hasPermission("coalgov.admin")) {
|
||||
sender.sendMessage(plugin.messages().text("&cYou lack permission."));
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
if (args.length >= 2 && args[1].equalsIgnoreCase("reload")) {
|
||||
plugin.reloadCoalGovConfig();
|
||||
sender.sendMessage(plugin.messages().text("&aConfig reloaded."));
|
||||
return true;
|
||||
}
|
||||
if (args.length >= 2 && args[1].equalsIgnoreCase("bypass")) {
|
||||
bypass(sender, args);
|
||||
return true;
|
||||
}
|
||||
if (args.length >= 2 && args[1].equalsIgnoreCase("spawn")) {
|
||||
spawn(sender, args);
|
||||
return true;
|
||||
}
|
||||
if (args.length >= 2 && args[1].equalsIgnoreCase("rod")) {
|
||||
rod(sender, args);
|
||||
return true;
|
||||
}
|
||||
if (args.length >= 2 && args[1].equalsIgnoreCase("treasury")) {
|
||||
treasury(sender, args);
|
||||
return true;
|
||||
}
|
||||
if (args.length >= 2 && args[1].equalsIgnoreCase("tax")) {
|
||||
tax(sender, args);
|
||||
return true;
|
||||
}
|
||||
if (args.length < 3) {
|
||||
usage(sender);
|
||||
return true;
|
||||
}
|
||||
OfflinePlayer target = Bukkit.getOfflinePlayer(args[2]);
|
||||
plugin.economyService().ensurePlayer(target);
|
||||
switch (args[1].toLowerCase()) {
|
||||
case "balance" -> sender.sendMessage(plugin.messages().text("&e" + args[2] + ": &f"
|
||||
+ CoalMoney.format(plugin.economyService().balance(target.getUniqueId()))));
|
||||
case "grant" -> grant(sender, target, args);
|
||||
case "take" -> take(sender, target, args);
|
||||
default -> usage(sender);
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
plugin.getLogger().warning("Admin command failed: " + exception.getMessage());
|
||||
sender.sendMessage(plugin.messages().text("&cCoalGov database unavailable."));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void usage(CommandSender sender) {
|
||||
sender.sendMessage(plugin.messages().text("&eUsage: /coalgov admin <balance|grant|take|reload|spawn|bypass|rod|treasury|tax>"));
|
||||
}
|
||||
|
||||
private void bypass(CommandSender sender, String[] args) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage("Players only.");
|
||||
return;
|
||||
}
|
||||
if (args.length == 2 || (args.length == 3 && args[2].equalsIgnoreCase("status"))) {
|
||||
String status = plugin.hasAdminBypass(player) ? "&aON" : "&cOFF";
|
||||
player.sendMessage(plugin.messages().text("&eAdmin bypass is " + status + "&e."));
|
||||
return;
|
||||
}
|
||||
if (args.length != 3) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /coalgov admin bypass <on|off|status>"));
|
||||
return;
|
||||
}
|
||||
if (args[2].equalsIgnoreCase("on")) {
|
||||
plugin.setAdminBypass(player, true);
|
||||
player.sendMessage(plugin.messages().text("&aAdmin bypass enabled. Build, mining, and marker restrictions are bypassed."));
|
||||
return;
|
||||
}
|
||||
if (args[2].equalsIgnoreCase("off")) {
|
||||
plugin.setAdminBypass(player, false);
|
||||
player.sendMessage(plugin.messages().text("&aAdmin bypass disabled. You now play under normal CoalGov rules."));
|
||||
return;
|
||||
}
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /coalgov admin bypass <on|off|status>"));
|
||||
}
|
||||
|
||||
private void spawn(CommandSender sender, String[] args) throws SQLException {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage("Players only.");
|
||||
return;
|
||||
}
|
||||
if (args.length != 3 || !args[2].equalsIgnoreCase("setup")) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /coalgov admin spawn setup"));
|
||||
return;
|
||||
}
|
||||
plugin.spawnGuideService().setup(player);
|
||||
player.sendMessage(plugin.messages().text("&aSpawn set, protected, and guide signs placed."));
|
||||
}
|
||||
|
||||
private void rod(CommandSender sender, String[] args) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage("Players only.");
|
||||
return;
|
||||
}
|
||||
if (args.length != 2) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /coalgov admin rod"));
|
||||
return;
|
||||
}
|
||||
player.getInventory().addItem(plugin.diviningRodService().rod());
|
||||
player.sendMessage(plugin.messages().text("&aDivining rod granted."));
|
||||
}
|
||||
|
||||
private void treasury(CommandSender sender, String[] args) throws SQLException {
|
||||
if (args.length == 3 && args[2].equalsIgnoreCase("balance")) {
|
||||
sender.sendMessage(plugin.messages().text("&eTreasury: &f" + CoalMoney.format(plugin.governmentService().treasuryBalance())));
|
||||
return;
|
||||
}
|
||||
if (args.length == 5 && args[2].equalsIgnoreCase("grant")) {
|
||||
OfflinePlayer target = Bukkit.getOfflinePlayer(args[3]);
|
||||
plugin.economyService().ensurePlayer(target);
|
||||
long amount = CoalMoney.parsePositive(args[4]);
|
||||
if (amount <= 0L) {
|
||||
sender.sendMessage(plugin.messages().text("&cAmount must be greater than zero."));
|
||||
return;
|
||||
}
|
||||
if (!plugin.governmentService().grantFromTreasury(target.getUniqueId(), amount)) {
|
||||
sender.sendMessage(plugin.messages().text("&cTreasury grant failed. Check treasury balance."));
|
||||
return;
|
||||
}
|
||||
sender.sendMessage(plugin.messages().text("&aGranted &f" + CoalMoney.format(amount) + " &afrom the treasury."));
|
||||
return;
|
||||
}
|
||||
sender.sendMessage(plugin.messages().text("&eUsage: /coalgov admin treasury <balance|grant <player> <amount>>"));
|
||||
}
|
||||
|
||||
private void tax(CommandSender sender, String[] args) throws SQLException {
|
||||
if (args.length != 5 || !args[2].equalsIgnoreCase("exempt")) {
|
||||
sender.sendMessage(plugin.messages().text("&eUsage: /coalgov admin tax exempt <player> <on|off|status>"));
|
||||
return;
|
||||
}
|
||||
OfflinePlayer target = Bukkit.getOfflinePlayer(args[3]);
|
||||
plugin.economyService().ensurePlayer(target);
|
||||
if (args[4].equalsIgnoreCase("status")) {
|
||||
boolean exempt = plugin.governmentService().taxExempt(target.getUniqueId());
|
||||
sender.sendMessage(plugin.messages().text("&eTax exemption for &f" + args[3] + "&e: " + (exempt ? "&aON" : "&cOFF")));
|
||||
return;
|
||||
}
|
||||
if (args[4].equalsIgnoreCase("on")) {
|
||||
plugin.governmentService().setTaxExempt(target.getUniqueId(), true);
|
||||
sender.sendMessage(plugin.messages().text("&aTax exemption enabled for &f" + args[3] + "&a."));
|
||||
return;
|
||||
}
|
||||
if (args[4].equalsIgnoreCase("off")) {
|
||||
plugin.governmentService().setTaxExempt(target.getUniqueId(), false);
|
||||
sender.sendMessage(plugin.messages().text("&aTax exemption disabled for &f" + args[3] + "&a."));
|
||||
return;
|
||||
}
|
||||
sender.sendMessage(plugin.messages().text("&eUsage: /coalgov admin tax exempt <player> <on|off|status>"));
|
||||
}
|
||||
|
||||
private void grant(CommandSender sender, OfflinePlayer target, String[] args) throws SQLException {
|
||||
if (args.length != 4) {
|
||||
sender.sendMessage(plugin.messages().text("&eUsage: /coalgov admin grant <player> <amount>"));
|
||||
return;
|
||||
}
|
||||
long amount = CoalMoney.parsePositive(args[3]);
|
||||
if (amount <= 0) {
|
||||
sender.sendMessage(plugin.messages().text("&cAmount must be greater than zero."));
|
||||
return;
|
||||
}
|
||||
plugin.economyService().grant(target.getUniqueId(), amount);
|
||||
sender.sendMessage(plugin.messages().text("&aGranted &f" + CoalMoney.format(amount) + "&a."));
|
||||
}
|
||||
|
||||
private void take(CommandSender sender, OfflinePlayer target, String[] args) throws SQLException {
|
||||
if (args.length != 4) {
|
||||
sender.sendMessage(plugin.messages().text("&eUsage: /coalgov admin take <player> <amount>"));
|
||||
return;
|
||||
}
|
||||
long amount = CoalMoney.parsePositive(args[3]);
|
||||
if (amount <= 0) {
|
||||
sender.sendMessage(plugin.messages().text("&cAmount must be greater than zero."));
|
||||
return;
|
||||
}
|
||||
if (plugin.economyService().take(target.getUniqueId(), amount)) {
|
||||
sender.sendMessage(plugin.messages().text("&aTook &f" + CoalMoney.format(amount) + "&a."));
|
||||
} else {
|
||||
sender.sendMessage(plugin.messages().text("&cBalance too low."));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
package com.librewiki.coalgov.command;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.model.Claim;
|
||||
import com.librewiki.coalgov.model.ClaimPoint;
|
||||
import com.librewiki.coalgov.model.LandRegion;
|
||||
import com.librewiki.coalgov.service.ClaimService;
|
||||
import com.librewiki.coalgov.service.LandAppraisalService;
|
||||
import com.librewiki.coalgov.util.CoalMoney;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class ClaimCommand implements CommandExecutor {
|
||||
private final CoalGovPlugin plugin;
|
||||
|
||||
public ClaimCommand(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage("Players only.");
|
||||
return true;
|
||||
}
|
||||
if (args.length == 0) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /claim <wand|buy|info|list|show|appraise|sell|transfer|abandon>"));
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
plugin.economyService().ensurePlayer(player);
|
||||
switch (args[0].toLowerCase()) {
|
||||
case "wand" -> wand(player);
|
||||
case "buy" -> buy(player, args);
|
||||
case "info" -> info(player);
|
||||
case "list" -> list(player);
|
||||
case "show" -> show(player, args);
|
||||
case "appraise" -> appraise(player, args);
|
||||
case "sell" -> sell(player, args);
|
||||
case "transfer" -> transfer(player, args);
|
||||
case "abandon" -> abandon(player, args);
|
||||
default -> player.sendMessage(plugin.messages().text("&eUsage: /claim <wand|buy|info|list|show|appraise|sell|transfer|abandon>"));
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
plugin.getLogger().warning("Claim command failed: " + exception.getMessage());
|
||||
player.sendMessage(plugin.messages().text("&cClaim registry unavailable."));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void buy(Player player, String[] args) throws SQLException {
|
||||
if (args.length != 2 && args.length != 3) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /claim buy <homestead|industrial> [radius]"));
|
||||
return;
|
||||
}
|
||||
|
||||
ClaimService.BuyResult result;
|
||||
if (args.length == 3) {
|
||||
int radius = parseInt(args[2]);
|
||||
result = plugin.claimService().buy(player.getUniqueId(), player.getLocation(), args[1], radius);
|
||||
} else {
|
||||
List<ClaimPoint> points = plugin.claimSelectionService().claimPoints(player);
|
||||
result = plugin.claimService().buyPolygon(player.getUniqueId(), player.getWorld().getName(), args[1], points);
|
||||
}
|
||||
if (!result.success()) {
|
||||
player.sendMessage(plugin.messages().text("&c" + result.message()));
|
||||
return;
|
||||
}
|
||||
player.sendMessage(plugin.messages().text("&aClaim #" + result.id() + " created for &f" + CoalMoney.format(result.cost()) + "&a."));
|
||||
plugin.claimSelectionService().consume(player);
|
||||
plugin.claimService().findOwnedById(player.getUniqueId(), result.id()).ifPresent(claim -> {
|
||||
plugin.claimMarkerService().placeMarkers(claim);
|
||||
plugin.claimVisualService().show(player, claim);
|
||||
});
|
||||
}
|
||||
|
||||
private void wand(Player player) {
|
||||
player.getInventory().addItem(plugin.claimSelectionService().wand());
|
||||
player.sendMessage(plugin.messages().text("&aClaim wand issued. Right-click blocks to draw a polygon with marker previews; sneak right-click to clear."));
|
||||
}
|
||||
|
||||
private void info(Player player) throws SQLException {
|
||||
Optional<Claim> claim = plugin.claimService().claimAt(player.getLocation());
|
||||
Optional<LandRegion> region = plugin.landService().regionAt(player.getLocation());
|
||||
player.sendMessage(plugin.messages().text("&eLand: &f" + region.map(r -> r.name() + " " + r.landClass()).orElse(plugin.landService().classAt(player.getLocation()).name())));
|
||||
if (claim.isPresent()) {
|
||||
Claim c = claim.get();
|
||||
player.sendMessage(plugin.messages().text("&eClaim: &f#" + c.id() + " " + c.claimType() + " owner " + c.ownerUuid()));
|
||||
if (c.isPolygon()) {
|
||||
player.sendMessage(plugin.messages().text("&eShape: &fpolygon with " + c.vertices().size() + " points"));
|
||||
}
|
||||
plugin.claimVisualService().show(player, c);
|
||||
} else {
|
||||
player.sendMessage(plugin.messages().text("&eClaim: &fnone"));
|
||||
}
|
||||
}
|
||||
|
||||
private void list(Player player) throws SQLException {
|
||||
List<Claim> claims = plugin.claimService().list(player.getUniqueId());
|
||||
if (claims.isEmpty()) {
|
||||
player.sendMessage(plugin.messages().text("&eYou have no claims."));
|
||||
return;
|
||||
}
|
||||
player.sendMessage(plugin.messages().text("&eYour claims:"));
|
||||
for (Claim claim : claims) {
|
||||
String shape = claim.isPolygon() ? " polygon" : " rectangle";
|
||||
player.sendMessage(plugin.messages().raw("&7#" + claim.id() + " &f" + claim.claimType() + shape + " purchase " + CoalMoney.format(plugin.claimService().claimPurchaseCost(claim)) + " " + claim.world()
|
||||
+ " [" + claim.x1() + "," + claim.z1() + "] to [" + claim.x2() + "," + claim.z2() + "]"));
|
||||
}
|
||||
player.sendMessage(plugin.messages().text("&7Use &f/claim appraise [id] &7for sale value, or &f/claim show <id>&7."));
|
||||
}
|
||||
|
||||
private void show(Player player, String[] args) throws SQLException {
|
||||
if (args.length != 2) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /claim show <id>"));
|
||||
return;
|
||||
}
|
||||
long id = parseLong(args[1]);
|
||||
if (id <= 0) {
|
||||
player.sendMessage(plugin.messages().text("&cInvalid claim id."));
|
||||
return;
|
||||
}
|
||||
Optional<Claim> claim = plugin.claimService().findOwnedById(player.getUniqueId(), id);
|
||||
if (claim.isEmpty()) {
|
||||
player.sendMessage(plugin.messages().text("&cNo owned claim with that id."));
|
||||
return;
|
||||
}
|
||||
plugin.claimVisualService().show(player, claim.get());
|
||||
player.sendMessage(plugin.messages().text("&aShowing claim #" + id + " boundary."));
|
||||
}
|
||||
|
||||
private void appraise(Player player, String[] args) throws SQLException {
|
||||
Optional<Claim> claim = resolveOwnedClaim(player, args, "appraise");
|
||||
if (claim.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
LandAppraisalService.Appraisal appraisal = plugin.landAppraisalService().appraise(claim.get());
|
||||
if (!appraisal.worldLoaded()) {
|
||||
player.sendMessage(plugin.messages().text("&cThat claim's world is not loaded."));
|
||||
return;
|
||||
}
|
||||
sendAppraisal(player, appraisal);
|
||||
}
|
||||
|
||||
private void sell(Player player, String[] args) throws SQLException {
|
||||
Optional<Claim> claim = resolveOwnedClaim(player, args, "sell");
|
||||
if (claim.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LandAppraisalService.Appraisal appraisal = plugin.landAppraisalService().appraise(claim.get());
|
||||
if (!appraisal.worldLoaded()) {
|
||||
player.sendMessage(plugin.messages().text("&cThat claim's world is not loaded."));
|
||||
return;
|
||||
}
|
||||
ClaimService.SellResult result = plugin.claimService().sell(player.getUniqueId(), claim.get().id(), appraisal.totalValue());
|
||||
if (!result.success()) {
|
||||
player.sendMessage(plugin.messages().text("&c" + result.message()));
|
||||
return;
|
||||
}
|
||||
plugin.claimMarkerService().removeMarkers(result.claim());
|
||||
if (result.refund() >= 0L) {
|
||||
player.sendMessage(plugin.messages().text("&aSold claim #" + result.claim().id() + " back to the Ministry for &f" + CoalMoney.format(result.refund()) + "&a."));
|
||||
} else {
|
||||
player.sendMessage(plugin.messages().text("&eSurrendered claim #" + result.claim().id() + " and paid &f" + CoalMoney.format(Math.abs(result.refund())) + " &efor extracted land value."));
|
||||
}
|
||||
sendAppraisal(player, appraisal);
|
||||
}
|
||||
|
||||
private void transfer(Player player, String[] args) throws SQLException {
|
||||
if (args.length != 3) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /claim transfer <id> <player>"));
|
||||
return;
|
||||
}
|
||||
long id = parseLong(args[1]);
|
||||
if (id <= 0L) {
|
||||
player.sendMessage(plugin.messages().text("&cInvalid claim id."));
|
||||
return;
|
||||
}
|
||||
OfflinePlayer recipient = Bukkit.getOfflinePlayer(args[2]);
|
||||
plugin.economyService().ensurePlayer(recipient);
|
||||
ClaimService.TransferResult result = plugin.claimService().transfer(player.getUniqueId(), id, recipient.getUniqueId());
|
||||
if (!result.success()) {
|
||||
player.sendMessage(plugin.messages().text("&c" + result.message()));
|
||||
return;
|
||||
}
|
||||
String recipientName = recipient.getName() == null ? recipient.getUniqueId().toString() : recipient.getName();
|
||||
player.sendMessage(plugin.messages().text("&aTransferred claim #" + id + " to &f" + recipientName + "&a."));
|
||||
if (recipient.isOnline() && recipient.getPlayer() != null) {
|
||||
recipient.getPlayer().sendMessage(plugin.messages().text("&a" + player.getName() + " transferred claim #" + id + " to you."));
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<Claim> resolveOwnedClaim(Player player, String[] args, String action) throws SQLException {
|
||||
if (args.length > 2) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /claim " + action + " [id]"));
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Optional<Claim> claim;
|
||||
if (args.length == 2) {
|
||||
long id = parseLong(args[1]);
|
||||
if (id <= 0) {
|
||||
player.sendMessage(plugin.messages().text("&cInvalid claim id."));
|
||||
return Optional.empty();
|
||||
}
|
||||
claim = plugin.claimService().findOwnedById(player.getUniqueId(), id);
|
||||
} else {
|
||||
claim = plugin.claimService().claimAt(player.getLocation())
|
||||
.filter(c -> c.ownerUuid().equals(player.getUniqueId()));
|
||||
}
|
||||
if (claim.isEmpty()) {
|
||||
player.sendMessage(plugin.messages().text("&cStand inside one of your claims, or use /claim " + action + " <id>."));
|
||||
}
|
||||
return claim;
|
||||
}
|
||||
|
||||
private void sendAppraisal(Player player, LandAppraisalService.Appraisal appraisal) {
|
||||
player.sendMessage(plugin.messages().text("&eAppraisal: &f" + CoalMoney.format(appraisal.totalValue())));
|
||||
player.sendMessage(plugin.messages().raw("&7Land condition: &f" + appraisal.conditionPercent()
|
||||
+ "% &7base value: &f" + CoalMoney.format(Math.round(appraisal.conditionValue()))
|
||||
+ " &7resources: &f" + CoalMoney.format(appraisal.resourceValue())
|
||||
+ " &7depletion charge: &f" + CoalMoney.format(appraisal.depletionChargeRounded())));
|
||||
if (appraisal.originalCost() == 0L) {
|
||||
player.sendMessage(plugin.messages().raw("&7Starter homestead land value is &f0&7; only resources can offset depletion."));
|
||||
}
|
||||
String resourceSummary = resourceSummary(appraisal);
|
||||
if (!resourceSummary.isEmpty()) {
|
||||
player.sendMessage(plugin.messages().raw("&7Counted resources: &f" + resourceSummary));
|
||||
}
|
||||
}
|
||||
|
||||
private String resourceSummary(LandAppraisalService.Appraisal appraisal) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
int shown = 0;
|
||||
for (var entry : appraisal.resources().entrySet()) {
|
||||
if (entry.getValue() <= 0L) {
|
||||
continue;
|
||||
}
|
||||
if (shown++ > 0) {
|
||||
builder.append(", ");
|
||||
}
|
||||
builder.append(readableMaterial(entry.getKey())).append(" ").append(entry.getValue());
|
||||
if (shown >= 5) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String readableMaterial(Material material) {
|
||||
return material.name().toLowerCase().replace('_', ' ');
|
||||
}
|
||||
|
||||
private void abandon(Player player, String[] args) throws SQLException {
|
||||
if (args.length != 2) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /claim abandon <id>"));
|
||||
return;
|
||||
}
|
||||
long id = parseLong(args[1]);
|
||||
if (id <= 0) {
|
||||
player.sendMessage(plugin.messages().text("&cInvalid claim id."));
|
||||
return;
|
||||
}
|
||||
Optional<Claim> claim = plugin.claimService().findOwnedById(player.getUniqueId(), id);
|
||||
if (plugin.claimService().abandon(player.getUniqueId(), id)) {
|
||||
claim.ifPresent(c -> plugin.claimMarkerService().removeMarkers(c));
|
||||
player.sendMessage(plugin.messages().text("&aClaim abandoned."));
|
||||
} else {
|
||||
player.sendMessage(plugin.messages().text("&cNo owned claim with that id."));
|
||||
}
|
||||
}
|
||||
|
||||
private int parseInt(String raw) {
|
||||
try {
|
||||
return Integer.parseInt(raw);
|
||||
} catch (NumberFormatException exception) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private long parseLong(String raw) {
|
||||
try {
|
||||
return Long.parseLong(raw);
|
||||
} catch (NumberFormatException exception) {
|
||||
return -1L;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package com.librewiki.coalgov.command;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.model.Fine;
|
||||
import com.librewiki.coalgov.util.CoalMoney;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
public final class CoalCommand implements CommandExecutor {
|
||||
private final CoalGovPlugin plugin;
|
||||
|
||||
public CoalCommand(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage("Players only.");
|
||||
return true;
|
||||
}
|
||||
if (args.length == 0) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /coal <balance|deposit|withdraw|pay|fines>"));
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
plugin.economyService().ensurePlayer(player);
|
||||
switch (args[0].toLowerCase()) {
|
||||
case "balance" -> balance(player);
|
||||
case "deposit" -> deposit(player);
|
||||
case "withdraw" -> withdraw(player, args);
|
||||
case "pay" -> pay(player, args);
|
||||
case "fines" -> fines(player, args);
|
||||
default -> player.sendMessage(plugin.messages().text("&eUsage: /coal <balance|deposit|withdraw|pay|fines>"));
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
plugin.getLogger().warning("Coal command failed: " + exception.getMessage());
|
||||
player.sendMessage(plugin.messages().text("&cCoal ledger unavailable."));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void balance(Player player) throws SQLException {
|
||||
player.sendMessage(plugin.messages().text("&eBalance: &f" + CoalMoney.format(plugin.economyService().balance(player.getUniqueId()))));
|
||||
}
|
||||
|
||||
private void deposit(Player player) throws SQLException {
|
||||
boolean coalEnabled = plugin.getConfig().getBoolean("economy.deposit_coal", true);
|
||||
boolean blocksEnabled = plugin.getConfig().getBoolean("economy.deposit_coal_blocks", true);
|
||||
long credit = 0;
|
||||
for (ItemStack item : player.getInventory().getContents()) {
|
||||
if (item == null) {
|
||||
continue;
|
||||
}
|
||||
if (coalEnabled && item.getType() == Material.COAL) {
|
||||
credit += item.getAmount() * CoalMoney.CENTS_PER_COAL;
|
||||
item.setAmount(0);
|
||||
} else if (blocksEnabled && item.getType() == Material.COAL_BLOCK) {
|
||||
credit += item.getAmount() * 9L * CoalMoney.CENTS_PER_COAL;
|
||||
item.setAmount(0);
|
||||
}
|
||||
}
|
||||
if (credit <= 0) {
|
||||
player.sendMessage(plugin.messages().text("&cNo depositable coal found."));
|
||||
return;
|
||||
}
|
||||
plugin.economyService().deposit(player.getUniqueId(), credit);
|
||||
player.sendMessage(plugin.messages().text("&aDeposited &f" + CoalMoney.format(credit) + "&a."));
|
||||
}
|
||||
|
||||
private void withdraw(Player player, String[] args) throws SQLException {
|
||||
if (args.length != 2) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /coal withdraw <amount>"));
|
||||
return;
|
||||
}
|
||||
long amount = CoalMoney.parsePositive(args[1]);
|
||||
if (amount <= 0) {
|
||||
player.sendMessage(plugin.messages().text("&cAmount must be greater than zero."));
|
||||
return;
|
||||
}
|
||||
if (amount % CoalMoney.CENTS_PER_COAL != 0L) {
|
||||
player.sendMessage(plugin.messages().text("&cWithdrawals must be whole coal because they create coal items."));
|
||||
return;
|
||||
}
|
||||
long coalItems = amount / CoalMoney.CENTS_PER_COAL;
|
||||
if (plugin.economyService().balance(player.getUniqueId()) < amount) {
|
||||
player.sendMessage(plugin.messages().text("&cBalance too low."));
|
||||
return;
|
||||
}
|
||||
if (!canFitCoal(player, coalItems)) {
|
||||
player.sendMessage(plugin.messages().text("&cYou need more inventory space."));
|
||||
return;
|
||||
}
|
||||
if (!plugin.economyService().withdraw(player.getUniqueId(), amount)) {
|
||||
player.sendMessage(plugin.messages().text("&cBalance too low."));
|
||||
return;
|
||||
}
|
||||
long remaining = coalItems;
|
||||
while (remaining > 0) {
|
||||
int stack = (int) Math.min(Material.COAL.getMaxStackSize(), remaining);
|
||||
player.getInventory().addItem(new ItemStack(Material.COAL, stack));
|
||||
remaining -= stack;
|
||||
}
|
||||
player.sendMessage(plugin.messages().text("&aWithdrew &f" + CoalMoney.format(amount) + "&a."));
|
||||
}
|
||||
|
||||
private void pay(Player player, String[] args) throws SQLException {
|
||||
if (args.length != 3) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /coal pay <player> <amount>"));
|
||||
return;
|
||||
}
|
||||
Player target = Bukkit.getPlayerExact(args[1]);
|
||||
if (target == null) {
|
||||
player.sendMessage(plugin.messages().text("&cThat player must be online."));
|
||||
return;
|
||||
}
|
||||
long amount = CoalMoney.parsePositive(args[2]);
|
||||
if (amount <= 0) {
|
||||
player.sendMessage(plugin.messages().text("&cAmount must be greater than zero."));
|
||||
return;
|
||||
}
|
||||
plugin.economyService().ensurePlayer(target);
|
||||
if (!plugin.economyService().pay(player.getUniqueId(), target.getUniqueId(), amount)) {
|
||||
player.sendMessage(plugin.messages().text("&cPayment failed. Check your balance."));
|
||||
return;
|
||||
}
|
||||
player.sendMessage(plugin.messages().text("&aPaid &f" + CoalMoney.format(amount) + " &ato " + target.getName() + "."));
|
||||
target.sendMessage(plugin.messages().text("&aReceived &f" + CoalMoney.format(amount) + " &afrom " + player.getName() + "."));
|
||||
}
|
||||
|
||||
private void fines(Player player, String[] args) throws SQLException {
|
||||
if (args.length == 1 || (args.length == 2 && args[1].equalsIgnoreCase("list"))) {
|
||||
List<Fine> fines = plugin.governmentService().unpaidFines(player.getUniqueId());
|
||||
if (fines.isEmpty()) {
|
||||
player.sendMessage(plugin.messages().text("&aYou have no unpaid fines."));
|
||||
return;
|
||||
}
|
||||
long total = fines.stream().mapToLong(Fine::amount).sum();
|
||||
player.sendMessage(plugin.messages().text("&eUnpaid fines: &f" + CoalMoney.format(total) + " &etotal"));
|
||||
for (Fine fine : fines) {
|
||||
player.sendMessage(plugin.messages().raw("&7#" + fine.id() + " &f" + CoalMoney.format(fine.amount()) + " &7" + fine.reason()));
|
||||
}
|
||||
player.sendMessage(plugin.messages().text("&7Use &f/coal fines pay <id|all>&7."));
|
||||
return;
|
||||
}
|
||||
if (args.length == 3 && args[1].equalsIgnoreCase("pay")) {
|
||||
if (args[2].equalsIgnoreCase("all")) {
|
||||
var result = plugin.governmentService().payAllFines(player.getUniqueId());
|
||||
if (!result.success()) {
|
||||
player.sendMessage(plugin.messages().text("&c" + result.message()));
|
||||
return;
|
||||
}
|
||||
player.sendMessage(plugin.messages().text("&aPaid &f" + CoalMoney.format(result.amount()) + " &ain fines."));
|
||||
return;
|
||||
}
|
||||
long id = parsePositive(args[2]);
|
||||
if (id <= 0L) {
|
||||
player.sendMessage(plugin.messages().text("&cInvalid fine id."));
|
||||
return;
|
||||
}
|
||||
var result = plugin.governmentService().payFine(player.getUniqueId(), id);
|
||||
if (!result.success()) {
|
||||
player.sendMessage(plugin.messages().text("&c" + result.message()));
|
||||
return;
|
||||
}
|
||||
player.sendMessage(plugin.messages().text("&aPaid fine #" + id + " for &f" + CoalMoney.format(result.amount()) + "&a."));
|
||||
return;
|
||||
}
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /coal fines [list|pay <id|all>]"));
|
||||
}
|
||||
|
||||
private boolean canFitCoal(Player player, long amount) {
|
||||
if (amount > Integer.MAX_VALUE) {
|
||||
return false;
|
||||
}
|
||||
long capacity = 0;
|
||||
for (ItemStack item : player.getInventory().getStorageContents()) {
|
||||
if (item == null || item.getType().isAir()) {
|
||||
capacity += Material.COAL.getMaxStackSize();
|
||||
} else if (item.getType() == Material.COAL) {
|
||||
capacity += Material.COAL.getMaxStackSize() - item.getAmount();
|
||||
}
|
||||
if (capacity >= amount) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private long parsePositive(String raw) {
|
||||
try {
|
||||
return Long.parseLong(raw);
|
||||
} catch (NumberFormatException exception) {
|
||||
return -1L;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.librewiki.coalgov.command;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public final class DiviningRodCommand implements CommandExecutor {
|
||||
private final CoalGovPlugin plugin;
|
||||
|
||||
public DiviningRodCommand(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage("Players only.");
|
||||
return true;
|
||||
}
|
||||
if (args.length != 0) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /diviningrod"));
|
||||
return true;
|
||||
}
|
||||
player.getInventory().addItem(plugin.diviningRodService().rod());
|
||||
player.sendMessage(plugin.messages().text("&aDivining rod granted."));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package com.librewiki.coalgov.command;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.model.LandClass;
|
||||
import com.librewiki.coalgov.model.LandRegion;
|
||||
import com.librewiki.coalgov.util.Cuboid2D;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class LandCommand implements CommandExecutor {
|
||||
private final CoalGovPlugin plugin;
|
||||
|
||||
public LandCommand(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (args.length == 0) {
|
||||
sender.sendMessage(plugin.messages().text("&eUsage: /land <info|create|delete|list>"));
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
switch (args[0].toLowerCase()) {
|
||||
case "info" -> info(sender);
|
||||
case "create" -> create(sender, args);
|
||||
case "delete" -> delete(sender, args);
|
||||
case "list" -> list(sender);
|
||||
default -> sender.sendMessage(plugin.messages().text("&eUsage: /land <info|create|delete|list>"));
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
plugin.getLogger().warning("Land command failed: " + exception.getMessage());
|
||||
sender.sendMessage(plugin.messages().text("&cLand registry unavailable."));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void info(CommandSender sender) throws SQLException {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage("Players only.");
|
||||
return;
|
||||
}
|
||||
if (!player.hasPermission("coalgov.land.info") && !player.hasPermission("coalgov.admin")) {
|
||||
player.sendMessage(plugin.messages().text("&cYou lack permission."));
|
||||
return;
|
||||
}
|
||||
Optional<LandRegion> region = plugin.landService().regionAt(player.getLocation());
|
||||
if (region.isPresent()) {
|
||||
LandRegion r = region.get();
|
||||
player.sendMessage(plugin.messages().text("&eLand: &f" + r.name() + " " + r.landClass()
|
||||
+ " [" + r.x1() + "," + r.z1() + "] to [" + r.x2() + "," + r.z2() + "]"));
|
||||
} else {
|
||||
player.sendMessage(plugin.messages().text("&eLand: &f" + plugin.landService().classAt(player.getLocation()) + " default"));
|
||||
}
|
||||
}
|
||||
|
||||
private void create(CommandSender sender, String[] args) throws SQLException {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage("Players only.");
|
||||
return;
|
||||
}
|
||||
if (!player.hasPermission("coalgov.admin")) {
|
||||
player.sendMessage(plugin.messages().text("&cYou lack permission."));
|
||||
return;
|
||||
}
|
||||
if (args.length != 4) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /land create <name> <landClass> <radius>"));
|
||||
return;
|
||||
}
|
||||
LandClass landClass;
|
||||
try {
|
||||
landClass = LandClass.valueOf(args[2].toUpperCase());
|
||||
} catch (IllegalArgumentException exception) {
|
||||
player.sendMessage(plugin.messages().text("&cInvalid land class."));
|
||||
return;
|
||||
}
|
||||
int radius = parseInt(args[3]);
|
||||
if (radius <= 0) {
|
||||
player.sendMessage(plugin.messages().text("&cRadius must be greater than zero."));
|
||||
return;
|
||||
}
|
||||
Cuboid2D area = new Cuboid2D(
|
||||
player.getWorld().getName(),
|
||||
player.getLocation().getBlockX() - radius,
|
||||
player.getLocation().getBlockZ() - radius,
|
||||
player.getLocation().getBlockX() + radius,
|
||||
player.getLocation().getBlockZ() + radius
|
||||
);
|
||||
if (plugin.landService().create(args[1], area, landClass)) {
|
||||
player.sendMessage(plugin.messages().text("&aLand region created."));
|
||||
} else {
|
||||
player.sendMessage(plugin.messages().text("&cCould not create land region."));
|
||||
}
|
||||
}
|
||||
|
||||
private void delete(CommandSender sender, String[] args) throws SQLException {
|
||||
if (!sender.hasPermission("coalgov.admin")) {
|
||||
sender.sendMessage(plugin.messages().text("&cYou lack permission."));
|
||||
return;
|
||||
}
|
||||
if (args.length != 2) {
|
||||
sender.sendMessage(plugin.messages().text("&eUsage: /land delete <name>"));
|
||||
return;
|
||||
}
|
||||
if (plugin.landService().delete(args[1])) {
|
||||
sender.sendMessage(plugin.messages().text("&aLand region deleted."));
|
||||
} else {
|
||||
sender.sendMessage(plugin.messages().text("&cNo land region with that name."));
|
||||
}
|
||||
}
|
||||
|
||||
private void list(CommandSender sender) throws SQLException {
|
||||
List<LandRegion> regions = plugin.landService().listAll();
|
||||
if (regions.isEmpty()) {
|
||||
sender.sendMessage(plugin.messages().text("&eNo land regions."));
|
||||
return;
|
||||
}
|
||||
sender.sendMessage(plugin.messages().text("&eLand regions:"));
|
||||
for (LandRegion r : regions) {
|
||||
sender.sendMessage(plugin.messages().raw("&7" + r.name() + " &f" + r.landClass() + " " + r.world()
|
||||
+ " [" + r.x1() + "," + r.z1() + "] to [" + r.x2() + "," + r.z2() + "]"));
|
||||
}
|
||||
}
|
||||
|
||||
private int parseInt(String raw) {
|
||||
try {
|
||||
return Integer.parseInt(raw);
|
||||
} catch (NumberFormatException exception) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.librewiki.coalgov.command;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
public final class MarketCommand implements CommandExecutor {
|
||||
private final CoalGovPlugin plugin;
|
||||
|
||||
public MarketCommand(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage("Players only.");
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
plugin.economyService().ensurePlayer(player);
|
||||
plugin.marketService().open(player);
|
||||
} catch (SQLException exception) {
|
||||
plugin.getLogger().warning("Market command failed: " + exception.getMessage());
|
||||
player.sendMessage(plugin.messages().text("&cMarket ledger unavailable."));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
package com.librewiki.coalgov.command;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.model.ClaimPoint;
|
||||
import com.librewiki.coalgov.model.CoalNpc;
|
||||
import com.librewiki.coalgov.model.NpcZone;
|
||||
import com.librewiki.coalgov.service.NpcService;
|
||||
import com.librewiki.coalgov.service.OpenRouterService;
|
||||
import com.librewiki.coalgov.util.CoalMoney;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class NpcCommand implements CommandExecutor {
|
||||
private final CoalGovPlugin plugin;
|
||||
|
||||
public NpcCommand(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (!sender.hasPermission("coalgov.admin")) {
|
||||
sender.sendMessage(plugin.messages().text("&cYou lack permission."));
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
if (args.length >= 1 && args[0].equalsIgnoreCase("zone")) {
|
||||
zone(sender, args);
|
||||
} else if (args.length >= 1 && args[0].equalsIgnoreCase("create")) {
|
||||
create(sender, args);
|
||||
} else if (args.length >= 1 && args[0].equalsIgnoreCase("remove")) {
|
||||
remove(sender, args);
|
||||
} else if (args.length >= 1 && args[0].equalsIgnoreCase("list")) {
|
||||
list(sender);
|
||||
} else if (args.length >= 1 && args[0].equalsIgnoreCase("stock")) {
|
||||
stock(sender, args);
|
||||
} else if (args.length >= 1 && args[0].equalsIgnoreCase("funds")) {
|
||||
funds(sender, args);
|
||||
} else if (args.length >= 1 && args[0].equalsIgnoreCase("haggle")) {
|
||||
haggle(sender, args);
|
||||
} else {
|
||||
usage(sender);
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
plugin.getLogger().warning("NPC command failed: " + exception.getMessage());
|
||||
sender.sendMessage(plugin.messages().text("&cNPC registry unavailable."));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void zone(CommandSender sender, String[] args) throws SQLException {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage("Players only.");
|
||||
return;
|
||||
}
|
||||
if (args.length != 3 || !args[1].equalsIgnoreCase("create")) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /cgnpc zone create <name>"));
|
||||
return;
|
||||
}
|
||||
List<ClaimPoint> points = plugin.claimSelectionService().claimPoints(player);
|
||||
if (points.size() < 3) {
|
||||
player.sendMessage(plugin.messages().text("&cUse /claim wand to select at least 3 polygon points."));
|
||||
return;
|
||||
}
|
||||
long id = plugin.npcService().createZone(args[2], player.getWorld().getName(), points);
|
||||
plugin.claimSelectionService().consume(player);
|
||||
player.sendMessage(plugin.messages().text("&aNPC zone #" + id + " created."));
|
||||
}
|
||||
|
||||
private void create(CommandSender sender, String[] args) throws SQLException {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage("Players only.");
|
||||
return;
|
||||
}
|
||||
if (args.length != 4) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /cgnpc create <trader|worker> <name> <zone>"));
|
||||
return;
|
||||
}
|
||||
String type = args[1].toUpperCase();
|
||||
if (!type.equals("TRADER") && !type.equals("WORKER")) {
|
||||
player.sendMessage(plugin.messages().text("&cNPC type must be trader or worker."));
|
||||
return;
|
||||
}
|
||||
Optional<NpcZone> zone = plugin.npcService().findZone(args[3]);
|
||||
if (zone.isEmpty()) {
|
||||
player.sendMessage(plugin.messages().text("&cNo NPC zone named " + args[3] + "."));
|
||||
return;
|
||||
}
|
||||
long id = plugin.npcService().createNpc(type, args[2], zone.get(), player.getLocation());
|
||||
player.sendMessage(plugin.messages().text("&aNPC #" + id + " created."));
|
||||
}
|
||||
|
||||
private void remove(CommandSender sender, String[] args) throws SQLException {
|
||||
if (args.length != 2) {
|
||||
sender.sendMessage(plugin.messages().text("&eUsage: /cgnpc remove <id>"));
|
||||
return;
|
||||
}
|
||||
long id = parseLong(args[1]);
|
||||
if (plugin.npcService().removeNpc(id)) {
|
||||
sender.sendMessage(plugin.messages().text("&aNPC removed."));
|
||||
} else {
|
||||
sender.sendMessage(plugin.messages().text("&cNo NPC with that id."));
|
||||
}
|
||||
}
|
||||
|
||||
private void list(CommandSender sender) throws SQLException {
|
||||
sender.sendMessage(plugin.messages().text("&eNPC zones:"));
|
||||
for (NpcZone zone : plugin.npcService().listZones()) {
|
||||
sender.sendMessage(plugin.messages().raw("&7#" + zone.id() + " &f" + zone.name() + " " + zone.world()));
|
||||
}
|
||||
sender.sendMessage(plugin.messages().text("&eNPCs:"));
|
||||
for (CoalNpc npc : plugin.npcService().listNpcs()) {
|
||||
sender.sendMessage(plugin.messages().raw("&7#" + npc.id() + " &f" + npc.name() + " " + npc.npcType() + " citizens " + npc.citizensId()));
|
||||
}
|
||||
}
|
||||
|
||||
private void stock(CommandSender sender, String[] args) throws SQLException {
|
||||
if (args.length != 4) {
|
||||
sender.sendMessage(plugin.messages().text("&eUsage: /cgnpc stock <id> <material> <amount>"));
|
||||
return;
|
||||
}
|
||||
Optional<CoalNpc> npc = plugin.npcService().findNpc(parseLong(args[1]));
|
||||
Material material = Material.matchMaterial(args[2]);
|
||||
long amount = parseLong(args[3]);
|
||||
if (npc.isEmpty() || material == null || amount <= 0L) {
|
||||
sender.sendMessage(plugin.messages().text("&cInvalid NPC, material, or amount."));
|
||||
return;
|
||||
}
|
||||
plugin.npcService().addInventory(npc.get().id(), material, amount);
|
||||
sender.sendMessage(plugin.messages().text("&aNPC stock added."));
|
||||
}
|
||||
|
||||
private void funds(CommandSender sender, String[] args) throws SQLException {
|
||||
if (args.length != 3) {
|
||||
sender.sendMessage(plugin.messages().text("&eUsage: /cgnpc funds <id> <amount>"));
|
||||
return;
|
||||
}
|
||||
Optional<CoalNpc> npc = plugin.npcService().findNpc(parseLong(args[1]));
|
||||
long amount = CoalMoney.parsePositive(args[2]);
|
||||
if (npc.isEmpty() || amount <= 0L) {
|
||||
sender.sendMessage(plugin.messages().text("&cInvalid NPC or amount."));
|
||||
return;
|
||||
}
|
||||
plugin.economyService().credit(npc.get().accountUuid(), amount, "npc_admin_funds");
|
||||
sender.sendMessage(plugin.messages().text("&aNPC funded."));
|
||||
}
|
||||
|
||||
private void haggle(CommandSender sender, String[] args) throws SQLException {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage("Players only.");
|
||||
return;
|
||||
}
|
||||
if (args.length != 6) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /cgnpc haggle <id> <buy|sell> <material> <amount> <offer>"));
|
||||
return;
|
||||
}
|
||||
Optional<CoalNpc> npc = plugin.npcService().findNpc(parseLong(args[1]));
|
||||
Material material = Material.matchMaterial(args[3]);
|
||||
int amount = (int) parseLong(args[4]);
|
||||
long offer = CoalMoney.parsePositive(args[5]);
|
||||
if (npc.isEmpty() || !npc.get().trader() || material == null || amount <= 0 || offer <= 0L) {
|
||||
player.sendMessage(plugin.messages().text("&cInvalid trader, material, amount, or offer."));
|
||||
return;
|
||||
}
|
||||
NpcService.HaggleQuote quote = plugin.npcService().quote(npc.get(), args[2], material, amount, offer);
|
||||
if (!quote.reason().isEmpty()) {
|
||||
player.sendMessage(plugin.messages().text("&c" + quote.reason()));
|
||||
return;
|
||||
}
|
||||
String prompt = "NPC " + npc.get().name() + " is haggling. Mode " + quote.mode()
|
||||
+ ", material " + material.name() + ", amount " + amount
|
||||
+ ", player offer " + CoalMoney.format(offer) + " (" + offer + " coal cents), acceptable range "
|
||||
+ CoalMoney.format(quote.floor()) + " to " + CoalMoney.format(quote.ceiling()) + " (" + quote.floor() + " to " + quote.ceiling() + " coal cents)"
|
||||
+ ", deterministic counter " + CoalMoney.format(quote.counter()) + " (" + quote.counter() + " coal cents). Reply with JSON using counter as an integer coal-cent amount.";
|
||||
player.sendMessage(plugin.messages().text("&7" + npc.get().name() + " considers the offer..."));
|
||||
plugin.openRouterService().haggle(prompt, quote.floor(), quote.ceiling()).thenAccept(ai -> plugin.getServer().getScheduler().runTask(plugin, () -> {
|
||||
try {
|
||||
long finalPrice = ai.map(OpenRouterService.HaggleAiResult::counter).orElse(quote.counter());
|
||||
boolean accepted = quote.accepted() || ai.map(OpenRouterService.HaggleAiResult::accept).orElse(false) && offer == finalPrice;
|
||||
String line = ai.map(OpenRouterService.HaggleAiResult::message)
|
||||
.orElse(accepted ? "Deal." : "I can do " + CoalMoney.format(finalPrice) + ".");
|
||||
player.sendMessage(plugin.messages().text("&e" + npc.get().name() + ": &f" + line));
|
||||
if (accepted) {
|
||||
if (plugin.npcService().completeTrade(player, npc.get(), quote, offer)) {
|
||||
player.sendMessage(plugin.messages().text("&aTrade complete for &f" + CoalMoney.format(offer) + "&a."));
|
||||
} else {
|
||||
player.sendMessage(plugin.messages().text("&cTrade failed. Check balance, stock, and inventory."));
|
||||
}
|
||||
} else {
|
||||
player.sendMessage(plugin.messages().text("&7Counter offer: &f" + CoalMoney.format(finalPrice) + "&7. Run the haggle command again with that offer to accept."));
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
player.sendMessage(plugin.messages().text("&cNPC trade failed."));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void usage(CommandSender sender) {
|
||||
sender.sendMessage(plugin.messages().text("&eUsage: /cgnpc <zone|create|remove|list|stock|funds|haggle>"));
|
||||
}
|
||||
|
||||
private long parseLong(String raw) {
|
||||
try {
|
||||
return Long.parseLong(raw);
|
||||
} catch (NumberFormatException exception) {
|
||||
return -1L;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.librewiki.coalgov.command;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.model.Permit;
|
||||
import com.librewiki.coalgov.service.PermitService;
|
||||
import com.librewiki.coalgov.util.CoalMoney;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
public final class PermitCommand implements CommandExecutor {
|
||||
private final CoalGovPlugin plugin;
|
||||
|
||||
public PermitCommand(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage("Players only.");
|
||||
return true;
|
||||
}
|
||||
if (args.length == 0) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /permit <buy|list>"));
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
plugin.economyService().ensurePlayer(player);
|
||||
switch (args[0].toLowerCase()) {
|
||||
case "buy" -> buy(player, args);
|
||||
case "list" -> list(player);
|
||||
default -> player.sendMessage(plugin.messages().text("&eUsage: /permit <buy|list>"));
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
plugin.getLogger().warning("Permit command failed: " + exception.getMessage());
|
||||
player.sendMessage(plugin.messages().text("&cPermit registry unavailable."));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void buy(Player player, String[] args) throws SQLException {
|
||||
if (args.length != 3 || !args[1].equalsIgnoreCase("mining")) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /permit buy mining <regionName>"));
|
||||
return;
|
||||
}
|
||||
PermitService.BuyResult result = plugin.permitService().buyMining(player.getUniqueId(), args[2]);
|
||||
if (!result.success()) {
|
||||
player.sendMessage(plugin.messages().text("&c" + result.message()));
|
||||
return;
|
||||
}
|
||||
player.sendMessage(plugin.messages().text("&aMining permit #" + result.id() + " bought for &f"
|
||||
+ CoalMoney.format(result.cost()) + "&a. Expires " + Instant.ofEpochMilli(result.expiresAt()) + "."));
|
||||
}
|
||||
|
||||
private void list(Player player) throws SQLException {
|
||||
List<Permit> permits = plugin.permitService().listActive(player.getUniqueId());
|
||||
if (permits.isEmpty()) {
|
||||
player.sendMessage(plugin.messages().text("&eYou have no active permits."));
|
||||
return;
|
||||
}
|
||||
player.sendMessage(plugin.messages().text("&eActive permits:"));
|
||||
for (Permit permit : permits) {
|
||||
String expiry = permit.expiresAt() == null ? "never" : Instant.ofEpochMilli(permit.expiresAt()).toString();
|
||||
player.sendMessage(plugin.messages().raw("&7#" + permit.id() + " &f" + permit.permitType()
|
||||
+ " " + (permit.regionName() == null ? "global" : permit.regionName()) + " expires " + expiry));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.librewiki.coalgov.command;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.util.CoalMoney;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class PoliceCommand implements CommandExecutor {
|
||||
private final CoalGovPlugin plugin;
|
||||
|
||||
public PoliceCommand(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (!sender.hasPermission("coalgov.police")) {
|
||||
sender.sendMessage(plugin.messages().text("&cYou lack permission."));
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
if (args.length >= 1 && args[0].equalsIgnoreCase("fine")) {
|
||||
fine(sender, args);
|
||||
return true;
|
||||
}
|
||||
usage(sender);
|
||||
} catch (SQLException exception) {
|
||||
plugin.getLogger().warning("Police command failed: " + exception.getMessage());
|
||||
sender.sendMessage(plugin.messages().text("&cPolice records unavailable."));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void fine(CommandSender sender, String[] args) throws SQLException {
|
||||
if (args.length < 4) {
|
||||
sender.sendMessage(plugin.messages().text("&eUsage: /police fine <player> <amount> <reason>"));
|
||||
return;
|
||||
}
|
||||
OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]);
|
||||
plugin.economyService().ensurePlayer(target);
|
||||
long amount = CoalMoney.parsePositive(args[2]);
|
||||
if (amount <= 0L) {
|
||||
sender.sendMessage(plugin.messages().text("&cAmount must be greater than zero."));
|
||||
return;
|
||||
}
|
||||
String reason = String.join(" ", Arrays.copyOfRange(args, 3, args.length));
|
||||
UUID issuer = sender instanceof Player player ? player.getUniqueId() : null;
|
||||
long id = plugin.governmentService().createFine(target.getUniqueId(), issuer, amount, reason);
|
||||
sender.sendMessage(plugin.messages().text("&aFine #" + id + " issued to &f" + args[1] + " &afor &f" + CoalMoney.format(amount) + "&a."));
|
||||
if (target.isOnline() && target.getPlayer() != null) {
|
||||
target.getPlayer().sendMessage(plugin.messages().text("&cFine #" + id + ": &f" + CoalMoney.format(amount) + " &cfor " + reason + ". Use /coal fines pay " + id + "."));
|
||||
}
|
||||
}
|
||||
|
||||
private void usage(CommandSender sender) {
|
||||
sender.sendMessage(plugin.messages().text("&eUsage: /police fine <player> <amount> <reason>"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.librewiki.coalgov.listener;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.service.BuildProtectionService;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
public final class BlockBreakListener implements Listener {
|
||||
private final CoalGovPlugin plugin;
|
||||
|
||||
public BlockBreakListener(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onBlockBreak(BlockBreakEvent event) {
|
||||
Material type = event.getBlock().getType();
|
||||
try {
|
||||
if (!plugin.hasAdminBypass(event.getPlayer()) && plugin.claimMarkerService().isMarkerBlock(event.getBlock())) {
|
||||
event.setCancelled(true);
|
||||
event.getPlayer().sendMessage(plugin.messages().text("&cClaim corner markers are protected."));
|
||||
return;
|
||||
}
|
||||
|
||||
BuildProtectionService.BuildDecision decision = plugin.buildProtectionService()
|
||||
.canModify(event.getPlayer(), event.getBlock().getLocation());
|
||||
if (!decision.allowed()) {
|
||||
event.setCancelled(true);
|
||||
event.getPlayer().sendMessage(plugin.messages().text("&c" + decision.message()));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((type == Material.COAL_ORE || type == Material.DEEPSLATE_COAL_ORE)
|
||||
&& !plugin.miningService().canMineCoal(event.getPlayer(), event.getBlock().getLocation())) {
|
||||
event.setCancelled(true);
|
||||
event.getPlayer().sendMessage(plugin.messages().text("&cMining denied: this coal seam is regulated by the Ministry."));
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
event.setCancelled(true);
|
||||
plugin.getLogger().warning("Block break check failed: " + exception.getMessage());
|
||||
event.getPlayer().sendMessage(plugin.messages().text("&cLand registry unavailable. Try again later."));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.librewiki.coalgov.listener;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.service.BuildProtectionService;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
public final class BlockPlaceListener implements Listener {
|
||||
private final CoalGovPlugin plugin;
|
||||
|
||||
public BlockPlaceListener(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onBlockPlace(BlockPlaceEvent event) {
|
||||
try {
|
||||
if (!plugin.hasAdminBypass(event.getPlayer()) && plugin.claimMarkerService().isMarkerColumn(event.getBlockPlaced())) {
|
||||
event.setCancelled(true);
|
||||
event.getPlayer().sendMessage(plugin.messages().text("&cClaim corner markers cannot be covered."));
|
||||
return;
|
||||
}
|
||||
|
||||
BuildProtectionService.BuildDecision decision = plugin.buildProtectionService()
|
||||
.canModify(event.getPlayer(), event.getBlockPlaced().getLocation());
|
||||
if (!decision.allowed()) {
|
||||
event.setCancelled(true);
|
||||
event.getPlayer().sendMessage(plugin.messages().text("&c" + decision.message()));
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
event.setCancelled(true);
|
||||
plugin.getLogger().warning("Build protection check failed: " + exception.getMessage());
|
||||
event.getPlayer().sendMessage(plugin.messages().text("&cLand registry unavailable. Try again later."));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.librewiki.coalgov.listener;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
|
||||
public final class ClaimWandListener implements Listener {
|
||||
private final CoalGovPlugin plugin;
|
||||
|
||||
public ClaimWandListener(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerInteract(PlayerInteractEvent event) {
|
||||
if (!plugin.claimSelectionService().isWand(event.getItem())) {
|
||||
return;
|
||||
}
|
||||
event.setCancelled(true);
|
||||
if (event.getAction() != Action.RIGHT_CLICK_BLOCK || event.getClickedBlock() == null) {
|
||||
return;
|
||||
}
|
||||
if (event.getPlayer().isSneaking()) {
|
||||
plugin.claimSelectionService().clear(event.getPlayer());
|
||||
return;
|
||||
}
|
||||
plugin.claimSelectionService().addPoint(event.getPlayer(), event.getClickedBlock().getLocation());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.librewiki.coalgov.listener;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
|
||||
public final class DiviningRodListener implements Listener {
|
||||
private final CoalGovPlugin plugin;
|
||||
|
||||
public DiviningRodListener(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerInteract(PlayerInteractEvent event) {
|
||||
if (!plugin.diviningRodService().isRod(event.getItem())) {
|
||||
return;
|
||||
}
|
||||
event.setCancelled(true);
|
||||
if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) {
|
||||
return;
|
||||
}
|
||||
plugin.diviningRodService().scan(event.getPlayer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.librewiki.coalgov.listener;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.service.MarketService;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.ClickType;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
public final class MarketListener implements Listener {
|
||||
private final CoalGovPlugin plugin;
|
||||
|
||||
public MarketListener(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onInventoryClick(InventoryClickEvent event) {
|
||||
if (!MarketService.TITLE.equals(event.getView().getTitle())) {
|
||||
return;
|
||||
}
|
||||
event.setCancelled(true);
|
||||
if (!(event.getWhoClicked() instanceof Player player)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (plugin.marketService().isSellAllSlot(event.getRawSlot())) {
|
||||
plugin.marketService().sellAll(player);
|
||||
player.closeInventory();
|
||||
return;
|
||||
}
|
||||
ItemStack clicked = event.getCurrentItem();
|
||||
if (clicked == null || clicked.getType() == Material.AIR) {
|
||||
return;
|
||||
}
|
||||
plugin.marketService().buy(player, clicked.getType(), purchaseAmount(event.getClick()));
|
||||
} catch (SQLException exception) {
|
||||
plugin.getLogger().warning("Market click failed: " + exception.getMessage());
|
||||
player.sendMessage(plugin.messages().text("&cMarket ledger unavailable."));
|
||||
}
|
||||
}
|
||||
|
||||
private int purchaseAmount(ClickType clickType) {
|
||||
if (clickType.isShiftClick()) {
|
||||
return 64;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.librewiki.coalgov.listener;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.model.CoalNpc;
|
||||
import com.librewiki.coalgov.util.CoalMoney;
|
||||
import net.citizensnpcs.api.event.NPCRightClickEvent;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Map;
|
||||
|
||||
public final class NpcListener implements Listener {
|
||||
private final CoalGovPlugin plugin;
|
||||
|
||||
public NpcListener(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onNpcRightClick(NPCRightClickEvent event) {
|
||||
try {
|
||||
var coalNpc = plugin.npcService().findByCitizensId(event.getNPC().getId());
|
||||
if (coalNpc.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
event.setCancelled(true);
|
||||
showNpc(event.getClicker(), coalNpc.get());
|
||||
} catch (SQLException exception) {
|
||||
plugin.getLogger().warning("NPC click failed: " + exception.getMessage());
|
||||
event.getClicker().sendMessage(plugin.messages().text("&cNPC registry unavailable."));
|
||||
}
|
||||
}
|
||||
|
||||
private void showNpc(org.bukkit.entity.Player player, CoalNpc npc) throws SQLException {
|
||||
player.sendMessage(plugin.messages().text("&e" + npc.name() + " &7(" + npc.npcType().toLowerCase() + " #" + npc.id() + ")"));
|
||||
player.sendMessage(plugin.messages().text("&7Balance: &f" + CoalMoney.format(plugin.npcService().balance(npc))));
|
||||
Map<Material, Long> inventory = plugin.npcService().inventory(npc.id());
|
||||
if (inventory.isEmpty()) {
|
||||
player.sendMessage(plugin.messages().text("&7Inventory: &fempty"));
|
||||
} else {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
int shown = 0;
|
||||
for (var entry : inventory.entrySet()) {
|
||||
if (shown++ > 0) {
|
||||
builder.append(", ");
|
||||
}
|
||||
builder.append(entry.getKey().name().toLowerCase()).append(" ").append(entry.getValue());
|
||||
if (shown >= 6) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
player.sendMessage(plugin.messages().text("&7Inventory: &f" + builder));
|
||||
}
|
||||
if (npc.trader()) {
|
||||
player.sendMessage(plugin.messages().text("&7Haggle: &f/cgnpc haggle " + npc.id() + " buy <material> <amount> <offer>"));
|
||||
player.sendMessage(plugin.messages().text("&7Or sell: &f/cgnpc haggle " + npc.id() + " sell <material> <amount> <offer>"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.librewiki.coalgov.listener;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
public final class PlayerJoinListener implements Listener {
|
||||
private final CoalGovPlugin plugin;
|
||||
|
||||
public PlayerJoinListener(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
try {
|
||||
plugin.economyService().ensurePlayer(event.getPlayer());
|
||||
} catch (SQLException exception) {
|
||||
plugin.getLogger().warning("Failed to ensure player row: " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.librewiki.coalgov.listener;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.inventory.FurnaceBurnEvent;
|
||||
import org.bukkit.event.inventory.FurnaceStartSmeltEvent;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
public final class SuperFurnaceListener implements Listener {
|
||||
private final CoalGovPlugin plugin;
|
||||
|
||||
public SuperFurnaceListener(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onBlockPlace(BlockPlaceEvent event) {
|
||||
if (event.getBlockPlaced().getType() != Material.FURNACE
|
||||
|| !plugin.superFurnaceService().isItem(event.getItemInHand())) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
plugin.superFurnaceService().add(event.getBlockPlaced());
|
||||
event.getPlayer().sendMessage(plugin.messages().text("&aSuper furnace installed."));
|
||||
} catch (SQLException exception) {
|
||||
event.setCancelled(true);
|
||||
plugin.getLogger().warning("Super furnace placement failed: " + exception.getMessage());
|
||||
event.getPlayer().sendMessage(plugin.messages().text("&cFurnace registry unavailable."));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onBlockBreak(BlockBreakEvent event) {
|
||||
try {
|
||||
if (!plugin.superFurnaceService().isSuperFurnace(event.getBlock())) {
|
||||
return;
|
||||
}
|
||||
plugin.superFurnaceService().remove(event.getBlock());
|
||||
event.setDropItems(false);
|
||||
event.getBlock().getWorld().dropItemNaturally(event.getBlock().getLocation(), plugin.superFurnaceService().item());
|
||||
} catch (SQLException exception) {
|
||||
event.setCancelled(true);
|
||||
plugin.getLogger().warning("Super furnace break failed: " + exception.getMessage());
|
||||
event.getPlayer().sendMessage(plugin.messages().text("&cFurnace registry unavailable."));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onFurnaceStartSmelt(FurnaceStartSmeltEvent event) {
|
||||
try {
|
||||
if (plugin.superFurnaceService().isSuperFurnace(event.getBlock())) {
|
||||
event.setTotalCookTime(plugin.superFurnaceService().fasterCookTime(event.getTotalCookTime()));
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
plugin.getLogger().warning("Super furnace smelt check failed: " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onFurnaceBurn(FurnaceBurnEvent event) {
|
||||
try {
|
||||
if (plugin.superFurnaceService().isSuperFurnace(event.getBlock())) {
|
||||
event.setBurnTime(plugin.superFurnaceService().shorterBurnTime(event.getBurnTime()));
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
plugin.getLogger().warning("Super furnace burn check failed: " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
65
CoalGov/src/main/java/com/librewiki/coalgov/model/Claim.java
Normal file
65
CoalGov/src/main/java/com/librewiki/coalgov/model/Claim.java
Normal file
@@ -0,0 +1,65 @@
|
||||
package com.librewiki.coalgov.model;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.List;
|
||||
|
||||
public record Claim(
|
||||
long id,
|
||||
UUID ownerUuid,
|
||||
String world,
|
||||
int x1,
|
||||
int z1,
|
||||
int x2,
|
||||
int z2,
|
||||
String claimType,
|
||||
Long expiresAt,
|
||||
long taxDue,
|
||||
long createdAt,
|
||||
long purchaseCost,
|
||||
List<ClaimPoint> vertices
|
||||
) {
|
||||
public Claim(
|
||||
long id,
|
||||
UUID ownerUuid,
|
||||
String world,
|
||||
int x1,
|
||||
int z1,
|
||||
int x2,
|
||||
int z2,
|
||||
String claimType,
|
||||
Long expiresAt,
|
||||
long taxDue,
|
||||
long createdAt
|
||||
) {
|
||||
this(id, ownerUuid, world, x1, z1, x2, z2, claimType, expiresAt, taxDue, createdAt, -1L, List.of());
|
||||
}
|
||||
|
||||
public boolean contains(String worldName, int x, int z) {
|
||||
if (!world.equals(worldName) || x < x1 || x > x2 || z < z1 || z > z2) {
|
||||
return false;
|
||||
}
|
||||
if (vertices == null || vertices.size() < 3) {
|
||||
return true;
|
||||
}
|
||||
return containsPolygon(x + 0.5D, z + 0.5D);
|
||||
}
|
||||
|
||||
public boolean isPolygon() {
|
||||
return vertices != null && vertices.size() >= 3;
|
||||
}
|
||||
|
||||
private boolean containsPolygon(double x, double z) {
|
||||
boolean inside = false;
|
||||
int size = vertices.size();
|
||||
for (int i = 0, j = size - 1; i < size; j = i++) {
|
||||
ClaimPoint a = vertices.get(i);
|
||||
ClaimPoint b = vertices.get(j);
|
||||
boolean intersects = ((a.z() > z) != (b.z() > z))
|
||||
&& (x < (double) (b.x() - a.x()) * (z - a.z()) / (double) (b.z() - a.z()) + a.x());
|
||||
if (intersects) {
|
||||
inside = !inside;
|
||||
}
|
||||
}
|
||||
return inside;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.librewiki.coalgov.model;
|
||||
|
||||
public record ClaimPoint(int x, int z) {
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.librewiki.coalgov.model;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record CoalNpc(
|
||||
long id,
|
||||
int citizensId,
|
||||
UUID accountUuid,
|
||||
String npcType,
|
||||
String name,
|
||||
long zoneId,
|
||||
String world,
|
||||
double x,
|
||||
double y,
|
||||
double z
|
||||
) {
|
||||
public boolean trader() {
|
||||
return npcType.equalsIgnoreCase("TRADER");
|
||||
}
|
||||
|
||||
public boolean worker() {
|
||||
return npcType.equalsIgnoreCase("WORKER");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.librewiki.coalgov.model;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record CoalTransaction(
|
||||
long id,
|
||||
UUID fromUuid,
|
||||
UUID toUuid,
|
||||
long amount,
|
||||
String reason,
|
||||
long createdAt
|
||||
) {
|
||||
}
|
||||
17
CoalGov/src/main/java/com/librewiki/coalgov/model/Fine.java
Normal file
17
CoalGov/src/main/java/com/librewiki/coalgov/model/Fine.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.librewiki.coalgov.model;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record Fine(
|
||||
long id,
|
||||
UUID playerUuid,
|
||||
UUID issuerUuid,
|
||||
long amount,
|
||||
String reason,
|
||||
Long paidAt,
|
||||
long createdAt
|
||||
) {
|
||||
public boolean paid() {
|
||||
return paidAt != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.librewiki.coalgov.model;
|
||||
|
||||
public enum LandClass {
|
||||
FREEHOLD,
|
||||
LEASE,
|
||||
GOVERNMENT,
|
||||
PUBLIC,
|
||||
MINING_CONCESSION,
|
||||
PROTECTED_PRESERVE,
|
||||
BORDER_ZONE
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.librewiki.coalgov.model;
|
||||
|
||||
public record LandRegion(
|
||||
long id,
|
||||
String name,
|
||||
String world,
|
||||
int x1,
|
||||
int z1,
|
||||
int x2,
|
||||
int z2,
|
||||
LandClass landClass,
|
||||
long createdAt
|
||||
) {
|
||||
public boolean contains(String worldName, int x, int z) {
|
||||
return world.equals(worldName) && x >= x1 && x <= x2 && z >= z1 && z <= z2;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.librewiki.coalgov.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record NpcZone(
|
||||
long id,
|
||||
String name,
|
||||
String world,
|
||||
int x1,
|
||||
int z1,
|
||||
int x2,
|
||||
int z2,
|
||||
List<ClaimPoint> vertices
|
||||
) {
|
||||
public boolean contains(String worldName, int x, int z) {
|
||||
if (!world.equals(worldName) || x < x1 || x > x2 || z < z1 || z > z2) {
|
||||
return false;
|
||||
}
|
||||
if (vertices == null || vertices.size() < 3) {
|
||||
return true;
|
||||
}
|
||||
boolean inside = false;
|
||||
int size = vertices.size();
|
||||
double px = x + 0.5D;
|
||||
double pz = z + 0.5D;
|
||||
for (int i = 0, j = size - 1; i < size; j = i++) {
|
||||
ClaimPoint a = vertices.get(i);
|
||||
ClaimPoint b = vertices.get(j);
|
||||
boolean intersects = ((a.z() > pz) != (b.z() > pz))
|
||||
&& (px < (double) (b.x() - a.x()) * (pz - a.z()) / (double) (b.z() - a.z()) + a.x());
|
||||
if (intersects) {
|
||||
inside = !inside;
|
||||
}
|
||||
}
|
||||
return inside;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.librewiki.coalgov.model;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record Permit(
|
||||
long id,
|
||||
UUID ownerUuid,
|
||||
PermitType permitType,
|
||||
String regionName,
|
||||
Long expiresAt,
|
||||
long createdAt
|
||||
) {
|
||||
public boolean isActive(long now) {
|
||||
return expiresAt == null || expiresAt > now;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.librewiki.coalgov.model;
|
||||
|
||||
public enum PermitType {
|
||||
MINING,
|
||||
BORDER_PASS,
|
||||
GOVERNMENT_CONTRACTOR
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.librewiki.coalgov.service;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.model.Claim;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class BuildProtectionService {
|
||||
private final CoalGovPlugin plugin;
|
||||
private final ClaimService claimService;
|
||||
|
||||
public BuildProtectionService(CoalGovPlugin plugin, ClaimService claimService) {
|
||||
this.plugin = plugin;
|
||||
this.claimService = claimService;
|
||||
}
|
||||
|
||||
public BuildDecision canModify(Player player, Location location) throws SQLException {
|
||||
if (plugin.hasAdminBypass(player)) {
|
||||
return BuildDecision.allow();
|
||||
}
|
||||
Optional<Claim> claim = claimService.claimAt(location);
|
||||
if (claim.isEmpty()) {
|
||||
return BuildDecision.deny("This land is unclaimed and protected by the Ministry.");
|
||||
}
|
||||
if (!claim.get().ownerUuid().equals(player.getUniqueId())) {
|
||||
return BuildDecision.deny("This claim belongs to another citizen.");
|
||||
}
|
||||
return BuildDecision.allow();
|
||||
}
|
||||
|
||||
public record BuildDecision(boolean allowed, String message) {
|
||||
public static BuildDecision allow() {
|
||||
return new BuildDecision(true, "");
|
||||
}
|
||||
|
||||
public static BuildDecision deny(String message) {
|
||||
return new BuildDecision(false, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.librewiki.coalgov.service;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.model.Claim;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
public final class ClaimMarkerService {
|
||||
private static final Material MARKER_MATERIAL = Material.GOLD_BLOCK;
|
||||
|
||||
private final CoalGovPlugin plugin;
|
||||
private final ClaimService claimService;
|
||||
|
||||
public ClaimMarkerService(CoalGovPlugin plugin, ClaimService claimService) {
|
||||
this.plugin = plugin;
|
||||
this.claimService = claimService;
|
||||
}
|
||||
|
||||
public void placeMarkers(Claim claim) {
|
||||
World world = plugin.getServer().getWorld(claim.world());
|
||||
if (world == null) {
|
||||
return;
|
||||
}
|
||||
if (claim.isPolygon()) {
|
||||
for (var point : claim.vertices()) {
|
||||
placeMarker(world, point.x(), point.z());
|
||||
}
|
||||
return;
|
||||
}
|
||||
placeMarker(world, claim.x1(), claim.z1());
|
||||
placeMarker(world, claim.x1(), claim.z2());
|
||||
placeMarker(world, claim.x2(), claim.z1());
|
||||
placeMarker(world, claim.x2(), claim.z2());
|
||||
}
|
||||
|
||||
public void removeMarkers(Claim claim) {
|
||||
World world = plugin.getServer().getWorld(claim.world());
|
||||
if (world == null) {
|
||||
return;
|
||||
}
|
||||
if (claim.isPolygon()) {
|
||||
for (var point : claim.vertices()) {
|
||||
removeMarkerColumn(world, point.x(), point.z());
|
||||
}
|
||||
return;
|
||||
}
|
||||
removeMarkerColumn(world, claim.x1(), claim.z1());
|
||||
removeMarkerColumn(world, claim.x1(), claim.z2());
|
||||
removeMarkerColumn(world, claim.x2(), claim.z1());
|
||||
removeMarkerColumn(world, claim.x2(), claim.z2());
|
||||
}
|
||||
|
||||
public void placeAllLoadedMarkers() {
|
||||
try {
|
||||
for (Claim claim : claimService.listAll()) {
|
||||
placeMarkers(claim);
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
plugin.getLogger().warning("Failed to place claim markers: " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isMarkerBlock(Block block) throws SQLException {
|
||||
if (block.getType() != MARKER_MATERIAL) {
|
||||
return false;
|
||||
}
|
||||
return isMarkerColumn(block);
|
||||
}
|
||||
|
||||
public boolean isMarkerColumn(Block block) throws SQLException {
|
||||
return claimService.claimAt(block.getLocation())
|
||||
.map(claim -> isCorner(claim, block.getX(), block.getZ()))
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
private void placeMarker(World world, int x, int z) {
|
||||
int y = world.getHighestBlockYAt(x, z);
|
||||
Block highest = world.getBlockAt(x, y, z);
|
||||
if (highest.getType() == MARKER_MATERIAL) {
|
||||
return;
|
||||
}
|
||||
world.getBlockAt(x, y + 1, z).setType(MARKER_MATERIAL, false);
|
||||
}
|
||||
|
||||
private void removeMarkerColumn(World world, int x, int z) {
|
||||
for (int y = world.getMinHeight(); y < world.getMaxHeight(); y++) {
|
||||
Block block = world.getBlockAt(x, y, z);
|
||||
if (block.getType() == MARKER_MATERIAL) {
|
||||
block.setType(Material.AIR, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCorner(Claim claim, int x, int z) {
|
||||
if (claim.isPolygon()) {
|
||||
return claim.vertices().stream().anyMatch(point -> point.x() == x && point.z() == z);
|
||||
}
|
||||
return (x == claim.x1() || x == claim.x2()) && (z == claim.z1() || z == claim.z2());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package com.librewiki.coalgov.service;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.model.ClaimPoint;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class ClaimSelectionService {
|
||||
private final CoalGovPlugin plugin;
|
||||
private final NamespacedKey wandKey;
|
||||
private final Map<UUID, List<Location>> selections = new HashMap<>();
|
||||
private final Map<UUID, List<Location>> markerPreviews = new HashMap<>();
|
||||
|
||||
public ClaimSelectionService(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.wandKey = new NamespacedKey(plugin, "claim_wand");
|
||||
}
|
||||
|
||||
public ItemStack wand() {
|
||||
ItemStack item = new ItemStack(Material.BLAZE_ROD);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
meta.setDisplayName(plugin.messages().raw("&6CoalGov Claim Wand"));
|
||||
meta.setLore(List.of(
|
||||
plugin.messages().raw("&7Right-click blocks to add polygon points."),
|
||||
plugin.messages().raw("&7Sneak right-click to clear.")
|
||||
));
|
||||
meta.getPersistentDataContainer().set(wandKey, PersistentDataType.BYTE, (byte) 1);
|
||||
item.setItemMeta(meta);
|
||||
return item;
|
||||
}
|
||||
|
||||
public boolean isWand(ItemStack item) {
|
||||
if (item == null || item.getType() != Material.BLAZE_ROD || !item.hasItemMeta()) {
|
||||
return false;
|
||||
}
|
||||
return item.getItemMeta().getPersistentDataContainer().has(wandKey, PersistentDataType.BYTE);
|
||||
}
|
||||
|
||||
public void addPoint(Player player, Location clicked) {
|
||||
List<Location> points = selections.computeIfAbsent(player.getUniqueId(), ignored -> new ArrayList<>());
|
||||
if (!points.isEmpty() && !points.get(0).getWorld().equals(clicked.getWorld())) {
|
||||
restoreMarkerPreviews(player);
|
||||
points.clear();
|
||||
}
|
||||
points.add(clicked.toBlockLocation());
|
||||
player.sendMessage(plugin.messages().text("&aPolygon point " + points.size() + " set at "
|
||||
+ clicked.getBlockX() + ", " + clicked.getBlockZ() + "."));
|
||||
show(player);
|
||||
}
|
||||
|
||||
public void clear(Player player) {
|
||||
restoreMarkerPreviews(player);
|
||||
selections.remove(player.getUniqueId());
|
||||
player.sendMessage(plugin.messages().text("&aClaim polygon cleared."));
|
||||
}
|
||||
|
||||
public List<Location> points(Player player) {
|
||||
return selections.getOrDefault(player.getUniqueId(), List.of());
|
||||
}
|
||||
|
||||
public List<ClaimPoint> claimPoints(Player player) {
|
||||
List<ClaimPoint> points = new ArrayList<>();
|
||||
for (Location location : points(player)) {
|
||||
points.add(new ClaimPoint(location.getBlockX(), location.getBlockZ()));
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
public void consume(Player player) {
|
||||
restoreMarkerPreviews(player);
|
||||
selections.remove(player.getUniqueId());
|
||||
}
|
||||
|
||||
public void show(Player player) {
|
||||
List<Location> points = points(player);
|
||||
if (points.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
showMarkerPreviews(player, points);
|
||||
Particle.DustOptions dust = new Particle.DustOptions(Color.fromRGB(64, 210, 255), 1.4f);
|
||||
Particle.DustOptions vertexDust = new Particle.DustOptions(Color.fromRGB(255, 220, 80), 1.8f);
|
||||
for (Location point : points) {
|
||||
spawn(player, point.getWorld(), point.getBlockX(), point.getBlockZ(), vertexDust, 8);
|
||||
}
|
||||
for (int i = 1; i < points.size(); i++) {
|
||||
drawLine(player, points.get(i - 1), points.get(i), dust);
|
||||
}
|
||||
if (points.size() >= 3) {
|
||||
drawLine(player, points.get(points.size() - 1), points.get(0), dust);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawLine(Player player, Location a, Location b, Particle.DustOptions dust) {
|
||||
if (!a.getWorld().equals(b.getWorld())) {
|
||||
return;
|
||||
}
|
||||
double dx = b.getBlockX() - a.getBlockX();
|
||||
double dz = b.getBlockZ() - a.getBlockZ();
|
||||
int steps = Math.max(1, (int) Math.ceil(Math.max(Math.abs(dx), Math.abs(dz)) / 2.0D));
|
||||
for (int i = 0; i <= steps; i++) {
|
||||
double t = (double) i / (double) steps;
|
||||
int x = (int) Math.round(a.getBlockX() + dx * t);
|
||||
int z = (int) Math.round(a.getBlockZ() + dz * t);
|
||||
spawn(player, a.getWorld(), x, z, dust, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void spawn(Player player, World world, int x, int z, Particle.DustOptions dust, int count) {
|
||||
double y = world.getHighestBlockYAt(x, z) + 1.2D;
|
||||
player.spawnParticle(Particle.DUST, x + 0.5D, y, z + 0.5D, count, 0.15D, 0.2D, 0.15D, 0.0D, dust);
|
||||
}
|
||||
|
||||
private void showMarkerPreviews(Player player, List<Location> points) {
|
||||
restoreMarkerPreviews(player);
|
||||
List<Location> previews = new ArrayList<>();
|
||||
for (Location point : points) {
|
||||
World world = point.getWorld();
|
||||
if (world == null) {
|
||||
continue;
|
||||
}
|
||||
Location marker = markerLocation(world, point.getBlockX(), point.getBlockZ());
|
||||
player.sendBlockChange(marker, Material.GOLD_BLOCK.createBlockData());
|
||||
previews.add(marker);
|
||||
}
|
||||
markerPreviews.put(player.getUniqueId(), previews);
|
||||
}
|
||||
|
||||
private void restoreMarkerPreviews(Player player) {
|
||||
List<Location> previews = markerPreviews.remove(player.getUniqueId());
|
||||
if (previews == null) {
|
||||
return;
|
||||
}
|
||||
for (Location preview : previews) {
|
||||
World world = preview.getWorld();
|
||||
if (world == null) {
|
||||
continue;
|
||||
}
|
||||
Block block = world.getBlockAt(preview);
|
||||
player.sendBlockChange(preview, block.getBlockData());
|
||||
}
|
||||
}
|
||||
|
||||
private Location markerLocation(World world, int x, int z) {
|
||||
int y = world.getHighestBlockYAt(x, z);
|
||||
Block highest = world.getBlockAt(x, y, z);
|
||||
if (highest.getType() == Material.GOLD_BLOCK) {
|
||||
return highest.getLocation();
|
||||
}
|
||||
return world.getBlockAt(x, y + 1, z).getLocation();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
package com.librewiki.coalgov.service;
|
||||
|
||||
import com.librewiki.coalgov.model.Claim;
|
||||
import com.librewiki.coalgov.model.ClaimPoint;
|
||||
import com.librewiki.coalgov.storage.ClaimRepository;
|
||||
import com.librewiki.coalgov.util.CoalMoney;
|
||||
import com.librewiki.coalgov.util.Cuboid2D;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class ClaimService {
|
||||
private final ClaimRepository repository;
|
||||
private final LandService landService;
|
||||
private final EconomyService economyService;
|
||||
private FileConfiguration config;
|
||||
|
||||
public ClaimService(ClaimRepository repository, LandService landService, EconomyService economyService, FileConfiguration config) {
|
||||
this.repository = repository;
|
||||
this.landService = landService;
|
||||
this.economyService = economyService;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public void setConfig(FileConfiguration config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public BuyResult buy(UUID owner, Location center, String claimType, int radius) throws SQLException {
|
||||
String type = claimType.toLowerCase();
|
||||
if (!type.equals("homestead") && !type.equals("industrial")) {
|
||||
return BuyResult.fail("Claim type must be homestead or industrial.");
|
||||
}
|
||||
int maxRadius = config.getInt("claims." + type + ".max_radius");
|
||||
if (radius <= 0 || radius > maxRadius) {
|
||||
return BuyResult.fail("Radius must be between 1 and " + maxRadius + ".");
|
||||
}
|
||||
Cuboid2D area = new Cuboid2D(
|
||||
center.getWorld().getName(),
|
||||
center.getBlockX() - radius,
|
||||
center.getBlockZ() - radius,
|
||||
center.getBlockX() + radius,
|
||||
center.getBlockZ() + radius
|
||||
);
|
||||
if (repository.overlaps(area)) {
|
||||
return BuyResult.fail("That claim overlaps an existing claim.");
|
||||
}
|
||||
if (landService.blocksPrivateClaim(area)) {
|
||||
return BuyResult.fail("That land class cannot be claimed.");
|
||||
}
|
||||
long cost = adjustedCost(owner, type, area.area(), area);
|
||||
if (cost > 0L && !economyService.charge(owner, cost, "claim_purchase")) {
|
||||
return BuyResult.fail("You need " + CoalMoney.format(cost) + " for that claim.");
|
||||
}
|
||||
long id = repository.create(owner, area, type.toUpperCase(), null, cost, List.of());
|
||||
return BuyResult.success(id, cost);
|
||||
}
|
||||
|
||||
public BuyResult buyPolygon(UUID owner, String world, String claimType, List<ClaimPoint> vertices) throws SQLException {
|
||||
String type = claimType.toLowerCase();
|
||||
if (!type.equals("homestead") && !type.equals("industrial")) {
|
||||
return BuyResult.fail("Claim type must be homestead or industrial.");
|
||||
}
|
||||
if (vertices.size() < 3) {
|
||||
return BuyResult.fail("Use the claim wand to set at least 3 polygon points.");
|
||||
}
|
||||
Cuboid2D area = bounds(world, vertices);
|
||||
int maxRadius = config.getInt("claims." + type + ".max_radius");
|
||||
int maxWidth = maxRadius * 2 + 1;
|
||||
if ((area.x2() - area.x1() + 1) > maxWidth || (area.z2() - area.z1() + 1) > maxWidth) {
|
||||
return BuyResult.fail("Polygon bounds exceed max " + type + " size.");
|
||||
}
|
||||
if (repository.overlaps(area)) {
|
||||
return BuyResult.fail("That claim overlaps an existing claim.");
|
||||
}
|
||||
if (landService.blocksPrivateClaim(area)) {
|
||||
return BuyResult.fail("That land class cannot be claimed.");
|
||||
}
|
||||
long cost = adjustedCost(owner, type, polygonArea(vertices), area);
|
||||
if (cost > 0L && !economyService.charge(owner, cost, "claim_purchase")) {
|
||||
return BuyResult.fail("You need " + CoalMoney.format(cost) + " for that claim.");
|
||||
}
|
||||
long id = repository.create(owner, area, type.toUpperCase(), null, cost, vertices);
|
||||
return BuyResult.success(id, cost);
|
||||
}
|
||||
|
||||
public Optional<Claim> claimAt(Location location) throws SQLException {
|
||||
return repository.findAt(location.getWorld().getName(), location.getBlockX(), location.getBlockZ());
|
||||
}
|
||||
|
||||
public List<Claim> list(UUID owner) throws SQLException {
|
||||
return repository.findByOwner(owner);
|
||||
}
|
||||
|
||||
public List<Claim> listAll() throws SQLException {
|
||||
return repository.listAll();
|
||||
}
|
||||
|
||||
public Optional<Claim> findOwnedById(UUID owner, long id) throws SQLException {
|
||||
return repository.findOwnedById(owner, id);
|
||||
}
|
||||
|
||||
public SellResult sell(UUID owner, long id, long refund) throws SQLException {
|
||||
Optional<Claim> claim = repository.findOwnedById(owner, id);
|
||||
if (claim.isEmpty()) {
|
||||
return SellResult.fail("No owned claim with that id.");
|
||||
}
|
||||
if (refund < 0L && !economyService.charge(owner, Math.abs(refund), "claim_sale_depletion")) {
|
||||
return SellResult.fail("You need " + CoalMoney.format(Math.abs(refund)) + " to settle extracted land value.");
|
||||
}
|
||||
if (!repository.deleteOwned(owner, id)) {
|
||||
if (refund < 0L) {
|
||||
economyService.credit(owner, Math.abs(refund), "claim_sale_depletion_refund");
|
||||
}
|
||||
return SellResult.fail("Could not sell that claim.");
|
||||
}
|
||||
if (refund > 0L) {
|
||||
economyService.credit(owner, refund, "claim_sale");
|
||||
}
|
||||
return SellResult.success(claim.get(), refund);
|
||||
}
|
||||
|
||||
public boolean abandon(UUID owner, long id) throws SQLException {
|
||||
return repository.deleteOwned(owner, id);
|
||||
}
|
||||
|
||||
public TransferResult transfer(UUID owner, long id, UUID newOwner) throws SQLException {
|
||||
if (owner.equals(newOwner)) {
|
||||
return TransferResult.fail("You already own that claim.");
|
||||
}
|
||||
Optional<Claim> claim = repository.findOwnedById(owner, id);
|
||||
if (claim.isEmpty()) {
|
||||
return TransferResult.fail("No owned claim with that id.");
|
||||
}
|
||||
if (!repository.transferOwned(owner, id, newOwner)) {
|
||||
return TransferResult.fail("Could not transfer that claim.");
|
||||
}
|
||||
return TransferResult.success(claim.get());
|
||||
}
|
||||
|
||||
public long claimPurchaseCost(Claim claim) {
|
||||
if (claim.purchaseCost() >= 0L) {
|
||||
return claim.purchaseCost();
|
||||
}
|
||||
return assessedClaimCost(claim);
|
||||
}
|
||||
|
||||
public long assessedClaimCost(Claim claim) {
|
||||
return cost(claim.claimType().toLowerCase(), (long) (claim.x2() - claim.x1() + 1) * (claim.z2() - claim.z1() + 1));
|
||||
}
|
||||
|
||||
private long cost(String type, long area) {
|
||||
double base = config.getDouble("claims." + type + ".base_cost");
|
||||
double perBlock = config.getDouble("claims." + type + ".cost_per_block");
|
||||
return CoalMoney.fromCoalConfig(base + area * perBlock);
|
||||
}
|
||||
|
||||
private long adjustedCost(UUID owner, String type, long areaBlocks, Cuboid2D bounds) throws SQLException {
|
||||
long cost = cost(type, areaBlocks);
|
||||
if (!type.equals("homestead") || !config.getBoolean("claims.homestead.first_claim.free", true)) {
|
||||
return cost;
|
||||
}
|
||||
if (!list(owner).isEmpty()) {
|
||||
return cost;
|
||||
}
|
||||
int freeRadius = config.getInt("claims.homestead.first_claim.max_radius", 12);
|
||||
int maxWidth = freeRadius * 2 + 1;
|
||||
if ((bounds.x2() - bounds.x1() + 1) > maxWidth || (bounds.z2() - bounds.z1() + 1) > maxWidth) {
|
||||
return cost;
|
||||
}
|
||||
return 0L;
|
||||
}
|
||||
|
||||
private Cuboid2D bounds(String world, List<ClaimPoint> vertices) {
|
||||
int minX = vertices.stream().mapToInt(ClaimPoint::x).min().orElse(0);
|
||||
int maxX = vertices.stream().mapToInt(ClaimPoint::x).max().orElse(0);
|
||||
int minZ = vertices.stream().mapToInt(ClaimPoint::z).min().orElse(0);
|
||||
int maxZ = vertices.stream().mapToInt(ClaimPoint::z).max().orElse(0);
|
||||
return new Cuboid2D(world, minX, minZ, maxX, maxZ);
|
||||
}
|
||||
|
||||
private long polygonArea(List<ClaimPoint> vertices) {
|
||||
long sum = 0L;
|
||||
for (int i = 0, j = vertices.size() - 1; i < vertices.size(); j = i++) {
|
||||
ClaimPoint a = vertices.get(j);
|
||||
ClaimPoint b = vertices.get(i);
|
||||
sum += (long) a.x() * b.z() - (long) b.x() * a.z();
|
||||
}
|
||||
return Math.max(1L, Math.round(Math.abs(sum) / 2.0D));
|
||||
}
|
||||
|
||||
public record BuyResult(boolean success, String message, long id, long cost) {
|
||||
public static BuyResult fail(String message) {
|
||||
return new BuyResult(false, message, -1L, 0L);
|
||||
}
|
||||
|
||||
public static BuyResult success(long id, long cost) {
|
||||
return new BuyResult(true, "", id, cost);
|
||||
}
|
||||
}
|
||||
|
||||
public record SellResult(boolean success, String message, Claim claim, long refund) {
|
||||
public static SellResult fail(String message) {
|
||||
return new SellResult(false, message, null, 0L);
|
||||
}
|
||||
|
||||
public static SellResult success(Claim claim, long refund) {
|
||||
return new SellResult(true, "", claim, refund);
|
||||
}
|
||||
}
|
||||
|
||||
public record TransferResult(boolean success, String message, Claim claim) {
|
||||
public static TransferResult fail(String message) {
|
||||
return new TransferResult(false, message, null);
|
||||
}
|
||||
|
||||
public static TransferResult success(Claim claim) {
|
||||
return new TransferResult(true, "", claim);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.librewiki.coalgov.service;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.model.Claim;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
public final class ClaimVisualService {
|
||||
private static final int REPEATS = 12;
|
||||
private static final int PERIOD_TICKS = 10;
|
||||
private static final int STEP_BLOCKS = 2;
|
||||
|
||||
private final CoalGovPlugin plugin;
|
||||
|
||||
public ClaimVisualService(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public void show(Player player, Claim claim) {
|
||||
World world = plugin.getServer().getWorld(claim.world());
|
||||
if (world == null) {
|
||||
player.sendMessage(plugin.messages().text("&cThat claim's world is not loaded."));
|
||||
return;
|
||||
}
|
||||
if (!player.getWorld().equals(world)) {
|
||||
player.sendMessage(plugin.messages().text("&cThat claim is in world " + claim.world() + "."));
|
||||
return;
|
||||
}
|
||||
|
||||
Particle.DustOptions edgeDust = new Particle.DustOptions(Color.fromRGB(255, 196, 64), 1.4f);
|
||||
Particle.DustOptions cornerDust = new Particle.DustOptions(Color.fromRGB(255, 80, 64), 1.8f);
|
||||
new BukkitRunnable() {
|
||||
private int remaining = REPEATS;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (remaining-- <= 0 || !player.isOnline()) {
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
draw(player, world, claim, edgeDust, cornerDust);
|
||||
}
|
||||
}.runTaskTimer(plugin, 0L, PERIOD_TICKS);
|
||||
}
|
||||
|
||||
private void draw(Player player, World world, Claim claim, Particle.DustOptions edgeDust, Particle.DustOptions cornerDust) {
|
||||
if (claim.isPolygon()) {
|
||||
for (int i = 0; i < claim.vertices().size(); i++) {
|
||||
var a = claim.vertices().get(i);
|
||||
var b = claim.vertices().get((i + 1) % claim.vertices().size());
|
||||
drawLine(player, world, a.x(), a.z(), b.x(), b.z(), edgeDust);
|
||||
spawnCorner(player, world, a.x(), a.z(), cornerDust);
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (int x = claim.x1(); x <= claim.x2(); x += STEP_BLOCKS) {
|
||||
spawnEdge(player, world, x, claim.z1(), edgeDust);
|
||||
spawnEdge(player, world, x, claim.z2(), edgeDust);
|
||||
}
|
||||
for (int z = claim.z1(); z <= claim.z2(); z += STEP_BLOCKS) {
|
||||
spawnEdge(player, world, claim.x1(), z, edgeDust);
|
||||
spawnEdge(player, world, claim.x2(), z, edgeDust);
|
||||
}
|
||||
spawnCorner(player, world, claim.x1(), claim.z1(), cornerDust);
|
||||
spawnCorner(player, world, claim.x1(), claim.z2(), cornerDust);
|
||||
spawnCorner(player, world, claim.x2(), claim.z1(), cornerDust);
|
||||
spawnCorner(player, world, claim.x2(), claim.z2(), cornerDust);
|
||||
}
|
||||
|
||||
private void spawnEdge(Player player, World world, int x, int z, Particle.DustOptions dust) {
|
||||
double y = world.getHighestBlockYAt(x, z) + 1.15;
|
||||
player.spawnParticle(Particle.DUST, x + 0.5, y, z + 0.5, 1, 0.0, 0.15, 0.0, 0.0, dust);
|
||||
}
|
||||
|
||||
private void drawLine(Player player, World world, int x1, int z1, int x2, int z2, Particle.DustOptions dust) {
|
||||
double dx = x2 - x1;
|
||||
double dz = z2 - z1;
|
||||
int steps = Math.max(1, (int) Math.ceil(Math.max(Math.abs(dx), Math.abs(dz)) / STEP_BLOCKS));
|
||||
for (int i = 0; i <= steps; i++) {
|
||||
double t = (double) i / (double) steps;
|
||||
spawnEdge(player, world, (int) Math.round(x1 + dx * t), (int) Math.round(z1 + dz * t), dust);
|
||||
}
|
||||
}
|
||||
|
||||
private void spawnCorner(Player player, World world, int x, int z, Particle.DustOptions dust) {
|
||||
double y = world.getHighestBlockYAt(x, z) + 1.25;
|
||||
player.spawnParticle(Particle.DUST, x + 0.5, y, z + 0.5, 8, 0.25, 0.35, 0.25, 0.0, dust);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
package com.librewiki.coalgov.service;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class DiviningRodService {
|
||||
private static final List<Material> ORES = List.of(
|
||||
Material.COAL_ORE,
|
||||
Material.DEEPSLATE_COAL_ORE,
|
||||
Material.COPPER_ORE,
|
||||
Material.DEEPSLATE_COPPER_ORE,
|
||||
Material.IRON_ORE,
|
||||
Material.DEEPSLATE_IRON_ORE,
|
||||
Material.GOLD_ORE,
|
||||
Material.DEEPSLATE_GOLD_ORE,
|
||||
Material.REDSTONE_ORE,
|
||||
Material.DEEPSLATE_REDSTONE_ORE,
|
||||
Material.LAPIS_ORE,
|
||||
Material.DEEPSLATE_LAPIS_ORE,
|
||||
Material.DIAMOND_ORE,
|
||||
Material.DEEPSLATE_DIAMOND_ORE,
|
||||
Material.EMERALD_ORE,
|
||||
Material.DEEPSLATE_EMERALD_ORE,
|
||||
Material.NETHER_GOLD_ORE,
|
||||
Material.NETHER_QUARTZ_ORE,
|
||||
Material.ANCIENT_DEBRIS
|
||||
);
|
||||
|
||||
private final CoalGovPlugin plugin;
|
||||
private final NamespacedKey rodKey;
|
||||
private final Map<UUID, Long> lastUseMillis = new HashMap<>();
|
||||
|
||||
public DiviningRodService(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.rodKey = new NamespacedKey(plugin, "divining_rod");
|
||||
}
|
||||
|
||||
public ItemStack rod() {
|
||||
ItemStack item = new ItemStack(Material.BLAZE_ROD);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
meta.setDisplayName(plugin.messages().raw("&dCoalGov Divining Rod"));
|
||||
meta.setLore(List.of(
|
||||
plugin.messages().raw("&7Right-click to test the ground for ore."),
|
||||
plugin.messages().raw("&7Issued by the Ministry Geological Survey.")
|
||||
));
|
||||
meta.getPersistentDataContainer().set(rodKey, PersistentDataType.BYTE, (byte) 1);
|
||||
item.setItemMeta(meta);
|
||||
return item;
|
||||
}
|
||||
|
||||
public boolean isRod(ItemStack item) {
|
||||
if (item == null || item.getType() != Material.BLAZE_ROD || !item.hasItemMeta()) {
|
||||
return false;
|
||||
}
|
||||
return item.getItemMeta().getPersistentDataContainer().has(rodKey, PersistentDataType.BYTE);
|
||||
}
|
||||
|
||||
public void scan(Player player) {
|
||||
long remaining = cooldownRemainingMillis(player);
|
||||
if (remaining > 0L) {
|
||||
player.sendMessage(plugin.messages().text("&cThe rod is still settling. Try again in "
|
||||
+ Math.ceil(remaining / 1000.0D) + "s."));
|
||||
return;
|
||||
}
|
||||
lastUseMillis.put(player.getUniqueId(), System.currentTimeMillis());
|
||||
|
||||
ScanHit hit = nearestOre(player.getLocation());
|
||||
if (hit == null) {
|
||||
player.playSound(player.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.35f, 0.55f);
|
||||
player.spawnParticle(Particle.ASH, player.getLocation().add(0.0D, 1.0D, 0.0D), 10, 0.35D, 0.2D, 0.35D, 0.0D);
|
||||
player.sendMessage(plugin.messages().text("&7The divining rod stays still. No ore signal nearby."));
|
||||
return;
|
||||
}
|
||||
|
||||
double strength = 1.0D - Math.min(1.0D, hit.distance() / Math.max(1.0D, scanDepth()));
|
||||
float pitch = (float) (0.75D + strength);
|
||||
int particles = 8 + (int) Math.round(strength * 18.0D);
|
||||
player.playSound(player.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_RESONATE, 0.75f, pitch);
|
||||
player.spawnParticle(Particle.DUST, player.getLocation().add(0.0D, 1.0D, 0.0D), particles,
|
||||
0.4D, 0.25D, 0.4D, 0.0D, new Particle.DustOptions(Color.fromRGB(190, 80, 255), 1.1f));
|
||||
player.sendMessage(plugin.messages().text("&dThe divining rod pulls " + direction(hit)
|
||||
+ " toward &f" + readable(hit.material()) + " &7(" + Math.round(hit.distance()) + " blocks, "
|
||||
+ signalName(strength) + " signal)&d."));
|
||||
}
|
||||
|
||||
private long cooldownRemainingMillis(Player player) {
|
||||
long cooldownMillis = Math.max(0L, Math.round(plugin.getConfig()
|
||||
.getDouble("divining_rod.cooldown_seconds", 5.0D) * 1000.0D));
|
||||
long lastUse = lastUseMillis.getOrDefault(player.getUniqueId(), 0L);
|
||||
return Math.max(0L, cooldownMillis - (System.currentTimeMillis() - lastUse));
|
||||
}
|
||||
|
||||
private ScanHit nearestOre(Location origin) {
|
||||
World world = origin.getWorld();
|
||||
if (world == null) {
|
||||
return null;
|
||||
}
|
||||
int radius = scanRadius();
|
||||
int depth = scanDepth();
|
||||
int originX = origin.getBlockX();
|
||||
int originY = origin.getBlockY();
|
||||
int originZ = origin.getBlockZ();
|
||||
int minY = Math.max(world.getMinHeight(), originY - depth);
|
||||
ScanHit nearest = null;
|
||||
for (int x = originX - radius; x <= originX + radius; x++) {
|
||||
for (int z = originZ - radius; z <= originZ + radius; z++) {
|
||||
for (int y = originY; y >= minY; y--) {
|
||||
Material material = world.getBlockAt(x, y, z).getType();
|
||||
if (!ORES.contains(material)) {
|
||||
continue;
|
||||
}
|
||||
double distance = origin.distance(new Location(world, x + 0.5D, y + 0.5D, z + 0.5D));
|
||||
if (nearest == null || distance < nearest.distance()) {
|
||||
nearest = new ScanHit(material, x - originX, y - originY, z - originZ, distance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nearest;
|
||||
}
|
||||
|
||||
private int scanRadius() {
|
||||
return Math.max(0, plugin.getConfig().getInt("divining_rod.scan_radius", 4));
|
||||
}
|
||||
|
||||
private int scanDepth() {
|
||||
return Math.max(1, plugin.getConfig().getInt("divining_rod.scan_depth", 48));
|
||||
}
|
||||
|
||||
private String direction(ScanHit hit) {
|
||||
if (hit.dx() == 0 && hit.dz() == 0) {
|
||||
return "straight down";
|
||||
}
|
||||
String northSouth = hit.dz() < 0 ? "north" : hit.dz() > 0 ? "south" : "";
|
||||
String eastWest = hit.dx() < 0 ? "west" : hit.dx() > 0 ? "east" : "";
|
||||
if (northSouth.isEmpty()) {
|
||||
return eastWest + " and down";
|
||||
}
|
||||
if (eastWest.isEmpty()) {
|
||||
return northSouth + " and down";
|
||||
}
|
||||
return northSouth + "-" + eastWest + " and down";
|
||||
}
|
||||
|
||||
private String signalName(double strength) {
|
||||
if (strength >= 0.75D) {
|
||||
return "strong";
|
||||
}
|
||||
if (strength >= 0.45D) {
|
||||
return "steady";
|
||||
}
|
||||
return "faint";
|
||||
}
|
||||
|
||||
private String readable(Material material) {
|
||||
return material.name().toLowerCase().replace('_', ' ');
|
||||
}
|
||||
|
||||
private record ScanHit(Material material, int dx, int dy, int dz, double distance) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.librewiki.coalgov.service;
|
||||
|
||||
import com.librewiki.coalgov.storage.EconomyRepository;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class EconomyService {
|
||||
private final EconomyRepository repository;
|
||||
private final long startingBalance;
|
||||
|
||||
public EconomyService(EconomyRepository repository, long startingBalance) {
|
||||
this.repository = repository;
|
||||
this.startingBalance = startingBalance;
|
||||
}
|
||||
|
||||
public void ensurePlayer(OfflinePlayer player) throws SQLException {
|
||||
repository.ensurePlayer(player, startingBalance);
|
||||
}
|
||||
|
||||
public void ensureAccount(UUID uuid, String name) throws SQLException {
|
||||
repository.ensureAccount(uuid, name, startingBalance);
|
||||
}
|
||||
|
||||
public long balance(UUID uuid) throws SQLException {
|
||||
return repository.balance(uuid);
|
||||
}
|
||||
|
||||
public boolean deposit(UUID uuid, long amount) throws SQLException {
|
||||
return repository.add(uuid, amount, null, uuid, "deposit");
|
||||
}
|
||||
|
||||
public boolean withdraw(UUID uuid, long amount) throws SQLException {
|
||||
return repository.subtract(uuid, amount, uuid, null, "withdrawal");
|
||||
}
|
||||
|
||||
public boolean pay(UUID from, UUID to, long amount) throws SQLException {
|
||||
return repository.transfer(from, to, amount, "payment");
|
||||
}
|
||||
|
||||
public boolean grant(UUID uuid, long amount) throws SQLException {
|
||||
return repository.add(uuid, amount, null, uuid, "admin_grant");
|
||||
}
|
||||
|
||||
public boolean credit(UUID uuid, long amount, String reason) throws SQLException {
|
||||
if (amount <= 0) {
|
||||
return true;
|
||||
}
|
||||
return repository.add(uuid, amount, null, uuid, reason);
|
||||
}
|
||||
|
||||
public boolean take(UUID uuid, long amount) throws SQLException {
|
||||
return repository.subtract(uuid, amount, uuid, null, "admin_take");
|
||||
}
|
||||
|
||||
public boolean charge(UUID uuid, long amount, String reason) throws SQLException {
|
||||
return repository.subtract(uuid, amount, uuid, null, reason);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package com.librewiki.coalgov.service;
|
||||
|
||||
import com.librewiki.coalgov.model.Fine;
|
||||
import com.librewiki.coalgov.storage.GovernmentRepository;
|
||||
import com.librewiki.coalgov.util.CoalMoney;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class GovernmentService {
|
||||
private final GovernmentRepository repository;
|
||||
private final EconomyService economyService;
|
||||
private FileConfiguration config;
|
||||
|
||||
public GovernmentService(GovernmentRepository repository, EconomyService economyService, FileConfiguration config) {
|
||||
this.repository = repository;
|
||||
this.economyService = economyService;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public void setConfig(FileConfiguration config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public long treasuryBalance() throws SQLException {
|
||||
return repository.treasuryBalance();
|
||||
}
|
||||
|
||||
public boolean grantFromTreasury(UUID target, long amount) throws SQLException {
|
||||
if (amount <= 0L || !repository.spendTreasury(amount)) {
|
||||
return false;
|
||||
}
|
||||
if (!economyService.credit(target, amount, "treasury_grant")) {
|
||||
repository.addTreasury(amount);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public long marketSaleTax(UUID playerUuid, long gross) throws SQLException {
|
||||
return transactionTax(playerUuid, gross, "government.taxes.market_sale_percent");
|
||||
}
|
||||
|
||||
public long marketPurchaseTax(UUID playerUuid, long gross) throws SQLException {
|
||||
return transactionTax(playerUuid, gross, "government.taxes.market_purchase_percent");
|
||||
}
|
||||
|
||||
public void collectTax(long amount) throws SQLException {
|
||||
repository.addTreasury(amount);
|
||||
}
|
||||
|
||||
public boolean taxExempt(UUID uuid) throws SQLException {
|
||||
return repository.taxExempt(uuid);
|
||||
}
|
||||
|
||||
public void setTaxExempt(UUID uuid, boolean exempt) throws SQLException {
|
||||
repository.setTaxExempt(uuid, exempt);
|
||||
}
|
||||
|
||||
public long createFine(UUID playerUuid, UUID issuerUuid, long amount, String reason) throws SQLException {
|
||||
if (amount <= 0L) {
|
||||
return -1L;
|
||||
}
|
||||
return repository.createFine(playerUuid, issuerUuid, amount, reason);
|
||||
}
|
||||
|
||||
public List<Fine> unpaidFines(UUID playerUuid) throws SQLException {
|
||||
return repository.unpaidFines(playerUuid);
|
||||
}
|
||||
|
||||
public PayFineResult payFine(UUID playerUuid, long id) throws SQLException {
|
||||
Optional<Fine> fine = repository.findUnpaidFine(playerUuid, id);
|
||||
if (fine.isEmpty()) {
|
||||
return PayFineResult.fail("No unpaid fine with that id.");
|
||||
}
|
||||
return pay(playerUuid, fine.get());
|
||||
}
|
||||
|
||||
public PayFineResult payAllFines(UUID playerUuid) throws SQLException {
|
||||
List<Fine> fines = repository.unpaidFines(playerUuid);
|
||||
if (fines.isEmpty()) {
|
||||
return PayFineResult.fail("You have no unpaid fines.");
|
||||
}
|
||||
long total = fines.stream().mapToLong(Fine::amount).sum();
|
||||
if (economyService.balance(playerUuid) < total) {
|
||||
return PayFineResult.fail("You need " + CoalMoney.format(total) + " to pay all fines.");
|
||||
}
|
||||
long paid = 0L;
|
||||
for (Fine fine : fines) {
|
||||
PayFineResult result = pay(playerUuid, fine);
|
||||
if (!result.success()) {
|
||||
return paid > 0L
|
||||
? PayFineResult.success(paid, "Paid " + CoalMoney.format(paid) + " before one fine failed.")
|
||||
: result;
|
||||
}
|
||||
paid += result.amount();
|
||||
}
|
||||
return PayFineResult.success(paid, "");
|
||||
}
|
||||
|
||||
private PayFineResult pay(UUID playerUuid, Fine fine) throws SQLException {
|
||||
if (!economyService.charge(playerUuid, fine.amount(), "fine_payment")) {
|
||||
return PayFineResult.fail("Balance too low.");
|
||||
}
|
||||
if (!repository.markFinePaid(fine.id())) {
|
||||
economyService.credit(playerUuid, fine.amount(), "fine_payment_refund");
|
||||
return PayFineResult.fail("Fine payment failed.");
|
||||
}
|
||||
repository.addTreasury(fine.amount());
|
||||
return PayFineResult.success(fine.amount(), "");
|
||||
}
|
||||
|
||||
private long transactionTax(UUID playerUuid, long gross, String configPath) throws SQLException {
|
||||
if (gross <= 0L || repository.taxExempt(playerUuid)) {
|
||||
return 0L;
|
||||
}
|
||||
double percent = Math.max(0.0D, config.getDouble(configPath, 0.0D));
|
||||
if (percent <= 0.0D) {
|
||||
return 0L;
|
||||
}
|
||||
return Math.min(gross, Math.max(0L, Math.round(gross * percent / 100.0D)));
|
||||
}
|
||||
|
||||
public record PayFineResult(boolean success, String message, long amount) {
|
||||
public static PayFineResult fail(String message) {
|
||||
return new PayFineResult(false, message, 0L);
|
||||
}
|
||||
|
||||
public static PayFineResult success(long amount, String message) {
|
||||
return new PayFineResult(true, message, amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package com.librewiki.coalgov.service;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.model.Claim;
|
||||
import com.librewiki.coalgov.util.CoalMoney;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class LandAppraisalService {
|
||||
private final CoalGovPlugin plugin;
|
||||
private final ClaimService claimService;
|
||||
|
||||
public LandAppraisalService(CoalGovPlugin plugin, ClaimService claimService) {
|
||||
this.plugin = plugin;
|
||||
this.claimService = claimService;
|
||||
}
|
||||
|
||||
public Appraisal appraise(Claim claim) {
|
||||
World world = plugin.getServer().getWorld(claim.world());
|
||||
if (world == null) {
|
||||
long base = claimService.claimPurchaseCost(claim);
|
||||
return new Appraisal(base, claimService.assessedClaimCost(claim), 0.0D, 0.0D, 0.0D, 0.0D, 0L, Map.of(), false);
|
||||
}
|
||||
|
||||
Map<Material, Double> prices = resourcePrices();
|
||||
Map<Material, Long> resources = new LinkedHashMap<>();
|
||||
long solid = 0L;
|
||||
long checked = 0L;
|
||||
double resourceValue = 0.0D;
|
||||
|
||||
for (int x = claim.x1(); x <= claim.x2(); x++) {
|
||||
for (int z = claim.z1(); z <= claim.z2(); z++) {
|
||||
int top = world.getHighestBlockYAt(x, z);
|
||||
for (int y = world.getMinHeight(); y <= top; y++) {
|
||||
Block block = world.getBlockAt(x, y, z);
|
||||
Material type = block.getType();
|
||||
checked++;
|
||||
if (!type.isAir()) {
|
||||
solid++;
|
||||
}
|
||||
resourceValue += addResource(resources, prices, type, 1L);
|
||||
resourceValue += inventoryResourceValue(block, resources, prices);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double intactRatio = checked == 0L ? 1.0D : (double) solid / (double) checked;
|
||||
double minMultiplier = plugin.getConfig().getDouble("claims.resale.min_condition_multiplier", 0.25D);
|
||||
minMultiplier = Math.max(0.0D, Math.min(1.0D, minMultiplier));
|
||||
double conditionMultiplier = minMultiplier + ((1.0D - minMultiplier) * intactRatio);
|
||||
long originalCost = claimService.claimPurchaseCost(claim);
|
||||
long assessedCost = claimService.assessedClaimCost(claim);
|
||||
double conditionValue = originalCost * conditionMultiplier;
|
||||
double depletionCharge = originalCost == 0L ? assessedCost * (1.0D - conditionMultiplier) : 0.0D;
|
||||
long total = Math.round(conditionValue + resourceValue - depletionCharge);
|
||||
return new Appraisal(originalCost, assessedCost, conditionMultiplier, conditionValue, resourceValue, depletionCharge, total, resources, true);
|
||||
}
|
||||
|
||||
private double inventoryResourceValue(Block block, Map<Material, Long> resources, Map<Material, Double> prices) {
|
||||
BlockState state = block.getState();
|
||||
if (!(state instanceof InventoryHolder holder)) {
|
||||
return 0.0D;
|
||||
}
|
||||
double value = 0.0D;
|
||||
for (ItemStack item : holder.getInventory().getContents()) {
|
||||
if (item == null || item.getType().isAir()) {
|
||||
continue;
|
||||
}
|
||||
value += addResource(resources, prices, item.getType(), item.getAmount());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private double addResource(Map<Material, Long> resources, Map<Material, Double> prices, Material material, long amount) {
|
||||
Double price = prices.get(material);
|
||||
if (price == null || amount <= 0L) {
|
||||
return 0.0D;
|
||||
}
|
||||
resources.merge(material, amount, Long::sum);
|
||||
return CoalMoney.fromCoal(price) * amount;
|
||||
}
|
||||
|
||||
private Map<Material, Double> resourcePrices() {
|
||||
Map<Material, Double> prices = defaultResourcePrices();
|
||||
ConfigurationSection section = plugin.getConfig().getConfigurationSection("claims.resale.resource_values");
|
||||
if (section == null) {
|
||||
return prices;
|
||||
}
|
||||
for (String key : section.getKeys(false)) {
|
||||
Material material = Material.matchMaterial(key);
|
||||
if (material != null) {
|
||||
prices.put(material, section.getDouble(key));
|
||||
}
|
||||
}
|
||||
return prices;
|
||||
}
|
||||
|
||||
private Map<Material, Double> defaultResourcePrices() {
|
||||
Map<Material, Double> prices = new LinkedHashMap<>();
|
||||
prices.put(Material.COBBLESTONE, 0.02D);
|
||||
prices.put(Material.COAL, 1.0D);
|
||||
prices.put(Material.COAL_BLOCK, 9.0D);
|
||||
prices.put(Material.RAW_COPPER, 0.5D);
|
||||
prices.put(Material.COPPER_INGOT, 1.0D);
|
||||
prices.put(Material.RAW_IRON, 1.0D);
|
||||
prices.put(Material.IRON_INGOT, 2.0D);
|
||||
prices.put(Material.RAW_GOLD, 2.0D);
|
||||
prices.put(Material.GOLD_INGOT, 4.0D);
|
||||
prices.put(Material.DIAMOND, 16.0D);
|
||||
prices.put(Material.EMERALD, 12.0D);
|
||||
return prices;
|
||||
}
|
||||
|
||||
public record Appraisal(
|
||||
long originalCost,
|
||||
long assessedCost,
|
||||
double conditionMultiplier,
|
||||
double conditionValue,
|
||||
double rawResourceValue,
|
||||
double depletionCharge,
|
||||
long totalValue,
|
||||
Map<Material, Long> resources,
|
||||
boolean worldLoaded
|
||||
) {
|
||||
public long resourceValue() {
|
||||
return Math.max(0L, Math.round(rawResourceValue));
|
||||
}
|
||||
|
||||
public long depletionChargeRounded() {
|
||||
return Math.max(0L, Math.round(depletionCharge));
|
||||
}
|
||||
|
||||
public int conditionPercent() {
|
||||
return (int) Math.round(conditionMultiplier * 100.0D);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.librewiki.coalgov.service;
|
||||
|
||||
import com.librewiki.coalgov.model.LandClass;
|
||||
import com.librewiki.coalgov.model.LandRegion;
|
||||
import com.librewiki.coalgov.storage.LandRepository;
|
||||
import com.librewiki.coalgov.util.Cuboid2D;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class LandService {
|
||||
private final LandRepository repository;
|
||||
private FileConfiguration config;
|
||||
|
||||
public LandService(LandRepository repository, FileConfiguration config) {
|
||||
this.repository = repository;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public void setConfig(FileConfiguration config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public Optional<LandRegion> regionAt(Location location) throws SQLException {
|
||||
return repository.findAt(location.getWorld().getName(), location.getBlockX(), location.getBlockZ());
|
||||
}
|
||||
|
||||
public LandClass classAt(Location location) throws SQLException {
|
||||
return regionAt(location).map(LandRegion::landClass).orElse(defaultLandClass());
|
||||
}
|
||||
|
||||
public Optional<LandRegion> findByName(String name) throws SQLException {
|
||||
return repository.findByName(name);
|
||||
}
|
||||
|
||||
public boolean create(String name, Cuboid2D area, LandClass landClass) throws SQLException {
|
||||
return repository.create(name, area, landClass);
|
||||
}
|
||||
|
||||
public boolean delete(String name) throws SQLException {
|
||||
return repository.delete(name);
|
||||
}
|
||||
|
||||
public List<LandRegion> listAll() throws SQLException {
|
||||
return repository.listAll();
|
||||
}
|
||||
|
||||
public boolean blocksPrivateClaim(Cuboid2D area) throws SQLException {
|
||||
return repository.overlaps(
|
||||
area,
|
||||
LandClass.GOVERNMENT,
|
||||
LandClass.PUBLIC,
|
||||
LandClass.MINING_CONCESSION,
|
||||
LandClass.PROTECTED_PRESERVE,
|
||||
LandClass.BORDER_ZONE
|
||||
);
|
||||
}
|
||||
|
||||
private LandClass defaultLandClass() {
|
||||
try {
|
||||
return LandClass.valueOf(config.getString("land.default_land_class", "FREEHOLD").toUpperCase());
|
||||
} catch (IllegalArgumentException exception) {
|
||||
return LandClass.FREEHOLD;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
package com.librewiki.coalgov.service;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.storage.MarketRepository;
|
||||
import com.librewiki.coalgov.util.CoalMoney;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public final class MarketService {
|
||||
public static final String TITLE = "Ministry Exchange";
|
||||
private static final int SELL_ALL_SLOT = 4;
|
||||
private final CoalGovPlugin plugin;
|
||||
private final MarketRepository repository;
|
||||
|
||||
public MarketService(CoalGovPlugin plugin, MarketRepository repository) {
|
||||
this.plugin = plugin;
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public void open(Player player) throws SQLException {
|
||||
Inventory inventory = Bukkit.createInventory(null, 45, TITLE);
|
||||
inventory.setItem(SELL_ALL_SLOT, button(Material.EMERALD, "&aSell appraised resources", List.of(
|
||||
"&7Sells all priced resources in your inventory.",
|
||||
"&7Price rises when Ministry stock is low."
|
||||
)));
|
||||
int slot = 9;
|
||||
for (Material material : basePrices().keySet()) {
|
||||
if (slot >= inventory.getSize()) {
|
||||
break;
|
||||
}
|
||||
Quote quote = quote(material);
|
||||
inventory.setItem(slot++, button(material, "&eBuy " + readable(material), List.of(
|
||||
"&7Click: buy 1",
|
||||
"&7Shift-click: buy 64",
|
||||
"&7Stock: " + quote.stock(),
|
||||
"&7Buy 1: " + CoalMoney.format(buyCost(quote, 1)),
|
||||
"&7Buy 64: " + CoalMoney.format(buyCost(quote, 64)),
|
||||
"&7Sell: " + CoalMoney.format(CoalMoney.fromCoal(quote.sellUnitPrice() * 64.0D)) + " per 64"
|
||||
)));
|
||||
}
|
||||
player.openInventory(inventory);
|
||||
}
|
||||
|
||||
public void sellAll(Player player) throws SQLException {
|
||||
Map<Material, Double> prices = basePrices();
|
||||
Map<Material, Long> simulatedStock = new LinkedHashMap<>();
|
||||
Map<Material, Long> sold = new LinkedHashMap<>();
|
||||
long grossCredit = 0L;
|
||||
for (ItemStack item : player.getInventory().getStorageContents()) {
|
||||
if (item == null || item.getType().isAir()) {
|
||||
continue;
|
||||
}
|
||||
Material material = item.getType();
|
||||
Double basePrice = prices.get(material);
|
||||
if (basePrice == null) {
|
||||
continue;
|
||||
}
|
||||
long stock = simulatedStock.containsKey(material)
|
||||
? simulatedStock.get(material)
|
||||
: repository.stock(material, initialStock(material));
|
||||
grossCredit += sellCredit(material, basePrice, stock, item.getAmount());
|
||||
simulatedStock.put(material, stock + item.getAmount());
|
||||
sold.merge(material, (long) item.getAmount(), Long::sum);
|
||||
}
|
||||
if (grossCredit <= 0L) {
|
||||
player.sendMessage(plugin.messages().text("&cNo appraised resources with enough value found in your inventory."));
|
||||
return;
|
||||
}
|
||||
long tax = plugin.governmentService().marketSaleTax(player.getUniqueId(), grossCredit);
|
||||
long credit = Math.max(0L, grossCredit - tax);
|
||||
plugin.economyService().credit(player.getUniqueId(), credit, "market_sale");
|
||||
plugin.governmentService().collectTax(tax);
|
||||
removeSoldItems(player, sold);
|
||||
for (var entry : sold.entrySet()) {
|
||||
repository.addStock(entry.getKey(), entry.getValue());
|
||||
}
|
||||
if (tax > 0L) {
|
||||
player.sendMessage(plugin.messages().text("&aSold resources for &f" + CoalMoney.format(credit) + " &aafter &f" + CoalMoney.format(tax) + " &atax."));
|
||||
} else {
|
||||
player.sendMessage(plugin.messages().text("&aSold resources for &f" + CoalMoney.format(credit) + "&a."));
|
||||
}
|
||||
}
|
||||
|
||||
public void buy(Player player, Material material, int amount) throws SQLException {
|
||||
if (!basePrices().containsKey(material)) {
|
||||
return;
|
||||
}
|
||||
if (amount <= 0) {
|
||||
player.sendMessage(plugin.messages().text("&cInvalid purchase amount."));
|
||||
return;
|
||||
}
|
||||
Quote quote = quote(material);
|
||||
if (quote.stock() < amount) {
|
||||
player.sendMessage(plugin.messages().text("&cThe Ministry has fewer than " + amount + " " + readable(material) + " in stock."));
|
||||
return;
|
||||
}
|
||||
long cost = buyCost(quote, amount);
|
||||
long tax = plugin.governmentService().marketPurchaseTax(player.getUniqueId(), cost);
|
||||
long total = cost + tax;
|
||||
if (plugin.economyService().balance(player.getUniqueId()) < total) {
|
||||
player.sendMessage(plugin.messages().text("&cYou need " + CoalMoney.format(total) + "."));
|
||||
return;
|
||||
}
|
||||
if (!hasRoomFor(player, material, amount)) {
|
||||
player.sendMessage(plugin.messages().text("&cYou need more inventory space."));
|
||||
return;
|
||||
}
|
||||
if (!plugin.economyService().charge(player.getUniqueId(), total, "market_purchase")) {
|
||||
player.sendMessage(plugin.messages().text("&cPurchase failed."));
|
||||
return;
|
||||
}
|
||||
if (!repository.removeStock(material, amount)) {
|
||||
plugin.economyService().credit(player.getUniqueId(), total, "market_refund");
|
||||
player.sendMessage(plugin.messages().text("&cThe Ministry ran out of stock."));
|
||||
return;
|
||||
}
|
||||
if (!player.getInventory().addItem(new ItemStack(material, amount)).isEmpty()) {
|
||||
repository.addStock(material, amount);
|
||||
plugin.economyService().credit(player.getUniqueId(), total, "market_refund");
|
||||
player.sendMessage(plugin.messages().text("&cYou need more inventory space."));
|
||||
return;
|
||||
}
|
||||
plugin.governmentService().collectTax(tax);
|
||||
if (tax > 0L) {
|
||||
player.sendMessage(plugin.messages().text("&aBought " + amount + " " + readable(material) + " for &f" + CoalMoney.format(total) + "&a, including &f" + CoalMoney.format(tax) + " &atax."));
|
||||
} else {
|
||||
player.sendMessage(plugin.messages().text("&aBought " + amount + " " + readable(material) + " for &f" + CoalMoney.format(cost) + "&a."));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSellAllSlot(int slot) {
|
||||
return slot == SELL_ALL_SLOT;
|
||||
}
|
||||
|
||||
public Map<Material, Double> basePrices() {
|
||||
Map<Material, Double> prices = defaultPrices();
|
||||
ConfigurationSection section = plugin.getConfig().getConfigurationSection("market.prices");
|
||||
if (section == null) {
|
||||
section = plugin.getConfig().getConfigurationSection("claims.resale.resource_values");
|
||||
}
|
||||
if (section == null) {
|
||||
return prices;
|
||||
}
|
||||
for (String key : section.getKeys(false)) {
|
||||
Material material = Material.matchMaterial(key);
|
||||
if (material != null) {
|
||||
prices.put(material, section.getDouble(key));
|
||||
}
|
||||
}
|
||||
return prices;
|
||||
}
|
||||
|
||||
private Quote quote(Material material) throws SQLException {
|
||||
double basePrice = basePrices().get(material);
|
||||
long stock = repository.stock(material, initialStock(material));
|
||||
return quote(material, basePrice, stock);
|
||||
}
|
||||
|
||||
private Quote quote(Material material, double basePrice, long stock) {
|
||||
return new Quote(stock, sellUnitPrice(material, basePrice, stock), buyUnitPrice(material, basePrice, stock));
|
||||
}
|
||||
|
||||
private long sellCredit(Material material, double basePrice, long startingStock, int amount) {
|
||||
double total = 0.0D;
|
||||
for (int index = 0; index < amount; index++) {
|
||||
total += sellUnitPrice(material, basePrice, startingStock + index);
|
||||
}
|
||||
return Math.max(1L, CoalMoney.fromCoal(total));
|
||||
}
|
||||
|
||||
private double sellUnitPrice(Material material, double basePrice, long stock) {
|
||||
double target = Math.max(1.0D, targetStock());
|
||||
double multiplier = target / Math.max(1.0D, stock);
|
||||
multiplier = Math.max(minMultiplier(), Math.min(maxMultiplier(), multiplier));
|
||||
return Math.max(coalEquivalentFloor(material), basePrice * multiplier * sellMultiplier());
|
||||
}
|
||||
|
||||
private double buyUnitPrice(Material material, double basePrice, long stock) {
|
||||
double sellPrice = sellUnitPrice(material, basePrice, stock);
|
||||
double target = Math.max(1.0D, targetStock());
|
||||
double multiplier = target / Math.max(1.0D, stock);
|
||||
multiplier = Math.max(minMultiplier(), Math.min(maxMultiplier(), multiplier));
|
||||
return Math.max(sellPrice, basePrice * multiplier * buyMultiplier());
|
||||
}
|
||||
|
||||
private long buyCost(Quote quote, int amount) {
|
||||
return Math.max(1L, CoalMoney.fromCoal(quote.buyUnitPrice() * amount));
|
||||
}
|
||||
|
||||
private double coalEquivalentFloor(Material material) {
|
||||
if (material == Material.COAL || material == Material.CHARCOAL) {
|
||||
return 1.0D;
|
||||
}
|
||||
if (material == Material.COAL_BLOCK) {
|
||||
return 9.0D;
|
||||
}
|
||||
String name = material.name();
|
||||
if (name.endsWith("_LOG") || name.endsWith("_STEM")) {
|
||||
return 1.0D;
|
||||
}
|
||||
if (name.endsWith("_PLANKS")) {
|
||||
return 1.5D / 8.0D;
|
||||
}
|
||||
if (material == Material.STICK) {
|
||||
return 0.5D / 8.0D;
|
||||
}
|
||||
return 0.0D;
|
||||
}
|
||||
|
||||
private boolean hasRoomFor(Player player, Material material, int amount) {
|
||||
int remaining = amount;
|
||||
for (ItemStack item : player.getInventory().getStorageContents()) {
|
||||
if (item == null || item.getType().isAir()) {
|
||||
remaining -= material.getMaxStackSize();
|
||||
} else if (item.getType() == material) {
|
||||
remaining -= Math.max(0, item.getMaxStackSize() - item.getAmount());
|
||||
}
|
||||
if (remaining <= 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void removeSoldItems(Player player, Map<Material, Long> sold) {
|
||||
Map<Material, Long> remainingByMaterial = new LinkedHashMap<>(sold);
|
||||
for (ItemStack item : player.getInventory().getStorageContents()) {
|
||||
if (item == null || item.getType().isAir()) {
|
||||
continue;
|
||||
}
|
||||
Long remaining = remainingByMaterial.get(item.getType());
|
||||
if (remaining == null || remaining <= 0L) {
|
||||
continue;
|
||||
}
|
||||
if (remaining >= item.getAmount()) {
|
||||
remainingByMaterial.put(item.getType(), remaining - item.getAmount());
|
||||
item.setAmount(0);
|
||||
} else {
|
||||
item.setAmount((int) (item.getAmount() - remaining));
|
||||
remainingByMaterial.put(item.getType(), 0L);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long initialStock() {
|
||||
return plugin.getConfig().getLong("market.dynamic.initial_stock", 1024L);
|
||||
}
|
||||
|
||||
private long initialStock(Material material) {
|
||||
return plugin.getConfig().getLong("market.initial_stock." + material.name(), initialStock());
|
||||
}
|
||||
|
||||
private long targetStock() {
|
||||
return plugin.getConfig().getLong("market.dynamic.target_stock", 1024L);
|
||||
}
|
||||
|
||||
private double minMultiplier() {
|
||||
return plugin.getConfig().getDouble("market.dynamic.min_multiplier", 0.35D);
|
||||
}
|
||||
|
||||
private double maxMultiplier() {
|
||||
return plugin.getConfig().getDouble("market.dynamic.max_multiplier", 3.0D);
|
||||
}
|
||||
|
||||
private double sellMultiplier() {
|
||||
return plugin.getConfig().getDouble("market.dynamic.sell_multiplier", 0.85D);
|
||||
}
|
||||
|
||||
private double buyMultiplier() {
|
||||
return plugin.getConfig().getDouble("market.dynamic.buy_multiplier", 1.15D);
|
||||
}
|
||||
|
||||
private ItemStack button(Material material, String name, List<String> lore) {
|
||||
ItemStack item = new ItemStack(material);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
meta.setDisplayName(plugin.messages().raw(name));
|
||||
meta.setLore(lore.stream().map(plugin.messages()::raw).toList());
|
||||
item.setItemMeta(meta);
|
||||
return item;
|
||||
}
|
||||
|
||||
private Map<Material, Double> defaultPrices() {
|
||||
Map<Material, Double> prices = new LinkedHashMap<>();
|
||||
prices.put(Material.DIRT, 0.01D);
|
||||
prices.put(Material.COBBLESTONE, 0.02D);
|
||||
prices.put(Material.SAND, 0.05D);
|
||||
prices.put(Material.GRAVEL, 0.04D);
|
||||
prices.put(Material.OAK_LOG, 0.25D);
|
||||
prices.put(Material.SPRUCE_LOG, 0.25D);
|
||||
prices.put(Material.BIRCH_LOG, 0.25D);
|
||||
prices.put(Material.TORCH, 0.10D);
|
||||
prices.put(Material.COAL, 1.0D);
|
||||
prices.put(Material.COAL_BLOCK, 9.0D);
|
||||
prices.put(Material.RAW_COPPER, 0.5D);
|
||||
prices.put(Material.IRON_INGOT, 2.0D);
|
||||
prices.put(Material.GOLD_INGOT, 4.0D);
|
||||
prices.put(Material.DIAMOND, 16.0D);
|
||||
prices.put(Material.EMERALD, 12.0D);
|
||||
prices.put(Material.WHEAT, 0.35D);
|
||||
prices.put(Material.BREAD, 1.25D);
|
||||
prices.put(Material.APPLE, 0.75D);
|
||||
prices.put(Material.CARROT, 0.25D);
|
||||
prices.put(Material.POTATO, 0.20D);
|
||||
prices.put(Material.BAKED_POTATO, 0.45D);
|
||||
prices.put(Material.BEETROOT, 0.20D);
|
||||
prices.put(Material.MELON_SLICE, 0.15D);
|
||||
prices.put(Material.SWEET_BERRIES, 0.20D);
|
||||
prices.put(Material.GLOW_BERRIES, 0.35D);
|
||||
prices.put(Material.COOKIE, 0.25D);
|
||||
prices.put(Material.PUMPKIN_PIE, 1.0D);
|
||||
prices.put(Material.COOKED_CHICKEN, 1.25D);
|
||||
prices.put(Material.COOKED_PORKCHOP, 1.5D);
|
||||
prices.put(Material.COOKED_BEEF, 1.75D);
|
||||
prices.put(Material.COOKED_MUTTON, 1.5D);
|
||||
prices.put(Material.COOKED_RABBIT, 1.25D);
|
||||
prices.put(Material.COOKED_COD, 1.0D);
|
||||
return prices;
|
||||
}
|
||||
|
||||
private String readable(Material material) {
|
||||
return material.name().toLowerCase().replace('_', ' ');
|
||||
}
|
||||
|
||||
private record Quote(long stock, double sellUnitPrice, double buyUnitPrice) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.librewiki.coalgov.service;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.model.Claim;
|
||||
import com.librewiki.coalgov.model.LandClass;
|
||||
import com.librewiki.coalgov.model.LandRegion;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class MiningService {
|
||||
private final CoalGovPlugin plugin;
|
||||
private final LandService landService;
|
||||
private final ClaimService claimService;
|
||||
private final PermitService permitService;
|
||||
|
||||
public MiningService(CoalGovPlugin plugin, LandService landService, ClaimService claimService, PermitService permitService) {
|
||||
this.plugin = plugin;
|
||||
this.landService = landService;
|
||||
this.claimService = claimService;
|
||||
this.permitService = permitService;
|
||||
}
|
||||
|
||||
public boolean canMineCoal(Player player, Location location) throws SQLException {
|
||||
if (plugin.hasAdminBypass(player)) {
|
||||
return true;
|
||||
}
|
||||
Optional<LandRegion> region = landService.regionAt(location);
|
||||
LandClass landClass = region.map(LandRegion::landClass).orElse(landService.classAt(location));
|
||||
if (landClass == LandClass.FREEHOLD) {
|
||||
Optional<Claim> claim = claimService.claimAt(location);
|
||||
return claim.isPresent() && claim.get().ownerUuid().equals(player.getUniqueId());
|
||||
}
|
||||
if (landClass == LandClass.MINING_CONCESSION) {
|
||||
return region.isPresent() && permitService.hasMiningPermit(player.getUniqueId(), region.get().name());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
package com.librewiki.coalgov.service;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.model.ClaimPoint;
|
||||
import com.librewiki.coalgov.model.CoalNpc;
|
||||
import com.librewiki.coalgov.model.NpcZone;
|
||||
import com.librewiki.coalgov.storage.NpcRepository;
|
||||
import com.librewiki.coalgov.util.CoalMoney;
|
||||
import net.citizensnpcs.api.CitizensAPI;
|
||||
import net.citizensnpcs.api.npc.NPC;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class NpcService {
|
||||
private final CoalGovPlugin plugin;
|
||||
private final NpcRepository repository;
|
||||
private final Random random = new Random();
|
||||
|
||||
public NpcService(CoalGovPlugin plugin, NpcRepository repository) {
|
||||
this.plugin = plugin;
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public long createZone(String name, String world, List<ClaimPoint> points) throws SQLException {
|
||||
return repository.createZone(name, world, points);
|
||||
}
|
||||
|
||||
public long createNpc(String type, String name, NpcZone zone, Location location) throws SQLException {
|
||||
NPC npc = CitizensAPI.getNPCRegistry().createNPC(EntityType.VILLAGER, name);
|
||||
npc.spawn(location);
|
||||
npc.setProtected(true);
|
||||
UUID accountUuid = UUID.nameUUIDFromBytes(("coalgov:npc:" + npc.getId()).getBytes(StandardCharsets.UTF_8));
|
||||
plugin.economyService().ensureAccount(accountUuid, "NPC-" + name);
|
||||
return repository.createNpc(npc.getId(), accountUuid, type, name, zone.id(),
|
||||
location.getWorld().getName(), location.getX(), location.getY(), location.getZ());
|
||||
}
|
||||
|
||||
public boolean removeNpc(long id) throws SQLException {
|
||||
Optional<CoalNpc> coalNpc = repository.findNpc(id);
|
||||
if (coalNpc.isPresent()) {
|
||||
NPC npc = CitizensAPI.getNPCRegistry().getById(coalNpc.get().citizensId());
|
||||
if (npc != null) {
|
||||
npc.destroy();
|
||||
}
|
||||
}
|
||||
return repository.deleteNpc(id);
|
||||
}
|
||||
|
||||
public Optional<CoalNpc> findNpc(long id) throws SQLException {
|
||||
return repository.findNpc(id);
|
||||
}
|
||||
|
||||
public Optional<CoalNpc> findByCitizensId(int citizensId) throws SQLException {
|
||||
return repository.findByCitizensId(citizensId);
|
||||
}
|
||||
|
||||
public Optional<NpcZone> findZone(String name) throws SQLException {
|
||||
return repository.findZone(name);
|
||||
}
|
||||
|
||||
public List<CoalNpc> listNpcs() throws SQLException {
|
||||
return repository.listNpcs();
|
||||
}
|
||||
|
||||
public List<NpcZone> listZones() throws SQLException {
|
||||
return repository.listZones();
|
||||
}
|
||||
|
||||
public Map<Material, Long> inventory(long npcId) throws SQLException {
|
||||
return repository.inventory(npcId);
|
||||
}
|
||||
|
||||
public void addInventory(long npcId, Material material, long amount) throws SQLException {
|
||||
repository.addInventory(npcId, material, amount);
|
||||
}
|
||||
|
||||
public long balance(CoalNpc npc) throws SQLException {
|
||||
return plugin.economyService().balance(npc.accountUuid());
|
||||
}
|
||||
|
||||
public HaggleQuote quote(CoalNpc npc, String mode, Material material, int amount, long offer) throws SQLException {
|
||||
double base = plugin.marketService().basePrices().getOrDefault(material, 1.0D);
|
||||
long fair = Math.max(1L, CoalMoney.fromCoal(base * amount));
|
||||
int maxDiscount = plugin.getConfig().getInt("npc.trader.max_discount_percent", 15);
|
||||
int maxSurcharge = plugin.getConfig().getInt("npc.trader.max_surcharge_percent", 20);
|
||||
if (mode.equalsIgnoreCase("buy")) {
|
||||
long stock = repository.inventoryAmount(npc.id(), material);
|
||||
long listPrice = Math.max(1L, Math.round(fair * 1.10D));
|
||||
long minimum = Math.max(1L, Math.round(listPrice * (100.0D - maxDiscount) / 100.0D));
|
||||
long counter = Math.max(minimum, Math.min(listPrice, offer <= 0L ? listPrice : Math.round((offer + listPrice) / 2.0D)));
|
||||
boolean possible = stock >= amount;
|
||||
boolean accepted = possible && offer >= minimum;
|
||||
return new HaggleQuote(mode.toLowerCase(), material, amount, offer, minimum, listPrice, counter, accepted, possible ? "" : "I do not have enough stock.");
|
||||
}
|
||||
long maximum = Math.max(1L, Math.round(fair * (100.0D + maxSurcharge) / 100.0D));
|
||||
long target = Math.max(1L, Math.round(fair * 0.90D));
|
||||
long counter = Math.min(maximum, Math.max(target, offer <= 0L ? target : Math.round((offer + target) / 2.0D)));
|
||||
boolean possible = balance(npc) >= offer;
|
||||
boolean accepted = possible && offer <= maximum;
|
||||
return new HaggleQuote(mode.toLowerCase(), material, amount, offer, target, maximum, counter, accepted, possible ? "" : "I do not have enough coal.");
|
||||
}
|
||||
|
||||
public boolean completeTrade(Player player, CoalNpc npc, HaggleQuote quote, long finalPrice) throws SQLException {
|
||||
if (quote.mode().equals("buy")) {
|
||||
if (repository.inventoryAmount(npc.id(), quote.material()) < quote.amount()) {
|
||||
return false;
|
||||
}
|
||||
if (!plugin.economyService().charge(player.getUniqueId(), finalPrice, "npc_purchase")) {
|
||||
return false;
|
||||
}
|
||||
repository.removeInventory(npc.id(), quote.material(), quote.amount());
|
||||
plugin.economyService().credit(npc.accountUuid(), finalPrice, "npc_sale");
|
||||
player.getInventory().addItem(new ItemStack(quote.material(), quote.amount()));
|
||||
return true;
|
||||
}
|
||||
if (!playerHas(player, quote.material(), quote.amount()) || balance(npc) < finalPrice) {
|
||||
return false;
|
||||
}
|
||||
if (!plugin.economyService().charge(npc.accountUuid(), finalPrice, "npc_purchase")) {
|
||||
return false;
|
||||
}
|
||||
removePlayerItems(player, quote.material(), quote.amount());
|
||||
plugin.economyService().credit(player.getUniqueId(), finalPrice, "npc_sale");
|
||||
repository.addInventory(npc.id(), quote.material(), quote.amount());
|
||||
return true;
|
||||
}
|
||||
|
||||
public void tickWorkers() {
|
||||
try {
|
||||
for (CoalNpc coalNpc : repository.listNpcs()) {
|
||||
if (!coalNpc.worker()) {
|
||||
continue;
|
||||
}
|
||||
NPC npc = CitizensAPI.getNPCRegistry().getById(coalNpc.citizensId());
|
||||
Optional<NpcZone> zone = repository.findZone(coalNpc.zoneId());
|
||||
if (npc == null || zone.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (!npc.isSpawned()) {
|
||||
World world = Bukkit.getWorld(coalNpc.world());
|
||||
if (world != null) {
|
||||
npc.spawn(new Location(world, coalNpc.x(), coalNpc.y(), coalNpc.z()));
|
||||
}
|
||||
}
|
||||
if (npc.isSpawned() && !npc.getNavigator().isNavigating()) {
|
||||
Location target = randomPoint(zone.get());
|
||||
if (target != null) {
|
||||
npc.getNavigator().setTarget(target);
|
||||
}
|
||||
}
|
||||
Material material = Material.matchMaterial(plugin.getConfig().getString("npc.worker.produce_material", "BREAD"));
|
||||
int amount = plugin.getConfig().getInt("npc.worker.produce_amount", 2);
|
||||
if (material != null && amount > 0) {
|
||||
repository.addInventory(coalNpc.id(), material, amount);
|
||||
}
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
plugin.getLogger().warning("NPC worker tick failed: " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private Location randomPoint(NpcZone zone) {
|
||||
World world = Bukkit.getWorld(zone.world());
|
||||
if (world == null) {
|
||||
return null;
|
||||
}
|
||||
for (int tries = 0; tries < 32; tries++) {
|
||||
int x = zone.x1() + random.nextInt(Math.max(1, zone.x2() - zone.x1() + 1));
|
||||
int z = zone.z1() + random.nextInt(Math.max(1, zone.z2() - zone.z1() + 1));
|
||||
if (zone.contains(world.getName(), x, z)) {
|
||||
return new Location(world, x + 0.5D, world.getHighestBlockYAt(x, z), z + 0.5D);
|
||||
}
|
||||
}
|
||||
return new Location(world, zone.x1() + 0.5D, world.getHighestBlockYAt(zone.x1(), zone.z1()), zone.z1() + 0.5D);
|
||||
}
|
||||
|
||||
private boolean playerHas(Player player, Material material, int amount) {
|
||||
int found = 0;
|
||||
for (ItemStack item : player.getInventory().getStorageContents()) {
|
||||
if (item != null && item.getType() == material) {
|
||||
found += item.getAmount();
|
||||
}
|
||||
}
|
||||
return found >= amount;
|
||||
}
|
||||
|
||||
private void removePlayerItems(Player player, Material material, int amount) {
|
||||
int remaining = amount;
|
||||
for (ItemStack item : player.getInventory().getStorageContents()) {
|
||||
if (item == null || item.getType() != material) {
|
||||
continue;
|
||||
}
|
||||
int take = Math.min(remaining, item.getAmount());
|
||||
item.setAmount(item.getAmount() - take);
|
||||
remaining -= take;
|
||||
if (remaining <= 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public record HaggleQuote(String mode, Material material, int amount, long offer, long floor, long ceiling,
|
||||
long counter, boolean accepted, String reason) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package com.librewiki.coalgov.service;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.util.CoalMoney;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public final class OpenRouterService {
|
||||
private static final Pattern MESSAGE_PATTERN = Pattern.compile("\"message\"\\s*:\\s*\"((?:\\\\.|[^\"])*)\"");
|
||||
private static final Pattern COUNTER_PATTERN = Pattern.compile("\"counter\"\\s*:\\s*(\\d+)");
|
||||
private static final Pattern ACCEPT_PATTERN = Pattern.compile("\"accept\"\\s*:\\s*(true|false)", Pattern.CASE_INSENSITIVE);
|
||||
private final CoalGovPlugin plugin;
|
||||
private final HttpClient client = HttpClient.newHttpClient();
|
||||
|
||||
public OpenRouterService(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public CompletableFuture<Optional<HaggleAiResult>> haggle(String prompt, long minCounter, long maxCounter) {
|
||||
if (!plugin.getConfig().getBoolean("npc.ai.enabled", true)) {
|
||||
return CompletableFuture.completedFuture(Optional.empty());
|
||||
}
|
||||
String key = plugin.getConfig().getString("npc.ai.api_key", "");
|
||||
if (key == null || key.isBlank()) {
|
||||
return CompletableFuture.completedFuture(Optional.empty());
|
||||
}
|
||||
String model = plugin.getConfig().getString("npc.ai.model", "openai/gpt-4.1-mini");
|
||||
int timeoutMs = plugin.getConfig().getInt("npc.ai.timeout_ms", 4000);
|
||||
String body = """
|
||||
{"model":"%s","messages":[{"role":"system","content":"You are a Minecraft CoalGov NPC trader. Return only compact JSON with accept boolean, counter integer, and message string. The counter must stay within the provided bounds."},{"role":"user","content":"%s"}],"temperature":0.8}
|
||||
""".formatted(escape(model), escape(prompt));
|
||||
HttpRequest request = HttpRequest.newBuilder(URI.create("https://openrouter.ai/api/v1/chat/completions"))
|
||||
.timeout(Duration.ofMillis(timeoutMs))
|
||||
.header("Authorization", "Bearer " + key)
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(body))
|
||||
.build();
|
||||
return client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
|
||||
.thenApply(response -> parse(response.body(), minCounter, maxCounter))
|
||||
.exceptionally(exception -> Optional.empty());
|
||||
}
|
||||
|
||||
private Optional<HaggleAiResult> parse(String response, long minCounter, long maxCounter) {
|
||||
String content = extractContent(response);
|
||||
Matcher counterMatcher = COUNTER_PATTERN.matcher(content);
|
||||
Matcher acceptMatcher = ACCEPT_PATTERN.matcher(content);
|
||||
Matcher messageMatcher = MESSAGE_PATTERN.matcher(content);
|
||||
if (!counterMatcher.find()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
long counter = Long.parseLong(counterMatcher.group(1));
|
||||
counter = Math.max(minCounter, Math.min(maxCounter, counter));
|
||||
boolean accept = acceptMatcher.find() && Boolean.parseBoolean(acceptMatcher.group(1));
|
||||
String message = messageMatcher.find() ? unescape(messageMatcher.group(1)) : "I can work with " + CoalMoney.format(counter) + ".";
|
||||
return Optional.of(new HaggleAiResult(accept, counter, message));
|
||||
}
|
||||
|
||||
private String extractContent(String response) {
|
||||
int index = response.indexOf("\"content\"");
|
||||
if (index < 0) {
|
||||
return response;
|
||||
}
|
||||
int start = response.indexOf('"', index + 9);
|
||||
if (start < 0) {
|
||||
return response;
|
||||
}
|
||||
start++;
|
||||
StringBuilder builder = new StringBuilder();
|
||||
boolean escaped = false;
|
||||
for (int i = start; i < response.length(); i++) {
|
||||
char c = response.charAt(i);
|
||||
if (escaped) {
|
||||
builder.append(c);
|
||||
escaped = false;
|
||||
} else if (c == '\\') {
|
||||
escaped = true;
|
||||
} else if (c == '"') {
|
||||
break;
|
||||
} else {
|
||||
builder.append(c);
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String escape(String value) {
|
||||
return value.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n");
|
||||
}
|
||||
|
||||
private String unescape(String value) {
|
||||
return value.replace("\\\"", "\"").replace("\\n", " ").replace("\\\\", "\\");
|
||||
}
|
||||
|
||||
public record HaggleAiResult(boolean accept, long counter, String message) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.librewiki.coalgov.service;
|
||||
|
||||
import com.librewiki.coalgov.model.LandClass;
|
||||
import com.librewiki.coalgov.model.LandRegion;
|
||||
import com.librewiki.coalgov.model.Permit;
|
||||
import com.librewiki.coalgov.model.PermitType;
|
||||
import com.librewiki.coalgov.storage.PermitRepository;
|
||||
import com.librewiki.coalgov.util.CoalMoney;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class PermitService {
|
||||
private final PermitRepository repository;
|
||||
private final LandService landService;
|
||||
private final EconomyService economyService;
|
||||
private FileConfiguration config;
|
||||
|
||||
public PermitService(PermitRepository repository, LandService landService, EconomyService economyService, FileConfiguration config) {
|
||||
this.repository = repository;
|
||||
this.landService = landService;
|
||||
this.economyService = economyService;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public void setConfig(FileConfiguration config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public BuyResult buyMining(UUID owner, String regionName) throws SQLException {
|
||||
Optional<LandRegion> region = landService.findByName(regionName);
|
||||
if (region.isEmpty()) {
|
||||
return BuyResult.fail("No land region named " + regionName + ".");
|
||||
}
|
||||
if (region.get().landClass() != LandClass.MINING_CONCESSION) {
|
||||
return BuyResult.fail("That region is not a mining concession.");
|
||||
}
|
||||
long cost = CoalMoney.fromCoalConfig(config.getDouble("permits.mining.cost", 64.0D));
|
||||
if (!economyService.charge(owner, cost, "permit_purchase")) {
|
||||
return BuyResult.fail("You need " + CoalMoney.format(cost) + " for that permit.");
|
||||
}
|
||||
long expiresAt = System.currentTimeMillis() + config.getLong("permits.mining.duration_hours", 72L) * 60L * 60L * 1000L;
|
||||
long id = repository.create(owner, PermitType.MINING, region.get().name(), expiresAt);
|
||||
return BuyResult.success(id, cost, expiresAt);
|
||||
}
|
||||
|
||||
public boolean hasMiningPermit(UUID owner, String regionName) throws SQLException {
|
||||
return repository.hasActive(owner, PermitType.MINING, regionName, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public List<Permit> listActive(UUID owner) throws SQLException {
|
||||
return repository.listActive(owner, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public record BuyResult(boolean success, String message, long id, long cost, long expiresAt) {
|
||||
public static BuyResult fail(String message) {
|
||||
return new BuyResult(false, message, -1L, 0L, 0L);
|
||||
}
|
||||
|
||||
public static BuyResult success(long id, long cost, long expiresAt) {
|
||||
return new BuyResult(true, "", id, cost, expiresAt);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.librewiki.coalgov.service;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.model.LandClass;
|
||||
import com.librewiki.coalgov.util.Cuboid2D;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.Sign;
|
||||
import org.bukkit.block.data.Rotatable;
|
||||
import org.bukkit.block.sign.Side;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class SpawnGuideService {
|
||||
private final CoalGovPlugin plugin;
|
||||
|
||||
public SpawnGuideService(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public void setup(Player player) throws SQLException {
|
||||
Location center = player.getLocation().toBlockLocation();
|
||||
World world = player.getWorld();
|
||||
world.setSpawnLocation(center);
|
||||
if (plugin.getConfig().getBoolean("spawn.guide.create_government_region", true)) {
|
||||
createSpawnRegion(center);
|
||||
}
|
||||
placeGuide(center);
|
||||
}
|
||||
|
||||
private void createSpawnRegion(Location center) throws SQLException {
|
||||
int radius = plugin.getConfig().getInt("spawn.protection_radius", 32);
|
||||
Cuboid2D area = new Cuboid2D(
|
||||
center.getWorld().getName(),
|
||||
center.getBlockX() - radius,
|
||||
center.getBlockZ() - radius,
|
||||
center.getBlockX() + radius,
|
||||
center.getBlockZ() + radius
|
||||
);
|
||||
plugin.landService().delete("spawn");
|
||||
plugin.landService().create("spawn", area, LandClass.GOVERNMENT);
|
||||
}
|
||||
|
||||
private void placeGuide(Location center) {
|
||||
List<String> lines = guideLines();
|
||||
int signs = Math.max(1, (int) Math.ceil(lines.size() / 4.0D));
|
||||
int startX = center.getBlockX() - signs + 1;
|
||||
int z = center.getBlockZ() + 3;
|
||||
for (int i = 0; i < signs; i++) {
|
||||
int x = startX + i * 2;
|
||||
int y = center.getWorld().getHighestBlockYAt(x, z);
|
||||
Block base = center.getWorld().getBlockAt(x, y, z);
|
||||
base.setType(Material.POLISHED_ANDESITE);
|
||||
Block signBlock = center.getWorld().getBlockAt(x, y + 1, z);
|
||||
signBlock.setType(Material.OAK_SIGN);
|
||||
if (signBlock.getBlockData() instanceof Rotatable rotatable) {
|
||||
rotatable.setRotation(org.bukkit.block.BlockFace.SOUTH);
|
||||
signBlock.setBlockData(rotatable);
|
||||
}
|
||||
if (signBlock.getState() instanceof Sign sign) {
|
||||
for (int line = 0; line < 4; line++) {
|
||||
int index = i * 4 + line;
|
||||
sign.getSide(Side.FRONT).setLine(line, index < lines.size() ? lines.get(index) : "");
|
||||
}
|
||||
sign.update(true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> guideLines() {
|
||||
ConfigurationSection section = plugin.getConfig().getConfigurationSection("spawn.guide");
|
||||
List<String> lines = section == null ? List.of() : section.getStringList("lines");
|
||||
if (!lines.isEmpty()) {
|
||||
return lines;
|
||||
}
|
||||
List<String> defaults = new ArrayList<>();
|
||||
defaults.add("COALGOV BASIN");
|
||||
defaults.add("Free first");
|
||||
defaults.add("homestead:");
|
||||
defaults.add("/claim buy");
|
||||
defaults.add("Use /claim wand");
|
||||
defaults.add("for polygons");
|
||||
defaults.add("Coal seams:");
|
||||
defaults.add("redwood ridges");
|
||||
defaults.add("Permits:");
|
||||
defaults.add("/permit buy");
|
||||
defaults.add("mining <region>");
|
||||
defaults.add("/market trades");
|
||||
return defaults;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.librewiki.coalgov.service;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.storage.SuperFurnaceRepository;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.ShapedRecipe;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
public final class SuperFurnaceService {
|
||||
private static final int SPEED_MULTIPLIER = 5;
|
||||
private static final int FUEL_MULTIPLIER = 4;
|
||||
|
||||
private final CoalGovPlugin plugin;
|
||||
private final SuperFurnaceRepository repository;
|
||||
private final NamespacedKey itemKey;
|
||||
private final NamespacedKey recipeKey;
|
||||
|
||||
public SuperFurnaceService(CoalGovPlugin plugin, SuperFurnaceRepository repository) {
|
||||
this.plugin = plugin;
|
||||
this.repository = repository;
|
||||
this.itemKey = new NamespacedKey(plugin, "super_furnace");
|
||||
this.recipeKey = new NamespacedKey(plugin, "super_furnace_recipe");
|
||||
}
|
||||
|
||||
public void registerRecipe() {
|
||||
Bukkit.removeRecipe(recipeKey);
|
||||
ShapedRecipe recipe = new ShapedRecipe(recipeKey, item());
|
||||
recipe.shape("SSS", "SFS", "SSS");
|
||||
recipe.setIngredient('S', Material.STONE);
|
||||
recipe.setIngredient('F', Material.FURNACE);
|
||||
Bukkit.addRecipe(recipe);
|
||||
}
|
||||
|
||||
public ItemStack item() {
|
||||
ItemStack item = new ItemStack(Material.FURNACE);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
meta.setDisplayName(plugin.messages().raw("&6CoalGov Super Furnace"));
|
||||
meta.setLore(List.of(
|
||||
plugin.messages().raw("&75x smelting speed."),
|
||||
plugin.messages().raw("&7Burns fuel 4x faster."),
|
||||
plugin.messages().raw("&7Crafted with stone around a furnace.")
|
||||
));
|
||||
meta.getPersistentDataContainer().set(itemKey, PersistentDataType.BYTE, (byte) 1);
|
||||
item.setItemMeta(meta);
|
||||
return item;
|
||||
}
|
||||
|
||||
public boolean isItem(ItemStack item) {
|
||||
if (item == null || item.getType() != Material.FURNACE || !item.hasItemMeta()) {
|
||||
return false;
|
||||
}
|
||||
return item.getItemMeta().getPersistentDataContainer().has(itemKey, PersistentDataType.BYTE);
|
||||
}
|
||||
|
||||
public void add(Block block) throws SQLException {
|
||||
repository.add(block.getLocation());
|
||||
}
|
||||
|
||||
public void remove(Block block) throws SQLException {
|
||||
repository.remove(block.getLocation());
|
||||
}
|
||||
|
||||
public boolean isSuperFurnace(Block block) throws SQLException {
|
||||
return block.getType() == Material.FURNACE && repository.exists(block.getLocation());
|
||||
}
|
||||
|
||||
public int fasterCookTime(int cookTime) {
|
||||
return Math.max(1, (int) Math.ceil(cookTime / (double) SPEED_MULTIPLIER));
|
||||
}
|
||||
|
||||
public int shorterBurnTime(int burnTime) {
|
||||
return Math.max(1, (int) Math.floor(burnTime / (double) FUEL_MULTIPLIER));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.librewiki.coalgov.service;
|
||||
|
||||
public final class TaxService {
|
||||
public void runMaintenance() {
|
||||
// Placeholder for future lease expiration and recurring tax collection.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
package com.librewiki.coalgov.storage;
|
||||
|
||||
import com.librewiki.coalgov.model.Claim;
|
||||
import com.librewiki.coalgov.model.ClaimPoint;
|
||||
import com.librewiki.coalgov.util.Cuboid2D;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class ClaimRepository {
|
||||
private final Database database;
|
||||
|
||||
public ClaimRepository(Database database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
public long create(UUID owner, Cuboid2D area, String claimType, Long expiresAt) throws SQLException {
|
||||
return create(owner, area, claimType, expiresAt, -1L, List.of());
|
||||
}
|
||||
|
||||
public long create(UUID owner, Cuboid2D area, String claimType, Long expiresAt, long purchaseCost, List<ClaimPoint> vertices) throws SQLException {
|
||||
boolean originalAutoCommit = database.connection().getAutoCommit();
|
||||
database.connection().setAutoCommit(false);
|
||||
try (PreparedStatement insert = database.connection().prepareStatement("""
|
||||
INSERT INTO claims(owner_uuid, world, x1, z1, x2, z2, claim_type, expires_at, tax_due, purchase_cost, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?)
|
||||
""", Statement.RETURN_GENERATED_KEYS)) {
|
||||
insert.setString(1, owner.toString());
|
||||
insert.setString(2, area.world());
|
||||
insert.setInt(3, area.x1());
|
||||
insert.setInt(4, area.z1());
|
||||
insert.setInt(5, area.x2());
|
||||
insert.setInt(6, area.z2());
|
||||
insert.setString(7, claimType);
|
||||
if (expiresAt == null) {
|
||||
insert.setObject(8, null);
|
||||
} else {
|
||||
insert.setLong(8, expiresAt);
|
||||
}
|
||||
insert.setLong(9, purchaseCost);
|
||||
insert.setLong(10, System.currentTimeMillis());
|
||||
insert.executeUpdate();
|
||||
try (ResultSet keys = insert.getGeneratedKeys()) {
|
||||
long id = keys.next() ? keys.getLong(1) : -1L;
|
||||
insertVertices(id, vertices);
|
||||
database.connection().commit();
|
||||
return id;
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
database.connection().rollback();
|
||||
throw exception;
|
||||
} finally {
|
||||
database.connection().setAutoCommit(originalAutoCommit);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean deleteOwned(UUID owner, long id) throws SQLException {
|
||||
boolean originalAutoCommit = database.connection().getAutoCommit();
|
||||
database.connection().setAutoCommit(false);
|
||||
try {
|
||||
try (PreparedStatement deleteVertices = database.connection().prepareStatement(
|
||||
"DELETE FROM claim_vertices WHERE claim_id = ?")) {
|
||||
deleteVertices.setLong(1, id);
|
||||
deleteVertices.executeUpdate();
|
||||
}
|
||||
try (PreparedStatement delete = database.connection().prepareStatement(
|
||||
"DELETE FROM claims WHERE id = ? AND owner_uuid = ?")) {
|
||||
delete.setLong(1, id);
|
||||
delete.setString(2, owner.toString());
|
||||
boolean deleted = delete.executeUpdate() == 1;
|
||||
database.connection().commit();
|
||||
return deleted;
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
database.connection().rollback();
|
||||
throw exception;
|
||||
} finally {
|
||||
database.connection().setAutoCommit(originalAutoCommit);
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<Claim> findAt(String world, int x, int z) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
SELECT * FROM claims
|
||||
WHERE world = ? AND x1 <= ? AND x2 >= ? AND z1 <= ? AND z2 >= ?
|
||||
ORDER BY id DESC
|
||||
""")) {
|
||||
statement.setString(1, world);
|
||||
statement.setInt(2, x);
|
||||
statement.setInt(3, x);
|
||||
statement.setInt(4, z);
|
||||
statement.setInt(5, z);
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
while (result.next()) {
|
||||
Claim claim = map(result);
|
||||
if (claim.contains(world, x, z)) {
|
||||
return Optional.of(claim);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<Claim> findByOwner(UUID owner) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement(
|
||||
"SELECT * FROM claims WHERE owner_uuid = ? ORDER BY id")) {
|
||||
statement.setString(1, owner.toString());
|
||||
return list(statement);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Claim> listAll() throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement(
|
||||
"SELECT * FROM claims ORDER BY id")) {
|
||||
return list(statement);
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<Claim> findOwnedById(UUID owner, long id) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement(
|
||||
"SELECT * FROM claims WHERE owner_uuid = ? AND id = ?")) {
|
||||
statement.setString(1, owner.toString());
|
||||
statement.setLong(2, id);
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
return result.next() ? Optional.of(map(result)) : Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean transferOwned(UUID owner, long id, UUID newOwner) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement(
|
||||
"UPDATE claims SET owner_uuid = ? WHERE id = ? AND owner_uuid = ?")) {
|
||||
statement.setString(1, newOwner.toString());
|
||||
statement.setLong(2, id);
|
||||
statement.setString(3, owner.toString());
|
||||
return statement.executeUpdate() == 1;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean overlaps(Cuboid2D area) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
SELECT 1 FROM claims
|
||||
WHERE world = ? AND x1 <= ? AND x2 >= ? AND z1 <= ? AND z2 >= ?
|
||||
LIMIT 1
|
||||
""")) {
|
||||
statement.setString(1, area.world());
|
||||
statement.setInt(2, area.x2());
|
||||
statement.setInt(3, area.x1());
|
||||
statement.setInt(4, area.z2());
|
||||
statement.setInt(5, area.z1());
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
return result.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Claim> list(PreparedStatement statement) throws SQLException {
|
||||
List<Claim> claims = new ArrayList<>();
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
while (result.next()) {
|
||||
claims.add(map(result));
|
||||
}
|
||||
}
|
||||
return claims;
|
||||
}
|
||||
|
||||
private Claim map(ResultSet result) throws SQLException {
|
||||
long expiresAt = result.getLong("expires_at");
|
||||
Long nullableExpiresAt = result.wasNull() ? null : expiresAt;
|
||||
return new Claim(
|
||||
result.getLong("id"),
|
||||
UUID.fromString(result.getString("owner_uuid")),
|
||||
result.getString("world"),
|
||||
result.getInt("x1"),
|
||||
result.getInt("z1"),
|
||||
result.getInt("x2"),
|
||||
result.getInt("z2"),
|
||||
result.getString("claim_type"),
|
||||
nullableExpiresAt,
|
||||
result.getLong("tax_due"),
|
||||
result.getLong("created_at"),
|
||||
result.getLong("purchase_cost"),
|
||||
vertices(result.getLong("id"))
|
||||
);
|
||||
}
|
||||
|
||||
private void insertVertices(long claimId, List<ClaimPoint> vertices) throws SQLException {
|
||||
if (claimId <= 0L || vertices == null || vertices.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
try (PreparedStatement insert = database.connection().prepareStatement("""
|
||||
INSERT INTO claim_vertices(claim_id, vertex_order, x, z)
|
||||
VALUES (?, ?, ?, ?)
|
||||
""")) {
|
||||
for (int i = 0; i < vertices.size(); i++) {
|
||||
ClaimPoint point = vertices.get(i);
|
||||
insert.setLong(1, claimId);
|
||||
insert.setInt(2, i);
|
||||
insert.setInt(3, point.x());
|
||||
insert.setInt(4, point.z());
|
||||
insert.addBatch();
|
||||
}
|
||||
insert.executeBatch();
|
||||
}
|
||||
}
|
||||
|
||||
private List<ClaimPoint> vertices(long claimId) throws SQLException {
|
||||
List<ClaimPoint> points = new ArrayList<>();
|
||||
try (PreparedStatement statement = database.connection().prepareStatement(
|
||||
"SELECT x, z FROM claim_vertices WHERE claim_id = ? ORDER BY vertex_order")) {
|
||||
statement.setLong(1, claimId);
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
while (result.next()) {
|
||||
points.add(new ClaimPoint(result.getInt("x"), result.getInt("z")));
|
||||
}
|
||||
}
|
||||
}
|
||||
return points;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
package com.librewiki.coalgov.storage;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
public final class Database {
|
||||
private final File databaseFile;
|
||||
private Connection connection;
|
||||
|
||||
public Database(File dataFolder) {
|
||||
this.databaseFile = new File(dataFolder, "coalgov.db");
|
||||
}
|
||||
|
||||
public void open() throws SQLException {
|
||||
if (connection != null && !connection.isClosed()) {
|
||||
return;
|
||||
}
|
||||
databaseFile.getParentFile().mkdirs();
|
||||
connection = DriverManager.getConnection("jdbc:sqlite:" + databaseFile.getAbsolutePath());
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
statement.execute("PRAGMA foreign_keys = ON");
|
||||
statement.execute("PRAGMA journal_mode = WAL");
|
||||
}
|
||||
initialize();
|
||||
}
|
||||
|
||||
public Connection connection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (connection == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
connection.close();
|
||||
} catch (SQLException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private void initialize() throws SQLException {
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
statement.execute("""
|
||||
CREATE TABLE IF NOT EXISTS schema_meta (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
)
|
||||
""");
|
||||
statement.execute("""
|
||||
CREATE TABLE IF NOT EXISTS players (
|
||||
uuid TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
coal_balance INTEGER NOT NULL DEFAULT 0,
|
||||
created_at INTEGER NOT NULL
|
||||
)
|
||||
""");
|
||||
statement.execute("""
|
||||
CREATE TABLE IF NOT EXISTS claims (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
owner_uuid TEXT NOT NULL,
|
||||
world TEXT NOT NULL,
|
||||
x1 INTEGER NOT NULL,
|
||||
z1 INTEGER NOT NULL,
|
||||
x2 INTEGER NOT NULL,
|
||||
z2 INTEGER NOT NULL,
|
||||
claim_type TEXT NOT NULL,
|
||||
expires_at INTEGER,
|
||||
tax_due INTEGER NOT NULL DEFAULT 0,
|
||||
purchase_cost INTEGER NOT NULL DEFAULT -1,
|
||||
created_at INTEGER NOT NULL
|
||||
)
|
||||
""");
|
||||
statement.execute("""
|
||||
CREATE TABLE IF NOT EXISTS claim_vertices (
|
||||
claim_id INTEGER NOT NULL,
|
||||
vertex_order INTEGER NOT NULL,
|
||||
x INTEGER NOT NULL,
|
||||
z INTEGER NOT NULL,
|
||||
PRIMARY KEY (claim_id, vertex_order)
|
||||
)
|
||||
""");
|
||||
statement.execute("""
|
||||
CREATE TABLE IF NOT EXISTS land_regions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
world TEXT NOT NULL,
|
||||
x1 INTEGER NOT NULL,
|
||||
z1 INTEGER NOT NULL,
|
||||
x2 INTEGER NOT NULL,
|
||||
z2 INTEGER NOT NULL,
|
||||
land_class TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL
|
||||
)
|
||||
""");
|
||||
statement.execute("""
|
||||
CREATE TABLE IF NOT EXISTS permits (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
owner_uuid TEXT NOT NULL,
|
||||
permit_type TEXT NOT NULL,
|
||||
region_name TEXT,
|
||||
expires_at INTEGER,
|
||||
created_at INTEGER NOT NULL
|
||||
)
|
||||
""");
|
||||
statement.execute("""
|
||||
CREATE TABLE IF NOT EXISTS transactions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
from_uuid TEXT,
|
||||
to_uuid TEXT,
|
||||
amount INTEGER NOT NULL,
|
||||
reason TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL
|
||||
)
|
||||
""");
|
||||
statement.execute("""
|
||||
CREATE TABLE IF NOT EXISTS market_stock (
|
||||
material TEXT PRIMARY KEY,
|
||||
stock INTEGER NOT NULL DEFAULT 0,
|
||||
updated_at INTEGER NOT NULL
|
||||
)
|
||||
""");
|
||||
statement.execute("""
|
||||
CREATE TABLE IF NOT EXISTS treasury (
|
||||
id TEXT PRIMARY KEY,
|
||||
balance INTEGER NOT NULL DEFAULT 0
|
||||
)
|
||||
""");
|
||||
statement.execute("""
|
||||
CREATE TABLE IF NOT EXISTS tax_exemptions (
|
||||
uuid TEXT PRIMARY KEY,
|
||||
exempt INTEGER NOT NULL DEFAULT 0,
|
||||
updated_at INTEGER NOT NULL
|
||||
)
|
||||
""");
|
||||
statement.execute("""
|
||||
CREATE TABLE IF NOT EXISTS fines (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
player_uuid TEXT NOT NULL,
|
||||
issuer_uuid TEXT,
|
||||
amount INTEGER NOT NULL,
|
||||
reason TEXT NOT NULL,
|
||||
paid_at INTEGER,
|
||||
created_at INTEGER NOT NULL
|
||||
)
|
||||
""");
|
||||
statement.execute("""
|
||||
CREATE TABLE IF NOT EXISTS super_furnaces (
|
||||
world TEXT NOT NULL,
|
||||
x INTEGER NOT NULL,
|
||||
y INTEGER NOT NULL,
|
||||
z INTEGER NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
PRIMARY KEY (world, x, y, z)
|
||||
)
|
||||
""");
|
||||
statement.execute("""
|
||||
CREATE TABLE IF NOT EXISTS npc_zones (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
world TEXT NOT NULL,
|
||||
x1 INTEGER NOT NULL,
|
||||
z1 INTEGER NOT NULL,
|
||||
x2 INTEGER NOT NULL,
|
||||
z2 INTEGER NOT NULL,
|
||||
created_at INTEGER NOT NULL
|
||||
)
|
||||
""");
|
||||
statement.execute("""
|
||||
CREATE TABLE IF NOT EXISTS npc_zone_vertices (
|
||||
zone_id INTEGER NOT NULL,
|
||||
vertex_order INTEGER NOT NULL,
|
||||
x INTEGER NOT NULL,
|
||||
z INTEGER NOT NULL,
|
||||
PRIMARY KEY (zone_id, vertex_order)
|
||||
)
|
||||
""");
|
||||
statement.execute("""
|
||||
CREATE TABLE IF NOT EXISTS coalgov_npcs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
citizens_id INTEGER NOT NULL UNIQUE,
|
||||
account_uuid TEXT NOT NULL UNIQUE,
|
||||
npc_type TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
zone_id INTEGER NOT NULL,
|
||||
world TEXT NOT NULL,
|
||||
x REAL NOT NULL,
|
||||
y REAL NOT NULL,
|
||||
z REAL NOT NULL,
|
||||
created_at INTEGER NOT NULL
|
||||
)
|
||||
""");
|
||||
statement.execute("""
|
||||
CREATE TABLE IF NOT EXISTS npc_inventory (
|
||||
npc_id INTEGER NOT NULL,
|
||||
material TEXT NOT NULL,
|
||||
amount INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (npc_id, material)
|
||||
)
|
||||
""");
|
||||
statement.execute("CREATE INDEX IF NOT EXISTS idx_claims_location ON claims(world, x1, z1, x2, z2)");
|
||||
statement.execute("CREATE INDEX IF NOT EXISTS idx_claim_vertices_claim ON claim_vertices(claim_id)");
|
||||
statement.execute("CREATE INDEX IF NOT EXISTS idx_land_location ON land_regions(world, x1, z1, x2, z2)");
|
||||
statement.execute("CREATE INDEX IF NOT EXISTS idx_permits_owner ON permits(owner_uuid, permit_type, region_name)");
|
||||
statement.execute("CREATE INDEX IF NOT EXISTS idx_fines_player ON fines(player_uuid, paid_at)");
|
||||
statement.execute("CREATE INDEX IF NOT EXISTS idx_coalgov_npcs_citizens ON coalgov_npcs(citizens_id)");
|
||||
statement.execute("CREATE INDEX IF NOT EXISTS idx_npc_zone_vertices_zone ON npc_zone_vertices(zone_id)");
|
||||
}
|
||||
ensureColumn("claims", "purchase_cost", "INTEGER NOT NULL DEFAULT -1");
|
||||
migrateCoalBalancesToCents();
|
||||
}
|
||||
|
||||
private void migrateCoalBalancesToCents() throws SQLException {
|
||||
String version = metaValue("economy_units");
|
||||
if ("coal_cents_v1".equals(version)) {
|
||||
return;
|
||||
}
|
||||
boolean originalAutoCommit = connection.getAutoCommit();
|
||||
connection.setAutoCommit(false);
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
statement.execute("UPDATE players SET coal_balance = coal_balance * 100");
|
||||
statement.execute("UPDATE transactions SET amount = amount * 100");
|
||||
statement.execute("UPDATE treasury SET balance = balance * 100");
|
||||
statement.execute("UPDATE fines SET amount = amount * 100");
|
||||
statement.execute("UPDATE claims SET tax_due = tax_due * 100");
|
||||
statement.execute("UPDATE claims SET purchase_cost = purchase_cost * 100 WHERE purchase_cost >= 0");
|
||||
setMetaValue("economy_units", "coal_cents_v1");
|
||||
connection.commit();
|
||||
} catch (SQLException exception) {
|
||||
connection.rollback();
|
||||
throw exception;
|
||||
} finally {
|
||||
connection.setAutoCommit(originalAutoCommit);
|
||||
}
|
||||
}
|
||||
|
||||
private String metaValue(String key) throws SQLException {
|
||||
try (PreparedStatement statement = connection.prepareStatement("SELECT value FROM schema_meta WHERE key = ?")) {
|
||||
statement.setString(1, key);
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
return result.next() ? result.getString("value") : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setMetaValue(String key, String value) throws SQLException {
|
||||
try (PreparedStatement statement = connection.prepareStatement("""
|
||||
INSERT INTO schema_meta(key, value)
|
||||
VALUES (?, ?)
|
||||
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
||||
""")) {
|
||||
statement.setString(1, key);
|
||||
statement.setString(2, value);
|
||||
statement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureColumn(String table, String column, String definition) throws SQLException {
|
||||
try (Statement statement = connection.createStatement();
|
||||
var result = statement.executeQuery("PRAGMA table_info(" + table + ")")) {
|
||||
while (result.next()) {
|
||||
if (column.equalsIgnoreCase(result.getString("name"))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
statement.execute("ALTER TABLE " + table + " ADD COLUMN " + column + " " + definition);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.librewiki.coalgov.storage;
|
||||
|
||||
import org.bukkit.OfflinePlayer;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class EconomyRepository {
|
||||
private final Database database;
|
||||
|
||||
public EconomyRepository(Database database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
public void ensurePlayer(OfflinePlayer player, long startingBalance) throws SQLException {
|
||||
String name = player.getName() == null ? player.getUniqueId().toString() : player.getName();
|
||||
ensureAccount(player.getUniqueId(), name, startingBalance);
|
||||
}
|
||||
|
||||
public void ensureAccount(UUID uuid, String name, long startingBalance) throws SQLException {
|
||||
try (PreparedStatement insert = database.connection().prepareStatement("""
|
||||
INSERT INTO players(uuid, name, coal_balance, created_at)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT(uuid) DO UPDATE SET name = excluded.name
|
||||
""")) {
|
||||
insert.setString(1, uuid.toString());
|
||||
insert.setString(2, name);
|
||||
insert.setLong(3, startingBalance);
|
||||
insert.setLong(4, System.currentTimeMillis());
|
||||
insert.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public long balance(UUID uuid) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement(
|
||||
"SELECT coal_balance FROM players WHERE uuid = ?")) {
|
||||
statement.setString(1, uuid.toString());
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
return result.next() ? result.getLong("coal_balance") : 0L;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean add(UUID uuid, long amount, UUID fromUuid, UUID toUuid, String reason) throws SQLException {
|
||||
if (amount <= 0) {
|
||||
return false;
|
||||
}
|
||||
try (PreparedStatement update = database.connection().prepareStatement(
|
||||
"UPDATE players SET coal_balance = coal_balance + ? WHERE uuid = ?")) {
|
||||
update.setLong(1, amount);
|
||||
update.setString(2, uuid.toString());
|
||||
if (update.executeUpdate() != 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
recordTransaction(fromUuid, toUuid, amount, reason);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean subtract(UUID uuid, long amount, UUID fromUuid, UUID toUuid, String reason) throws SQLException {
|
||||
if (amount <= 0) {
|
||||
return false;
|
||||
}
|
||||
try (PreparedStatement update = database.connection().prepareStatement("""
|
||||
UPDATE players SET coal_balance = coal_balance - ?
|
||||
WHERE uuid = ? AND coal_balance >= ?
|
||||
""")) {
|
||||
update.setLong(1, amount);
|
||||
update.setString(2, uuid.toString());
|
||||
update.setLong(3, amount);
|
||||
if (update.executeUpdate() != 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
recordTransaction(fromUuid, toUuid, amount, reason);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean transfer(UUID from, UUID to, long amount, String reason) throws SQLException {
|
||||
if (amount <= 0 || from.equals(to)) {
|
||||
return false;
|
||||
}
|
||||
boolean originalAutoCommit = database.connection().getAutoCommit();
|
||||
database.connection().setAutoCommit(false);
|
||||
try {
|
||||
try (PreparedStatement debit = database.connection().prepareStatement("""
|
||||
UPDATE players SET coal_balance = coal_balance - ?
|
||||
WHERE uuid = ? AND coal_balance >= ?
|
||||
""")) {
|
||||
debit.setLong(1, amount);
|
||||
debit.setString(2, from.toString());
|
||||
debit.setLong(3, amount);
|
||||
if (debit.executeUpdate() != 1) {
|
||||
database.connection().rollback();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
try (PreparedStatement credit = database.connection().prepareStatement(
|
||||
"UPDATE players SET coal_balance = coal_balance + ? WHERE uuid = ?")) {
|
||||
credit.setLong(1, amount);
|
||||
credit.setString(2, to.toString());
|
||||
if (credit.executeUpdate() != 1) {
|
||||
database.connection().rollback();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
recordTransaction(from, to, amount, reason);
|
||||
database.connection().commit();
|
||||
return true;
|
||||
} catch (SQLException exception) {
|
||||
database.connection().rollback();
|
||||
throw exception;
|
||||
} finally {
|
||||
database.connection().setAutoCommit(originalAutoCommit);
|
||||
}
|
||||
}
|
||||
|
||||
private void recordTransaction(UUID fromUuid, UUID toUuid, long amount, String reason) throws SQLException {
|
||||
try (PreparedStatement insert = database.connection().prepareStatement("""
|
||||
INSERT INTO transactions(from_uuid, to_uuid, amount, reason, created_at)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""")) {
|
||||
insert.setString(1, fromUuid == null ? null : fromUuid.toString());
|
||||
insert.setString(2, toUuid == null ? null : toUuid.toString());
|
||||
insert.setLong(3, amount);
|
||||
insert.setString(4, reason);
|
||||
insert.setLong(5, System.currentTimeMillis());
|
||||
insert.executeUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package com.librewiki.coalgov.storage;
|
||||
|
||||
import com.librewiki.coalgov.model.Fine;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class GovernmentRepository {
|
||||
private static final String TREASURY_ID = "main";
|
||||
private final Database database;
|
||||
|
||||
public GovernmentRepository(Database database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
public long treasuryBalance() throws SQLException {
|
||||
ensureTreasury();
|
||||
try (PreparedStatement statement = database.connection().prepareStatement(
|
||||
"SELECT balance FROM treasury WHERE id = ?")) {
|
||||
statement.setString(1, TREASURY_ID);
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
return result.next() ? result.getLong("balance") : 0L;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addTreasury(long amount) throws SQLException {
|
||||
if (amount <= 0L) {
|
||||
return;
|
||||
}
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
INSERT INTO treasury(id, balance)
|
||||
VALUES (?, ?)
|
||||
ON CONFLICT(id) DO UPDATE SET balance = balance + excluded.balance
|
||||
""")) {
|
||||
statement.setString(1, TREASURY_ID);
|
||||
statement.setLong(2, amount);
|
||||
statement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean spendTreasury(long amount) throws SQLException {
|
||||
if (amount <= 0L) {
|
||||
return false;
|
||||
}
|
||||
ensureTreasury();
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
UPDATE treasury SET balance = balance - ?
|
||||
WHERE id = ? AND balance >= ?
|
||||
""")) {
|
||||
statement.setLong(1, amount);
|
||||
statement.setString(2, TREASURY_ID);
|
||||
statement.setLong(3, amount);
|
||||
return statement.executeUpdate() == 1;
|
||||
}
|
||||
}
|
||||
|
||||
public void setTaxExempt(UUID uuid, boolean exempt) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
INSERT INTO tax_exemptions(uuid, exempt, updated_at)
|
||||
VALUES (?, ?, ?)
|
||||
ON CONFLICT(uuid) DO UPDATE SET exempt = excluded.exempt, updated_at = excluded.updated_at
|
||||
""")) {
|
||||
statement.setString(1, uuid.toString());
|
||||
statement.setInt(2, exempt ? 1 : 0);
|
||||
statement.setLong(3, System.currentTimeMillis());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean taxExempt(UUID uuid) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement(
|
||||
"SELECT exempt FROM tax_exemptions WHERE uuid = ?")) {
|
||||
statement.setString(1, uuid.toString());
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
return result.next() && result.getInt("exempt") == 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public long createFine(UUID playerUuid, UUID issuerUuid, long amount, String reason) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
INSERT INTO fines(player_uuid, issuer_uuid, amount, reason, created_at)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""", Statement.RETURN_GENERATED_KEYS)) {
|
||||
statement.setString(1, playerUuid.toString());
|
||||
statement.setString(2, issuerUuid == null ? null : issuerUuid.toString());
|
||||
statement.setLong(3, amount);
|
||||
statement.setString(4, reason);
|
||||
statement.setLong(5, System.currentTimeMillis());
|
||||
statement.executeUpdate();
|
||||
try (ResultSet keys = statement.getGeneratedKeys()) {
|
||||
return keys.next() ? keys.getLong(1) : -1L;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<Fine> unpaidFines(UUID playerUuid) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
SELECT * FROM fines
|
||||
WHERE player_uuid = ? AND paid_at IS NULL
|
||||
ORDER BY id
|
||||
""")) {
|
||||
statement.setString(1, playerUuid.toString());
|
||||
return list(statement);
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<Fine> findUnpaidFine(UUID playerUuid, long id) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
SELECT * FROM fines
|
||||
WHERE player_uuid = ? AND id = ? AND paid_at IS NULL
|
||||
""")) {
|
||||
statement.setString(1, playerUuid.toString());
|
||||
statement.setLong(2, id);
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
return result.next() ? Optional.of(map(result)) : Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean markFinePaid(long id) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
UPDATE fines SET paid_at = ?
|
||||
WHERE id = ? AND paid_at IS NULL
|
||||
""")) {
|
||||
statement.setLong(1, System.currentTimeMillis());
|
||||
statement.setLong(2, id);
|
||||
return statement.executeUpdate() == 1;
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureTreasury() throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement(
|
||||
"INSERT INTO treasury(id, balance) VALUES (?, 0) ON CONFLICT(id) DO NOTHING")) {
|
||||
statement.setString(1, TREASURY_ID);
|
||||
statement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private List<Fine> list(PreparedStatement statement) throws SQLException {
|
||||
List<Fine> fines = new ArrayList<>();
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
while (result.next()) {
|
||||
fines.add(map(result));
|
||||
}
|
||||
}
|
||||
return fines;
|
||||
}
|
||||
|
||||
private Fine map(ResultSet result) throws SQLException {
|
||||
String issuer = result.getString("issuer_uuid");
|
||||
long paidAt = result.getLong("paid_at");
|
||||
boolean paidAtNull = result.wasNull();
|
||||
return new Fine(
|
||||
result.getLong("id"),
|
||||
UUID.fromString(result.getString("player_uuid")),
|
||||
issuer == null ? null : UUID.fromString(issuer),
|
||||
result.getLong("amount"),
|
||||
result.getString("reason"),
|
||||
paidAtNull ? null : paidAt,
|
||||
result.getLong("created_at")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.librewiki.coalgov.storage;
|
||||
|
||||
import com.librewiki.coalgov.model.LandClass;
|
||||
import com.librewiki.coalgov.model.LandRegion;
|
||||
import com.librewiki.coalgov.util.Cuboid2D;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class LandRepository {
|
||||
private final Database database;
|
||||
|
||||
public LandRepository(Database database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
public boolean create(String name, Cuboid2D area, LandClass landClass) throws SQLException {
|
||||
try (PreparedStatement insert = database.connection().prepareStatement("""
|
||||
INSERT INTO land_regions(name, world, x1, z1, x2, z2, land_class, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""")) {
|
||||
insert.setString(1, name);
|
||||
insert.setString(2, area.world());
|
||||
insert.setInt(3, area.x1());
|
||||
insert.setInt(4, area.z1());
|
||||
insert.setInt(5, area.x2());
|
||||
insert.setInt(6, area.z2());
|
||||
insert.setString(7, landClass.name());
|
||||
insert.setLong(8, System.currentTimeMillis());
|
||||
return insert.executeUpdate() == 1;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean delete(String name) throws SQLException {
|
||||
try (PreparedStatement delete = database.connection().prepareStatement(
|
||||
"DELETE FROM land_regions WHERE lower(name) = lower(?)")) {
|
||||
delete.setString(1, name);
|
||||
return delete.executeUpdate() == 1;
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<LandRegion> findByName(String name) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement(
|
||||
"SELECT * FROM land_regions WHERE lower(name) = lower(?)")) {
|
||||
statement.setString(1, name);
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
return result.next() ? Optional.of(map(result)) : Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<LandRegion> findAt(String world, int x, int z) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
SELECT * FROM land_regions
|
||||
WHERE world = ? AND x1 <= ? AND x2 >= ? AND z1 <= ? AND z2 >= ?
|
||||
ORDER BY id DESC LIMIT 1
|
||||
""")) {
|
||||
statement.setString(1, world);
|
||||
statement.setInt(2, x);
|
||||
statement.setInt(3, x);
|
||||
statement.setInt(4, z);
|
||||
statement.setInt(5, z);
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
return result.next() ? Optional.of(map(result)) : Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<LandRegion> listAll() throws SQLException {
|
||||
try (Statement statement = database.connection().createStatement();
|
||||
ResultSet result = statement.executeQuery("SELECT * FROM land_regions ORDER BY name")) {
|
||||
List<LandRegion> regions = new ArrayList<>();
|
||||
while (result.next()) {
|
||||
regions.add(map(result));
|
||||
}
|
||||
return regions;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean overlaps(Cuboid2D area, LandClass... blockedClasses) throws SQLException {
|
||||
StringBuilder sql = new StringBuilder("""
|
||||
SELECT land_class FROM land_regions
|
||||
WHERE world = ? AND x1 <= ? AND x2 >= ? AND z1 <= ? AND z2 >= ?
|
||||
""");
|
||||
try (PreparedStatement statement = database.connection().prepareStatement(sql.toString())) {
|
||||
statement.setString(1, area.world());
|
||||
statement.setInt(2, area.x2());
|
||||
statement.setInt(3, area.x1());
|
||||
statement.setInt(4, area.z2());
|
||||
statement.setInt(5, area.z1());
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
while (result.next()) {
|
||||
LandClass actual = LandClass.valueOf(result.getString("land_class"));
|
||||
for (LandClass blocked : blockedClasses) {
|
||||
if (actual == blocked) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private LandRegion map(ResultSet result) throws SQLException {
|
||||
return new LandRegion(
|
||||
result.getLong("id"),
|
||||
result.getString("name"),
|
||||
result.getString("world"),
|
||||
result.getInt("x1"),
|
||||
result.getInt("z1"),
|
||||
result.getInt("x2"),
|
||||
result.getInt("z2"),
|
||||
LandClass.valueOf(result.getString("land_class")),
|
||||
result.getLong("created_at")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.librewiki.coalgov.storage;
|
||||
|
||||
import org.bukkit.Material;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public final class MarketRepository {
|
||||
private final Database database;
|
||||
|
||||
public MarketRepository(Database database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
public long stock(Material material, long initialStock) throws SQLException {
|
||||
try (PreparedStatement insert = database.connection().prepareStatement("""
|
||||
INSERT INTO market_stock(material, stock, updated_at)
|
||||
VALUES (?, ?, ?)
|
||||
ON CONFLICT(material) DO NOTHING
|
||||
""")) {
|
||||
insert.setString(1, material.name());
|
||||
insert.setLong(2, Math.max(0L, initialStock));
|
||||
insert.setLong(3, System.currentTimeMillis());
|
||||
insert.executeUpdate();
|
||||
}
|
||||
try (PreparedStatement select = database.connection().prepareStatement(
|
||||
"SELECT stock FROM market_stock WHERE material = ?")) {
|
||||
select.setString(1, material.name());
|
||||
try (ResultSet result = select.executeQuery()) {
|
||||
return result.next() ? result.getLong("stock") : 0L;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addStock(Material material, long amount) throws SQLException {
|
||||
if (amount <= 0) {
|
||||
return;
|
||||
}
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
INSERT INTO market_stock(material, stock, updated_at)
|
||||
VALUES (?, ?, ?)
|
||||
ON CONFLICT(material) DO UPDATE SET
|
||||
stock = stock + excluded.stock,
|
||||
updated_at = excluded.updated_at
|
||||
""")) {
|
||||
statement.setString(1, material.name());
|
||||
statement.setLong(2, amount);
|
||||
statement.setLong(3, System.currentTimeMillis());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean removeStock(Material material, long amount) throws SQLException {
|
||||
if (amount <= 0) {
|
||||
return false;
|
||||
}
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
UPDATE market_stock SET stock = stock - ?, updated_at = ?
|
||||
WHERE material = ? AND stock >= ?
|
||||
""")) {
|
||||
statement.setLong(1, amount);
|
||||
statement.setLong(2, System.currentTimeMillis());
|
||||
statement.setString(3, material.name());
|
||||
statement.setLong(4, amount);
|
||||
return statement.executeUpdate() == 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
package com.librewiki.coalgov.storage;
|
||||
|
||||
import com.librewiki.coalgov.model.ClaimPoint;
|
||||
import com.librewiki.coalgov.model.CoalNpc;
|
||||
import com.librewiki.coalgov.model.NpcZone;
|
||||
import org.bukkit.Material;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class NpcRepository {
|
||||
private final Database database;
|
||||
|
||||
public NpcRepository(Database database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
public long createZone(String name, String world, List<ClaimPoint> vertices) throws SQLException {
|
||||
int x1 = vertices.stream().mapToInt(ClaimPoint::x).min().orElse(0);
|
||||
int x2 = vertices.stream().mapToInt(ClaimPoint::x).max().orElse(0);
|
||||
int z1 = vertices.stream().mapToInt(ClaimPoint::z).min().orElse(0);
|
||||
int z2 = vertices.stream().mapToInt(ClaimPoint::z).max().orElse(0);
|
||||
boolean originalAutoCommit = database.connection().getAutoCommit();
|
||||
database.connection().setAutoCommit(false);
|
||||
try (PreparedStatement insert = database.connection().prepareStatement("""
|
||||
INSERT INTO npc_zones(name, world, x1, z1, x2, z2, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
""", Statement.RETURN_GENERATED_KEYS)) {
|
||||
insert.setString(1, name);
|
||||
insert.setString(2, world);
|
||||
insert.setInt(3, x1);
|
||||
insert.setInt(4, z1);
|
||||
insert.setInt(5, x2);
|
||||
insert.setInt(6, z2);
|
||||
insert.setLong(7, System.currentTimeMillis());
|
||||
insert.executeUpdate();
|
||||
try (ResultSet keys = insert.getGeneratedKeys()) {
|
||||
long id = keys.next() ? keys.getLong(1) : -1L;
|
||||
insertVertices(id, vertices);
|
||||
database.connection().commit();
|
||||
return id;
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
database.connection().rollback();
|
||||
throw exception;
|
||||
} finally {
|
||||
database.connection().setAutoCommit(originalAutoCommit);
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<NpcZone> findZone(String name) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement(
|
||||
"SELECT * FROM npc_zones WHERE name = ?")) {
|
||||
statement.setString(1, name);
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
return result.next() ? Optional.of(mapZone(result)) : Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<NpcZone> findZone(long id) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement(
|
||||
"SELECT * FROM npc_zones WHERE id = ?")) {
|
||||
statement.setLong(1, id);
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
return result.next() ? Optional.of(mapZone(result)) : Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<NpcZone> listZones() throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("SELECT * FROM npc_zones ORDER BY id")) {
|
||||
List<NpcZone> zones = new ArrayList<>();
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
while (result.next()) {
|
||||
zones.add(mapZone(result));
|
||||
}
|
||||
}
|
||||
return zones;
|
||||
}
|
||||
}
|
||||
|
||||
public long createNpc(int citizensId, UUID accountUuid, String type, String name, long zoneId,
|
||||
String world, double x, double y, double z) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
INSERT INTO coalgov_npcs(citizens_id, account_uuid, npc_type, name, zone_id, world, x, y, z, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""", Statement.RETURN_GENERATED_KEYS)) {
|
||||
statement.setInt(1, citizensId);
|
||||
statement.setString(2, accountUuid.toString());
|
||||
statement.setString(3, type.toUpperCase());
|
||||
statement.setString(4, name);
|
||||
statement.setLong(5, zoneId);
|
||||
statement.setString(6, world);
|
||||
statement.setDouble(7, x);
|
||||
statement.setDouble(8, y);
|
||||
statement.setDouble(9, z);
|
||||
statement.setLong(10, System.currentTimeMillis());
|
||||
statement.executeUpdate();
|
||||
try (ResultSet keys = statement.getGeneratedKeys()) {
|
||||
return keys.next() ? keys.getLong(1) : -1L;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<CoalNpc> findNpc(long id) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("SELECT * FROM coalgov_npcs WHERE id = ?")) {
|
||||
statement.setLong(1, id);
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
return result.next() ? Optional.of(mapNpc(result)) : Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<CoalNpc> findByCitizensId(int citizensId) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("SELECT * FROM coalgov_npcs WHERE citizens_id = ?")) {
|
||||
statement.setInt(1, citizensId);
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
return result.next() ? Optional.of(mapNpc(result)) : Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<CoalNpc> listNpcs() throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("SELECT * FROM coalgov_npcs ORDER BY id")) {
|
||||
List<CoalNpc> npcs = new ArrayList<>();
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
while (result.next()) {
|
||||
npcs.add(mapNpc(result));
|
||||
}
|
||||
}
|
||||
return npcs;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean deleteNpc(long id) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("DELETE FROM coalgov_npcs WHERE id = ?")) {
|
||||
statement.setLong(1, id);
|
||||
return statement.executeUpdate() == 1;
|
||||
}
|
||||
}
|
||||
|
||||
public void addInventory(long npcId, Material material, long amount) throws SQLException {
|
||||
if (amount <= 0L) {
|
||||
return;
|
||||
}
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
INSERT INTO npc_inventory(npc_id, material, amount)
|
||||
VALUES (?, ?, ?)
|
||||
ON CONFLICT(npc_id, material) DO UPDATE SET amount = amount + excluded.amount
|
||||
""")) {
|
||||
statement.setLong(1, npcId);
|
||||
statement.setString(2, material.name());
|
||||
statement.setLong(3, amount);
|
||||
statement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean removeInventory(long npcId, Material material, long amount) throws SQLException {
|
||||
if (amount <= 0L) {
|
||||
return false;
|
||||
}
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
UPDATE npc_inventory SET amount = amount - ?
|
||||
WHERE npc_id = ? AND material = ? AND amount >= ?
|
||||
""")) {
|
||||
statement.setLong(1, amount);
|
||||
statement.setLong(2, npcId);
|
||||
statement.setString(3, material.name());
|
||||
statement.setLong(4, amount);
|
||||
return statement.executeUpdate() == 1;
|
||||
}
|
||||
}
|
||||
|
||||
public long inventoryAmount(long npcId, Material material) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement(
|
||||
"SELECT amount FROM npc_inventory WHERE npc_id = ? AND material = ?")) {
|
||||
statement.setLong(1, npcId);
|
||||
statement.setString(2, material.name());
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
return result.next() ? result.getLong("amount") : 0L;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Map<Material, Long> inventory(long npcId) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement(
|
||||
"SELECT material, amount FROM npc_inventory WHERE npc_id = ? AND amount > 0 ORDER BY material")) {
|
||||
statement.setLong(1, npcId);
|
||||
Map<Material, Long> inventory = new LinkedHashMap<>();
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
while (result.next()) {
|
||||
Material material = Material.matchMaterial(result.getString("material"));
|
||||
if (material != null) {
|
||||
inventory.put(material, result.getLong("amount"));
|
||||
}
|
||||
}
|
||||
}
|
||||
return inventory;
|
||||
}
|
||||
}
|
||||
|
||||
private void insertVertices(long zoneId, List<ClaimPoint> vertices) throws SQLException {
|
||||
try (PreparedStatement insert = database.connection().prepareStatement("""
|
||||
INSERT INTO npc_zone_vertices(zone_id, vertex_order, x, z)
|
||||
VALUES (?, ?, ?, ?)
|
||||
""")) {
|
||||
for (int index = 0; index < vertices.size(); index++) {
|
||||
ClaimPoint point = vertices.get(index);
|
||||
insert.setLong(1, zoneId);
|
||||
insert.setInt(2, index);
|
||||
insert.setInt(3, point.x());
|
||||
insert.setInt(4, point.z());
|
||||
insert.addBatch();
|
||||
}
|
||||
insert.executeBatch();
|
||||
}
|
||||
}
|
||||
|
||||
private NpcZone mapZone(ResultSet result) throws SQLException {
|
||||
long id = result.getLong("id");
|
||||
return new NpcZone(
|
||||
id,
|
||||
result.getString("name"),
|
||||
result.getString("world"),
|
||||
result.getInt("x1"),
|
||||
result.getInt("z1"),
|
||||
result.getInt("x2"),
|
||||
result.getInt("z2"),
|
||||
vertices(id)
|
||||
);
|
||||
}
|
||||
|
||||
private CoalNpc mapNpc(ResultSet result) throws SQLException {
|
||||
return new CoalNpc(
|
||||
result.getLong("id"),
|
||||
result.getInt("citizens_id"),
|
||||
UUID.fromString(result.getString("account_uuid")),
|
||||
result.getString("npc_type"),
|
||||
result.getString("name"),
|
||||
result.getLong("zone_id"),
|
||||
result.getString("world"),
|
||||
result.getDouble("x"),
|
||||
result.getDouble("y"),
|
||||
result.getDouble("z")
|
||||
);
|
||||
}
|
||||
|
||||
private List<ClaimPoint> vertices(long zoneId) throws SQLException {
|
||||
List<ClaimPoint> points = new ArrayList<>();
|
||||
try (PreparedStatement statement = database.connection().prepareStatement(
|
||||
"SELECT x, z FROM npc_zone_vertices WHERE zone_id = ? ORDER BY vertex_order")) {
|
||||
statement.setLong(1, zoneId);
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
while (result.next()) {
|
||||
points.add(new ClaimPoint(result.getInt("x"), result.getInt("z")));
|
||||
}
|
||||
}
|
||||
}
|
||||
return points;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.librewiki.coalgov.storage;
|
||||
|
||||
import com.librewiki.coalgov.model.Permit;
|
||||
import com.librewiki.coalgov.model.PermitType;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class PermitRepository {
|
||||
private final Database database;
|
||||
|
||||
public PermitRepository(Database database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
public long create(UUID owner, PermitType permitType, String regionName, Long expiresAt) throws SQLException {
|
||||
try (PreparedStatement insert = database.connection().prepareStatement("""
|
||||
INSERT INTO permits(owner_uuid, permit_type, region_name, expires_at, created_at)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""", Statement.RETURN_GENERATED_KEYS)) {
|
||||
insert.setString(1, owner.toString());
|
||||
insert.setString(2, permitType.name());
|
||||
insert.setString(3, regionName);
|
||||
if (expiresAt == null) {
|
||||
insert.setObject(4, null);
|
||||
} else {
|
||||
insert.setLong(4, expiresAt);
|
||||
}
|
||||
insert.setLong(5, System.currentTimeMillis());
|
||||
insert.executeUpdate();
|
||||
try (ResultSet keys = insert.getGeneratedKeys()) {
|
||||
return keys.next() ? keys.getLong(1) : -1L;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasActive(UUID owner, PermitType permitType, String regionName, long now) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
SELECT 1 FROM permits
|
||||
WHERE owner_uuid = ? AND permit_type = ? AND (region_name IS NULL OR lower(region_name) = lower(?))
|
||||
AND (expires_at IS NULL OR expires_at > ?)
|
||||
LIMIT 1
|
||||
""")) {
|
||||
statement.setString(1, owner.toString());
|
||||
statement.setString(2, permitType.name());
|
||||
statement.setString(3, regionName);
|
||||
statement.setLong(4, now);
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
return result.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<Permit> listActive(UUID owner, long now) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
SELECT * FROM permits
|
||||
WHERE owner_uuid = ? AND (expires_at IS NULL OR expires_at > ?)
|
||||
ORDER BY id
|
||||
""")) {
|
||||
statement.setString(1, owner.toString());
|
||||
statement.setLong(2, now);
|
||||
List<Permit> permits = new ArrayList<>();
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
while (result.next()) {
|
||||
permits.add(map(result));
|
||||
}
|
||||
}
|
||||
return permits;
|
||||
}
|
||||
}
|
||||
|
||||
private Permit map(ResultSet result) throws SQLException {
|
||||
long expiresAt = result.getLong("expires_at");
|
||||
Long nullableExpiresAt = result.wasNull() ? null : expiresAt;
|
||||
return new Permit(
|
||||
result.getLong("id"),
|
||||
UUID.fromString(result.getString("owner_uuid")),
|
||||
PermitType.valueOf(result.getString("permit_type")),
|
||||
result.getString("region_name"),
|
||||
nullableExpiresAt,
|
||||
result.getLong("created_at")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.librewiki.coalgov.storage;
|
||||
|
||||
import org.bukkit.Location;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public final class SuperFurnaceRepository {
|
||||
private final Database database;
|
||||
|
||||
public SuperFurnaceRepository(Database database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
public void add(Location location) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
INSERT INTO super_furnaces(world, x, y, z, created_at)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
ON CONFLICT(world, x, y, z) DO NOTHING
|
||||
""")) {
|
||||
bindLocation(statement, location);
|
||||
statement.setLong(5, System.currentTimeMillis());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(Location location) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
DELETE FROM super_furnaces
|
||||
WHERE world = ? AND x = ? AND y = ? AND z = ?
|
||||
""")) {
|
||||
bindLocation(statement, location);
|
||||
statement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean exists(Location location) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
SELECT 1 FROM super_furnaces
|
||||
WHERE world = ? AND x = ? AND y = ? AND z = ?
|
||||
""")) {
|
||||
bindLocation(statement, location);
|
||||
try (var result = statement.executeQuery()) {
|
||||
return result.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void bindLocation(PreparedStatement statement, Location location) throws SQLException {
|
||||
statement.setString(1, location.getWorld().getName());
|
||||
statement.setInt(2, location.getBlockX());
|
||||
statement.setInt(3, location.getBlockY());
|
||||
statement.setInt(4, location.getBlockZ());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.librewiki.coalgov.util;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
public final class CoalMoney {
|
||||
public static final long CENTS_PER_COAL = 100L;
|
||||
|
||||
private CoalMoney() {
|
||||
}
|
||||
|
||||
public static long fromCoal(double coal) {
|
||||
return Math.round(coal * CENTS_PER_COAL);
|
||||
}
|
||||
|
||||
public static long fromCoalConfig(double coal) {
|
||||
return Math.max(0L, fromCoal(coal));
|
||||
}
|
||||
|
||||
public static String format(long cents) {
|
||||
boolean negative = cents < 0L;
|
||||
long absolute = Math.abs(cents);
|
||||
long coal = absolute / CENTS_PER_COAL;
|
||||
long coalCents = absolute % CENTS_PER_COAL;
|
||||
String prefix = negative ? "-" : "";
|
||||
if (coal > 0L && coalCents > 0L) {
|
||||
return prefix + coal + " coal " + coalCents + "cc";
|
||||
}
|
||||
if (coal > 0L) {
|
||||
return prefix + coal + " coal";
|
||||
}
|
||||
return prefix + coalCents + "cc";
|
||||
}
|
||||
|
||||
public static long parsePositive(String raw) {
|
||||
long amount = parse(raw);
|
||||
return amount > 0L ? amount : -1L;
|
||||
}
|
||||
|
||||
public static long parse(String raw) {
|
||||
if (raw == null) {
|
||||
return -1L;
|
||||
}
|
||||
String normalized = raw.trim().toLowerCase();
|
||||
if (normalized.isEmpty() || normalized.startsWith("-")) {
|
||||
return -1L;
|
||||
}
|
||||
try {
|
||||
if (normalized.endsWith("cc")) {
|
||||
return Long.parseLong(normalized.substring(0, normalized.length() - 2));
|
||||
}
|
||||
if (normalized.endsWith("c")) {
|
||||
return parseDecimalCoal(normalized.substring(0, normalized.length() - 1));
|
||||
}
|
||||
if (normalized.endsWith("coal")) {
|
||||
return parseDecimalCoal(normalized.substring(0, normalized.length() - 4));
|
||||
}
|
||||
return parseDecimalCoal(normalized);
|
||||
} catch (ArithmeticException | NumberFormatException exception) {
|
||||
return -1L;
|
||||
}
|
||||
}
|
||||
|
||||
private static long parseDecimalCoal(String raw) {
|
||||
BigDecimal coal = new BigDecimal(raw.trim());
|
||||
BigDecimal cents = coal.multiply(BigDecimal.valueOf(CENTS_PER_COAL));
|
||||
return cents.setScale(0, RoundingMode.HALF_UP).longValueExact();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.librewiki.coalgov.util;
|
||||
|
||||
public record Cuboid2D(String world, int x1, int z1, int x2, int z2) {
|
||||
public Cuboid2D {
|
||||
int minX = Math.min(x1, x2);
|
||||
int maxX = Math.max(x1, x2);
|
||||
int minZ = Math.min(z1, z2);
|
||||
int maxZ = Math.max(z1, z2);
|
||||
x1 = minX;
|
||||
x2 = maxX;
|
||||
z1 = minZ;
|
||||
z2 = maxZ;
|
||||
}
|
||||
|
||||
public boolean contains(String worldName, int x, int z) {
|
||||
return world.equals(worldName) && x >= x1 && x <= x2 && z >= z1 && z <= z2;
|
||||
}
|
||||
|
||||
public boolean overlaps(Cuboid2D other) {
|
||||
return world.equals(other.world)
|
||||
&& x1 <= other.x2
|
||||
&& x2 >= other.x1
|
||||
&& z1 <= other.z2
|
||||
&& z2 >= other.z1;
|
||||
}
|
||||
|
||||
public long area() {
|
||||
return (long) (x2 - x1 + 1) * (z2 - z1 + 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.librewiki.coalgov.util;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
|
||||
public final class Messages {
|
||||
private final FileConfiguration config;
|
||||
|
||||
public Messages(FileConfiguration config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public String prefix() {
|
||||
return color(config.getString("messages.prefix", "&8[&6CoalGov&8]&r "));
|
||||
}
|
||||
|
||||
public String text(String message) {
|
||||
return prefix() + color(message);
|
||||
}
|
||||
|
||||
public String raw(String message) {
|
||||
return color(message);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private String color(String message) {
|
||||
return ChatColor.translateAlternateColorCodes('&', message);
|
||||
}
|
||||
}
|
||||
152
CoalGov/src/main/resources/config.yml
Normal file
152
CoalGov/src/main/resources/config.yml
Normal file
@@ -0,0 +1,152 @@
|
||||
economy:
|
||||
starting_balance: 0
|
||||
deposit_coal: true
|
||||
deposit_coal_blocks: true
|
||||
|
||||
government:
|
||||
taxes:
|
||||
market_sale_percent: 5.0
|
||||
market_purchase_percent: 5.0
|
||||
|
||||
npc:
|
||||
ai:
|
||||
enabled: true
|
||||
api_key: ""
|
||||
model: "openai/gpt-4.1-mini"
|
||||
timeout_ms: 4000
|
||||
trader:
|
||||
max_discount_percent: 15
|
||||
max_surcharge_percent: 20
|
||||
worker:
|
||||
tick_interval_seconds: 20
|
||||
produce_material: BREAD
|
||||
produce_amount: 2
|
||||
|
||||
claims:
|
||||
resale:
|
||||
min_condition_multiplier: 0.25
|
||||
resource_values:
|
||||
COBBLESTONE: 0.02
|
||||
COAL: 1.0
|
||||
COAL_BLOCK: 9.0
|
||||
RAW_COPPER: 0.5
|
||||
COPPER_INGOT: 1.0
|
||||
RAW_IRON: 1.0
|
||||
IRON_INGOT: 2.0
|
||||
RAW_GOLD: 2.0
|
||||
GOLD_INGOT: 4.0
|
||||
DIAMOND: 32.0
|
||||
EMERALD: 12.0
|
||||
homestead:
|
||||
base_cost: 32
|
||||
cost_per_block: 0.05
|
||||
max_radius: 45
|
||||
first_claim:
|
||||
free: true
|
||||
max_radius: 12
|
||||
industrial:
|
||||
base_cost: 128
|
||||
cost_per_block: 0.15
|
||||
max_radius: 96
|
||||
|
||||
permits:
|
||||
mining:
|
||||
cost: 64
|
||||
duration_hours: 72
|
||||
|
||||
divining_rod:
|
||||
scan_radius: 4
|
||||
scan_depth: 48
|
||||
cooldown_seconds: 5
|
||||
|
||||
market:
|
||||
dynamic:
|
||||
initial_stock: 1024
|
||||
target_stock: 1024
|
||||
min_multiplier: 0.35
|
||||
max_multiplier: 3.0
|
||||
sell_multiplier: 0.85
|
||||
buy_multiplier: 1.15
|
||||
initial_stock:
|
||||
WHEAT: 0
|
||||
BREAD: 0
|
||||
APPLE: 0
|
||||
CARROT: 0
|
||||
POTATO: 0
|
||||
BAKED_POTATO: 0
|
||||
BEETROOT: 0
|
||||
MELON_SLICE: 0
|
||||
SWEET_BERRIES: 0
|
||||
GLOW_BERRIES: 0
|
||||
COOKIE: 0
|
||||
PUMPKIN_PIE: 0
|
||||
COOKED_CHICKEN: 0
|
||||
COOKED_PORKCHOP: 0
|
||||
COOKED_BEEF: 0
|
||||
COOKED_MUTTON: 0
|
||||
COOKED_RABBIT: 0
|
||||
COOKED_COD: 0
|
||||
prices:
|
||||
DIRT: 0.01
|
||||
COBBLESTONE: 0.02
|
||||
SAND: 0.05
|
||||
GRAVEL: 0.04
|
||||
OAK_LOG: 0.25
|
||||
SPRUCE_LOG: 0.25
|
||||
BIRCH_LOG: 0.25
|
||||
TORCH: 0.10
|
||||
COAL: 1.0
|
||||
COAL_BLOCK: 9.0
|
||||
RAW_COPPER: 0.5
|
||||
COPPER_INGOT: 1.0
|
||||
RAW_IRON: 1.0
|
||||
IRON_INGOT: 2.0
|
||||
RAW_GOLD: 2.0
|
||||
GOLD_INGOT: 4.0
|
||||
DIAMOND: 32.0
|
||||
EMERALD: 12.0
|
||||
WHEAT: 0.35
|
||||
BREAD: 1.25
|
||||
APPLE: 0.75
|
||||
CARROT: 0.25
|
||||
POTATO: 0.20
|
||||
BAKED_POTATO: 0.45
|
||||
BEETROOT: 0.20
|
||||
MELON_SLICE: 0.15
|
||||
SWEET_BERRIES: 0.20
|
||||
GLOW_BERRIES: 0.35
|
||||
COOKIE: 0.25
|
||||
PUMPKIN_PIE: 1.0
|
||||
COOKED_CHICKEN: 1.25
|
||||
COOKED_PORKCHOP: 1.5
|
||||
COOKED_BEEF: 1.75
|
||||
COOKED_MUTTON: 1.5
|
||||
COOKED_RABBIT: 1.25
|
||||
COOKED_COD: 1.0
|
||||
|
||||
land:
|
||||
default_land_class: FREEHOLD
|
||||
deny_mining_in_government: true
|
||||
deny_mining_in_protected_preserve: true
|
||||
deny_mining_in_border_zone: true
|
||||
|
||||
spawn:
|
||||
protection_radius: 32
|
||||
guide:
|
||||
create_government_region: true
|
||||
lines:
|
||||
- "COALGOV BASIN"
|
||||
- "Free first"
|
||||
- "homestead:"
|
||||
- "/claim buy"
|
||||
- "Use /claim wand"
|
||||
- "for polygons"
|
||||
- "Coal seams:"
|
||||
- "redwood ridges"
|
||||
- "Permits:"
|
||||
- "/permit buy"
|
||||
- "mining <region>"
|
||||
- "/market trades"
|
||||
|
||||
messages:
|
||||
prefix: "&8[&6CoalGov&8]&r "
|
||||
70
CoalGov/src/main/resources/plugin.yml
Normal file
70
CoalGov/src/main/resources/plugin.yml
Normal file
@@ -0,0 +1,70 @@
|
||||
name: CoalGov
|
||||
version: 1.0.0
|
||||
main: com.librewiki.coalgov.CoalGovPlugin
|
||||
api-version: '1.21'
|
||||
author: LibreWiki
|
||||
description: Coal-backed land, claims, permits, and mining governance.
|
||||
depend: [Citizens]
|
||||
|
||||
commands:
|
||||
coal:
|
||||
description: CoalGov economy commands.
|
||||
usage: /coal <balance|deposit|withdraw|pay|fines>
|
||||
permission: coalgov.coal
|
||||
claim:
|
||||
description: CoalGov claim commands.
|
||||
usage: /claim <wand|buy|info|list|show|appraise|sell|transfer|abandon>
|
||||
permission: coalgov.claim
|
||||
permit:
|
||||
description: CoalGov permit commands.
|
||||
usage: /permit <buy|list>
|
||||
permission: coalgov.permit
|
||||
land:
|
||||
description: CoalGov land commands.
|
||||
usage: /land <info|create|delete|list>
|
||||
market:
|
||||
description: CoalGov resource market.
|
||||
usage: /market
|
||||
permission: coalgov.market
|
||||
diviningrod:
|
||||
description: Receive a CoalGov divining rod.
|
||||
usage: /diviningrod
|
||||
permission: coalgov.diviningrod
|
||||
police:
|
||||
description: CoalGov police commands.
|
||||
usage: /police <fine>
|
||||
permission: coalgov.police
|
||||
cgnpc:
|
||||
description: CoalGov NPC commands.
|
||||
usage: /cgnpc <zone|create|remove|list|stock|funds|haggle>
|
||||
permission: coalgov.admin
|
||||
coalgov:
|
||||
description: CoalGov admin commands.
|
||||
usage: /coalgov admin <balance|grant|take|reload|spawn|bypass|rod|treasury|tax>
|
||||
permission: coalgov.admin
|
||||
|
||||
permissions:
|
||||
coalgov.admin:
|
||||
description: Allows CoalGov admin commands. Gameplay bypass must be enabled with /coalgov admin bypass on.
|
||||
default: op
|
||||
coalgov.coal:
|
||||
description: Allows coal economy commands.
|
||||
default: true
|
||||
coalgov.claim:
|
||||
description: Allows claim commands.
|
||||
default: true
|
||||
coalgov.permit:
|
||||
description: Allows permit commands.
|
||||
default: true
|
||||
coalgov.land.info:
|
||||
description: Allows land info commands.
|
||||
default: true
|
||||
coalgov.market:
|
||||
description: Allows market trading.
|
||||
default: true
|
||||
coalgov.diviningrod:
|
||||
description: Allows receiving a divining rod.
|
||||
default: true
|
||||
coalgov.police:
|
||||
description: Allows issuing police fines.
|
||||
default: op
|
||||
Reference in New Issue
Block a user