Add newer CoalGov update
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package com.librewiki.coalgov;
|
||||
|
||||
import com.librewiki.coalgov.command.AdminCommand;
|
||||
import com.librewiki.coalgov.command.AuctionCommand;
|
||||
import com.librewiki.coalgov.command.ClaimCommand;
|
||||
import com.librewiki.coalgov.command.CoalCommand;
|
||||
import com.librewiki.coalgov.command.DiviningRodCommand;
|
||||
@@ -9,6 +10,7 @@ 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.command.PreserveCommand;
|
||||
import com.librewiki.coalgov.command.ShowPropertyLinesCommand;
|
||||
import com.librewiki.coalgov.listener.BlockBreakListener;
|
||||
import com.librewiki.coalgov.listener.BlockPlaceListener;
|
||||
@@ -16,10 +18,13 @@ import com.librewiki.coalgov.listener.AnimalProtectionListener;
|
||||
import com.librewiki.coalgov.listener.ClaimWandListener;
|
||||
import com.librewiki.coalgov.listener.DiviningRodListener;
|
||||
import com.librewiki.coalgov.listener.MarketListener;
|
||||
import com.librewiki.coalgov.listener.NameTagRecoveryListener;
|
||||
import com.librewiki.coalgov.listener.NpcListener;
|
||||
import com.librewiki.coalgov.listener.PlayerJoinListener;
|
||||
import com.librewiki.coalgov.listener.PropertyInteractListener;
|
||||
import com.librewiki.coalgov.listener.PreserveProtectionListener;
|
||||
import com.librewiki.coalgov.listener.SuperFurnaceListener;
|
||||
import com.librewiki.coalgov.service.AuctionService;
|
||||
import com.librewiki.coalgov.service.BuildProtectionService;
|
||||
import com.librewiki.coalgov.service.ClaimMarkerService;
|
||||
import com.librewiki.coalgov.service.ClaimSelectionService;
|
||||
@@ -30,23 +35,28 @@ 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.LotteryService;
|
||||
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.PreserveWorldService;
|
||||
import com.librewiki.coalgov.service.SpawnGuideService;
|
||||
import com.librewiki.coalgov.service.SuperFurnaceService;
|
||||
import com.librewiki.coalgov.service.TaxService;
|
||||
import com.librewiki.coalgov.service.WebTokenService;
|
||||
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.LotteryRepository;
|
||||
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.storage.WebTokenRepository;
|
||||
import com.librewiki.coalgov.util.CoalMoney;
|
||||
import com.librewiki.coalgov.util.Messages;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -61,6 +71,7 @@ import java.util.UUID;
|
||||
public final class CoalGovPlugin extends JavaPlugin {
|
||||
private Database database;
|
||||
private EconomyService economyService;
|
||||
private AuctionService auctionService;
|
||||
private GovernmentService governmentService;
|
||||
private ClaimService claimService;
|
||||
private ClaimMarkerService claimMarkerService;
|
||||
@@ -72,12 +83,15 @@ public final class CoalGovPlugin extends JavaPlugin {
|
||||
private NpcService npcService;
|
||||
private OpenRouterService openRouterService;
|
||||
private LandService landService;
|
||||
private LotteryService lotteryService;
|
||||
private PermitService permitService;
|
||||
private PreserveWorldService preserveWorldService;
|
||||
private MiningService miningService;
|
||||
private DiviningRodService diviningRodService;
|
||||
private SuperFurnaceService superFurnaceService;
|
||||
private SpawnGuideService spawnGuideService;
|
||||
private TaxService taxService;
|
||||
private WebTokenService webTokenService;
|
||||
private Messages messages;
|
||||
private final Set<UUID> adminBypassPlayers = new HashSet<>();
|
||||
|
||||
@@ -99,11 +113,14 @@ public final class CoalGovPlugin extends JavaPlugin {
|
||||
ClaimRepository claimRepository = new ClaimRepository(database);
|
||||
PermitRepository permitRepository = new PermitRepository(database);
|
||||
MarketRepository marketRepository = new MarketRepository(database);
|
||||
LotteryRepository lotteryRepository = new LotteryRepository(database);
|
||||
GovernmentRepository governmentRepository = new GovernmentRepository(database);
|
||||
SuperFurnaceRepository superFurnaceRepository = new SuperFurnaceRepository(database);
|
||||
NpcRepository npcRepository = new NpcRepository(database);
|
||||
WebTokenRepository webTokenRepository = new WebTokenRepository(database);
|
||||
|
||||
economyService = new EconomyService(economyRepository, CoalMoney.fromCoalConfig(getConfig().getDouble("economy.starting_balance", 0.0D)));
|
||||
auctionService = new AuctionService(this);
|
||||
governmentService = new GovernmentService(governmentRepository, economyService, getConfig());
|
||||
landService = new LandService(landRepository, getConfig());
|
||||
claimService = new ClaimService(claimRepository, landService, economyService, getConfig());
|
||||
@@ -113,16 +130,20 @@ public final class CoalGovPlugin extends JavaPlugin {
|
||||
buildProtectionService = new BuildProtectionService(this, claimService);
|
||||
landAppraisalService = new LandAppraisalService(this, claimService);
|
||||
marketService = new MarketService(this, marketRepository);
|
||||
lotteryService = new LotteryService(lotteryRepository, economyService, governmentService, getConfig());
|
||||
npcService = new NpcService(this, npcRepository);
|
||||
openRouterService = new OpenRouterService(this);
|
||||
permitService = new PermitService(permitRepository, landService, economyService, getConfig());
|
||||
preserveWorldService = new PreserveWorldService(this);
|
||||
miningService = new MiningService(this, landService, claimService, permitService);
|
||||
diviningRodService = new DiviningRodService(this);
|
||||
superFurnaceService = new SuperFurnaceService(this, superFurnaceRepository);
|
||||
spawnGuideService = new SpawnGuideService(this);
|
||||
taxService = new TaxService();
|
||||
webTokenService = new WebTokenService(webTokenRepository, getConfig());
|
||||
|
||||
Objects.requireNonNull(getCommand("coal")).setExecutor(new CoalCommand(this));
|
||||
Objects.requireNonNull(getCommand("auction")).setExecutor(new AuctionCommand(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));
|
||||
@@ -132,6 +153,7 @@ public final class CoalGovPlugin extends JavaPlugin {
|
||||
Objects.requireNonNull(getCommand("police")).setExecutor(new PoliceCommand(this));
|
||||
Objects.requireNonNull(getCommand("cgnpc")).setExecutor(new NpcCommand(this));
|
||||
Objects.requireNonNull(getCommand("coalgov")).setExecutor(new AdminCommand(this));
|
||||
Objects.requireNonNull(getCommand("preserve")).setExecutor(new PreserveCommand(this));
|
||||
|
||||
getServer().getPluginManager().registerEvents(new BlockBreakListener(this), this);
|
||||
getServer().getPluginManager().registerEvents(new BlockPlaceListener(this), this);
|
||||
@@ -140,11 +162,14 @@ public final class CoalGovPlugin extends JavaPlugin {
|
||||
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 NameTagRecoveryListener(this), this);
|
||||
getServer().getPluginManager().registerEvents(new NpcListener(this), this);
|
||||
getServer().getPluginManager().registerEvents(new PlayerJoinListener(this), this);
|
||||
getServer().getPluginManager().registerEvents(new SuperFurnaceListener(this), this);
|
||||
getServer().getPluginManager().registerEvents(new PreserveProtectionListener(this), this);
|
||||
superFurnaceService.registerRecipe();
|
||||
getServer().getScheduler().runTaskLater(this, () -> claimMarkerService.placeAllLoadedMarkers(), 40L);
|
||||
getServer().getScheduler().runTaskLater(this, () -> preserveWorldService.loadPreserveWorld(), 60L);
|
||||
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.");
|
||||
@@ -152,6 +177,9 @@ public final class CoalGovPlugin extends JavaPlugin {
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (auctionService != null) {
|
||||
auctionService.cancelAll();
|
||||
}
|
||||
if (database != null) {
|
||||
database.close();
|
||||
}
|
||||
@@ -164,6 +192,9 @@ public final class CoalGovPlugin extends JavaPlugin {
|
||||
landService.setConfig(getConfig());
|
||||
claimService.setConfig(getConfig());
|
||||
permitService.setConfig(getConfig());
|
||||
lotteryService.setConfig(getConfig());
|
||||
webTokenService.setConfig(getConfig());
|
||||
preserveWorldService.setConfig(getConfig());
|
||||
superFurnaceService.registerRecipe();
|
||||
}
|
||||
|
||||
@@ -183,6 +214,10 @@ public final class CoalGovPlugin extends JavaPlugin {
|
||||
return economyService;
|
||||
}
|
||||
|
||||
public AuctionService auctionService() {
|
||||
return auctionService;
|
||||
}
|
||||
|
||||
public GovernmentService governmentService() {
|
||||
return governmentService;
|
||||
}
|
||||
@@ -215,6 +250,10 @@ public final class CoalGovPlugin extends JavaPlugin {
|
||||
return marketService;
|
||||
}
|
||||
|
||||
public LotteryService lotteryService() {
|
||||
return lotteryService;
|
||||
}
|
||||
|
||||
public NpcService npcService() {
|
||||
return npcService;
|
||||
}
|
||||
@@ -251,6 +290,14 @@ public final class CoalGovPlugin extends JavaPlugin {
|
||||
return taxService;
|
||||
}
|
||||
|
||||
public WebTokenService webTokenService() {
|
||||
return webTokenService;
|
||||
}
|
||||
|
||||
public PreserveWorldService preserveWorldService() {
|
||||
return preserveWorldService;
|
||||
}
|
||||
|
||||
public Messages messages() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
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.service.ClaimService;
|
||||
import com.librewiki.coalgov.util.CoalMoney;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
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.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class AdminCommand implements CommandExecutor {
|
||||
private final CoalGovPlugin plugin;
|
||||
@@ -21,6 +30,10 @@ public final class AdminCommand implements CommandExecutor {
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (args.length == 0 || !args[0].equalsIgnoreCase("admin")) {
|
||||
if (args.length == 1 && args[0].equalsIgnoreCase("webtoken")) {
|
||||
webToken(sender);
|
||||
return true;
|
||||
}
|
||||
usage(sender);
|
||||
return true;
|
||||
}
|
||||
@@ -46,6 +59,14 @@ public final class AdminCommand implements CommandExecutor {
|
||||
rod(sender, args);
|
||||
return true;
|
||||
}
|
||||
if (args.length >= 2 && args[1].equalsIgnoreCase("furnace")) {
|
||||
furnace(sender, args);
|
||||
return true;
|
||||
}
|
||||
if (args.length >= 2 && args[1].equalsIgnoreCase("mob")) {
|
||||
mob(sender, args);
|
||||
return true;
|
||||
}
|
||||
if (args.length >= 2 && args[1].equalsIgnoreCase("treasury")) {
|
||||
treasury(sender, args);
|
||||
return true;
|
||||
@@ -54,6 +75,10 @@ public final class AdminCommand implements CommandExecutor {
|
||||
tax(sender, args);
|
||||
return true;
|
||||
}
|
||||
if (args.length >= 2 && args[1].equalsIgnoreCase("govtland")) {
|
||||
govtLand(sender, args);
|
||||
return true;
|
||||
}
|
||||
if (args.length < 3) {
|
||||
usage(sender);
|
||||
return true;
|
||||
@@ -75,7 +100,24 @@ public final class AdminCommand implements CommandExecutor {
|
||||
}
|
||||
|
||||
private void usage(CommandSender sender) {
|
||||
sender.sendMessage(plugin.messages().text("&eUsage: /coalgov admin <balance|grant|take|reload|spawn|bypass|rod|treasury|tax>"));
|
||||
sender.sendMessage(plugin.messages().text("&eUsage: /coalgov webtoken"));
|
||||
sender.sendMessage(plugin.messages().text("&eAdmin: /coalgov admin <balance|grant|take|reload|spawn|bypass|rod|furnace|mob|treasury|tax|govtland>"));
|
||||
}
|
||||
|
||||
private void webToken(CommandSender sender) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage("Players only.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
plugin.economyService().ensurePlayer(player);
|
||||
var issue = plugin.webTokenService().issue(player);
|
||||
player.sendMessage(plugin.messages().text("&eYour property management token is &f" + issue.token()
|
||||
+ "&e. This token expires in &f" + issue.expiresMinutes() + " &eminutes and is single-use."));
|
||||
} catch (SQLException exception) {
|
||||
plugin.getLogger().warning("Web token failed: " + exception.getMessage());
|
||||
player.sendMessage(plugin.messages().text("&cWeb token service unavailable."));
|
||||
}
|
||||
}
|
||||
|
||||
private void bypass(CommandSender sender, String[] args) {
|
||||
@@ -131,6 +173,48 @@ public final class AdminCommand implements CommandExecutor {
|
||||
player.sendMessage(plugin.messages().text("&aDivining rod granted."));
|
||||
}
|
||||
|
||||
private void furnace(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 furnace"));
|
||||
return;
|
||||
}
|
||||
player.getInventory().addItem(plugin.superFurnaceService().item());
|
||||
player.sendMessage(plugin.messages().text("&aSuper furnace granted."));
|
||||
}
|
||||
|
||||
private void mob(CommandSender sender, String[] args) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage("Players only.");
|
||||
return;
|
||||
}
|
||||
if (args.length < 3 || args.length > 4) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /coalgov admin mob <wandering_trader|trader_llama> [count]"));
|
||||
return;
|
||||
}
|
||||
EntityType type = switch (args[2].toLowerCase()) {
|
||||
case "wandering_trader", "trader" -> EntityType.WANDERING_TRADER;
|
||||
case "trader_llama", "llama" -> EntityType.TRADER_LLAMA;
|
||||
default -> null;
|
||||
};
|
||||
int count = args.length == 4 ? (int) parseAmount(args[3]) : 1;
|
||||
if (type == null || count <= 0 || count > 16) {
|
||||
player.sendMessage(plugin.messages().text("&cInvalid mob type or count."));
|
||||
return;
|
||||
}
|
||||
Location location = player.getLocation();
|
||||
for (int i = 0; i < count; i++) {
|
||||
var entity = player.getWorld().spawnEntity(location, type);
|
||||
if (entity.getType() == EntityType.WANDERING_TRADER) {
|
||||
entity.getWorld().dropItemNaturally(location, new ItemStack(Material.EMERALD, 1));
|
||||
}
|
||||
}
|
||||
player.sendMessage(plugin.messages().text("&aSpawned &f" + count + " &a" + args[2] + "."));
|
||||
}
|
||||
|
||||
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())));
|
||||
@@ -179,6 +263,93 @@ public final class AdminCommand implements CommandExecutor {
|
||||
sender.sendMessage(plugin.messages().text("&eUsage: /coalgov admin tax exempt <player> <on|off|status>"));
|
||||
}
|
||||
|
||||
private void govtLand(CommandSender sender, String[] args) throws SQLException {
|
||||
if (args.length < 3) {
|
||||
sender.sendMessage(plugin.messages().text("&eUsage: /coalgov admin govtland <create|sell|info>"));
|
||||
return;
|
||||
}
|
||||
switch (args[2].toLowerCase()) {
|
||||
case "create" -> govtLandCreate(sender, args);
|
||||
case "sell" -> govtLandSell(sender, args);
|
||||
case "info" -> govtLandInfo(sender, args);
|
||||
default -> sender.sendMessage(plugin.messages().text("&eUsage: /coalgov admin govtland <create|sell|info>"));
|
||||
}
|
||||
}
|
||||
|
||||
private void govtLandCreate(CommandSender sender, String[] args) throws SQLException {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage("Players only.");
|
||||
return;
|
||||
}
|
||||
if (args.length != 4 && args.length != 5) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /coalgov admin govtland create <homestead|industrial> [radius]"));
|
||||
return;
|
||||
}
|
||||
ClaimService.BuyResult result;
|
||||
if (args.length == 5) {
|
||||
int radius = parseInt(args[4]);
|
||||
result = plugin.claimService().createGovernmentClaim(player.getUniqueId(), player.getLocation(), args[3], radius);
|
||||
} else {
|
||||
List<ClaimPoint> points = plugin.claimSelectionService().claimPoints(player);
|
||||
result = plugin.claimService().createGovernmentPolygon(player.getUniqueId(), player.getWorld().getName(), args[3], points);
|
||||
}
|
||||
if (!result.success()) {
|
||||
player.sendMessage(plugin.messages().text("&c" + result.message()));
|
||||
return;
|
||||
}
|
||||
plugin.claimSelectionService().consume(player);
|
||||
plugin.claimService().findById(result.id()).ifPresent(claim -> {
|
||||
plugin.claimMarkerService().placeMarkers(claim);
|
||||
plugin.claimVisualService().show(player, claim);
|
||||
});
|
||||
player.sendMessage(plugin.messages().text("&aGovernment claim #" + result.id() + " created. You were granted conventional claim permissions on it."));
|
||||
}
|
||||
|
||||
private void govtLandSell(CommandSender sender, String[] args) throws SQLException {
|
||||
if (args.length != 6) {
|
||||
sender.sendMessage(plugin.messages().text("&eUsage: /coalgov admin govtland sell <id> <player> <price>"));
|
||||
return;
|
||||
}
|
||||
long id = parseLong(args[3]);
|
||||
long price = CoalMoney.parsePositive(args[5]);
|
||||
if (id <= 0L || price <= 0L) {
|
||||
sender.sendMessage(plugin.messages().text("&cInvalid claim id or price."));
|
||||
return;
|
||||
}
|
||||
OfflinePlayer target = Bukkit.getOfflinePlayer(args[4]);
|
||||
plugin.economyService().ensurePlayer(target);
|
||||
ClaimService.TransferResult result = plugin.claimService().sellGovernmentClaim(id, target.getUniqueId(), price);
|
||||
if (!result.success()) {
|
||||
sender.sendMessage(plugin.messages().text("&c" + result.message()));
|
||||
return;
|
||||
}
|
||||
plugin.claimMarkerService().removeMarkers(result.claim());
|
||||
plugin.claimService().findById(id).ifPresent(plugin.claimMarkerService()::placeMarkers);
|
||||
plugin.governmentService().collectMarketRevenue(price);
|
||||
String targetName = target.getName() == null ? target.getUniqueId().toString() : target.getName();
|
||||
sender.sendMessage(plugin.messages().text("&aSold government claim #" + id + " to &f" + targetName + " &afor &f" + CoalMoney.format(price) + "&a."));
|
||||
}
|
||||
|
||||
private void govtLandInfo(CommandSender sender, String[] args) throws SQLException {
|
||||
if (args.length != 4) {
|
||||
sender.sendMessage(plugin.messages().text("&eUsage: /coalgov admin govtland info <id>"));
|
||||
return;
|
||||
}
|
||||
long id = parseLong(args[3]);
|
||||
if (id <= 0L) {
|
||||
sender.sendMessage(plugin.messages().text("&cInvalid claim id."));
|
||||
return;
|
||||
}
|
||||
Optional<Claim> claim = plugin.claimService().findById(id);
|
||||
if (claim.isEmpty()) {
|
||||
sender.sendMessage(plugin.messages().text("&cNo claim with that id."));
|
||||
return;
|
||||
}
|
||||
Claim c = claim.get();
|
||||
sender.sendMessage(plugin.messages().text("&eClaim #" + c.id() + ": &f" + c.claimType() + " owner " + c.ownerUuid()
|
||||
+ (plugin.claimService().isGovernmentClaim(c) ? " &a(government)" : "")));
|
||||
}
|
||||
|
||||
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>"));
|
||||
@@ -210,4 +381,28 @@ public final class AdminCommand implements CommandExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private long parseAmount(String raw) {
|
||||
try {
|
||||
return Long.parseLong(raw);
|
||||
} catch (NumberFormatException exception) {
|
||||
return -1L;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package com.librewiki.coalgov.command;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.service.AuctionService;
|
||||
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;
|
||||
|
||||
public final class AuctionCommand implements CommandExecutor {
|
||||
private final CoalGovPlugin plugin;
|
||||
|
||||
public AuctionCommand(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);
|
||||
if (args.length == 0 || args[0].equalsIgnoreCase("status")) {
|
||||
status(player);
|
||||
} else if (args[0].equalsIgnoreCase("start")) {
|
||||
start(player, args);
|
||||
} else if (args[0].equalsIgnoreCase("bid")) {
|
||||
bid(player, args);
|
||||
} else if (args[0].equalsIgnoreCase("cancel")) {
|
||||
if (!plugin.auctionService().cancel(player)) {
|
||||
player.sendMessage(plugin.messages().text("&cNo cancellable auction."));
|
||||
}
|
||||
} else {
|
||||
usage(player);
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
plugin.getLogger().warning("Auction command failed: " + exception.getMessage());
|
||||
player.sendMessage(plugin.messages().text("&cAuction ledger unavailable."));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void start(Player player, String[] args) throws SQLException {
|
||||
if (args.length < 4 || args.length > 5) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /auction start <material> <amount> <minBid> [seconds]"));
|
||||
return;
|
||||
}
|
||||
Material material = Material.matchMaterial(args[1]);
|
||||
int amount = (int) parseLong(args[2]);
|
||||
long minimumBid = CoalMoney.parsePositive(args[3]);
|
||||
int seconds = args.length == 5 ? (int) parseLong(args[4]) : plugin.getConfig().getInt("auction.default_seconds", 120);
|
||||
if (material == null || amount <= 0 || minimumBid <= 0L) {
|
||||
player.sendMessage(plugin.messages().text("&cInvalid material, amount, or bid."));
|
||||
return;
|
||||
}
|
||||
var result = plugin.auctionService().start(player, material, amount, minimumBid, seconds);
|
||||
if (!result.success()) {
|
||||
player.sendMessage(plugin.messages().text("&c" + result.message()));
|
||||
}
|
||||
}
|
||||
|
||||
private void bid(Player player, String[] args) throws SQLException {
|
||||
if (args.length != 2) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /auction bid <amount>"));
|
||||
return;
|
||||
}
|
||||
long amount = CoalMoney.parsePositive(args[1]);
|
||||
if (amount <= 0L) {
|
||||
player.sendMessage(plugin.messages().text("&cInvalid bid."));
|
||||
return;
|
||||
}
|
||||
var result = plugin.auctionService().bid(player, amount);
|
||||
if (!result.success()) {
|
||||
player.sendMessage(plugin.messages().text("&c" + result.message()));
|
||||
}
|
||||
}
|
||||
|
||||
private void status(Player player) {
|
||||
AuctionService.Status status = plugin.auctionService().status();
|
||||
if (status == null) {
|
||||
player.sendMessage(plugin.messages().text("&7No auction is running."));
|
||||
return;
|
||||
}
|
||||
String highBid = status.highBid() > 0L
|
||||
? CoalMoney.format(status.highBid()) + " by " + status.highBidderName()
|
||||
: "none";
|
||||
player.sendMessage(plugin.messages().text("&eAuction: &f" + status.amount() + " "
|
||||
+ status.material().name().toLowerCase().replace('_', ' ')
|
||||
+ " &7seller &f" + status.sellerName()
|
||||
+ " &7minimum &f" + CoalMoney.format(status.minimumBid())
|
||||
+ " &7high bid &f" + highBid
|
||||
+ " &7time &f" + status.remainingSeconds() + "s"));
|
||||
}
|
||||
|
||||
private void usage(Player player) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /auction <start|bid|status|cancel>"));
|
||||
}
|
||||
|
||||
private long parseLong(String raw) {
|
||||
try {
|
||||
return Long.parseLong(raw);
|
||||
} catch (NumberFormatException exception) {
|
||||
return -1L;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,12 +213,12 @@ public final class ClaimCommand implements CommandExecutor {
|
||||
}
|
||||
|
||||
private void trust(Player player, String[] args) throws SQLException {
|
||||
if (args.length != 5) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /claim trust <id> <player> <build|interact|container|animal|manage|all>"));
|
||||
if (args.length != 4) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /claim trust <id> <player> <build|interact|container|animal|manage|all|any>"));
|
||||
return;
|
||||
}
|
||||
long id = parseLong(args[1]);
|
||||
List<ClaimPermission> permissions = ClaimPermission.grantSet(args[4]);
|
||||
List<ClaimPermission> permissions = ClaimPermission.grantSet(args[3]);
|
||||
if (id <= 0L || permissions.isEmpty()) {
|
||||
player.sendMessage(plugin.messages().text("&cInvalid claim id or permission."));
|
||||
return;
|
||||
@@ -233,12 +233,12 @@ public final class ClaimCommand implements CommandExecutor {
|
||||
return;
|
||||
}
|
||||
}
|
||||
player.sendMessage(plugin.messages().text("&aGranted &f" + args[4].toLowerCase() + " &aaccess on claim #" + id + " to &f" + targetName(target) + "&a."));
|
||||
player.sendMessage(plugin.messages().text("&aGranted &f" + args[3].toLowerCase() + " &aaccess on claim #" + id + " to &f" + targetName(target) + "&a."));
|
||||
}
|
||||
|
||||
private void untrust(Player player, String[] args) throws SQLException {
|
||||
if (args.length != 5) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /claim untrust <id> <player> <build|interact|container|animal|manage|all>"));
|
||||
if (args.length != 4) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /claim untrust <id> <player> <build|interact|container|animal|manage|all|any>"));
|
||||
return;
|
||||
}
|
||||
long id = parseLong(args[1]);
|
||||
@@ -247,7 +247,7 @@ public final class ClaimCommand implements CommandExecutor {
|
||||
return;
|
||||
}
|
||||
OfflinePlayer target = Bukkit.getOfflinePlayer(args[2]);
|
||||
if (args[4].equalsIgnoreCase("all")) {
|
||||
if (args[3].equalsIgnoreCase("all") || args[3].equalsIgnoreCase("any")) {
|
||||
ClaimService.PermissionResult result = plugin.claimService()
|
||||
.revokeAllPermissions(player.getUniqueId(), id, target.getUniqueId());
|
||||
if (!result.success()) {
|
||||
@@ -257,7 +257,7 @@ public final class ClaimCommand implements CommandExecutor {
|
||||
player.sendMessage(plugin.messages().text("&aRemoved all claim access for &f" + targetName(target) + "&a."));
|
||||
return;
|
||||
}
|
||||
Optional<ClaimPermission> permission = ClaimPermission.parse(args[4]);
|
||||
Optional<ClaimPermission> permission = ClaimPermission.parse(args[3]);
|
||||
if (permission.isEmpty()) {
|
||||
player.sendMessage(plugin.messages().text("&cInvalid permission."));
|
||||
return;
|
||||
|
||||
@@ -28,7 +28,7 @@ public final class CoalCommand implements CommandExecutor {
|
||||
return true;
|
||||
}
|
||||
if (args.length == 0) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /coal <balance|deposit|withdraw|pay|fines>"));
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /coal <balance|deposit|withdraw|pay|fines|networth|lottery>"));
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
@@ -39,7 +39,9 @@ public final class CoalCommand implements CommandExecutor {
|
||||
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>"));
|
||||
case "networth" -> networth(player);
|
||||
case "lottery" -> lottery(player, args);
|
||||
default -> player.sendMessage(plugin.messages().text("&eUsage: /coal <balance|deposit|withdraw|pay|fines|networth|lottery>"));
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
plugin.getLogger().warning("Coal command failed: " + exception.getMessage());
|
||||
@@ -52,6 +54,29 @@ public final class CoalCommand implements CommandExecutor {
|
||||
player.sendMessage(plugin.messages().text("&eBalance: &f" + CoalMoney.format(plugin.economyService().balance(player.getUniqueId()))));
|
||||
}
|
||||
|
||||
private void networth(Player player) throws SQLException {
|
||||
long balance = plugin.economyService().balance(player.getUniqueId());
|
||||
long inventory = 0L;
|
||||
for (ItemStack item : player.getInventory().getStorageContents()) {
|
||||
if (item == null || item.getType().isAir()) {
|
||||
continue;
|
||||
}
|
||||
Double price = plugin.marketService().basePrices().get(item.getType());
|
||||
if (price != null) {
|
||||
inventory += CoalMoney.fromCoal(price * item.getAmount());
|
||||
}
|
||||
}
|
||||
long claims = plugin.claimService().list(player.getUniqueId()).stream()
|
||||
.mapToLong(plugin.claimService()::claimPurchaseCost)
|
||||
.filter(value -> value > 0L)
|
||||
.sum();
|
||||
long total = balance + inventory + claims;
|
||||
player.sendMessage(plugin.messages().text("&eNet worth: &f" + CoalMoney.format(total)));
|
||||
player.sendMessage(plugin.messages().text("&7Balance: &f" + CoalMoney.format(balance)
|
||||
+ " &7Inventory: &f" + CoalMoney.format(inventory)
|
||||
+ " &7Claims: &f" + CoalMoney.format(claims)));
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -177,6 +202,53 @@ public final class CoalCommand implements CommandExecutor {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /coal fines [list|pay <id|all>]"));
|
||||
}
|
||||
|
||||
private void lottery(Player player, String[] args) throws SQLException {
|
||||
if (args.length == 1 || (args.length == 2 && args[1].equalsIgnoreCase("status"))) {
|
||||
var status = plugin.lotteryService().status();
|
||||
player.sendMessage(plugin.messages().text("&eLottery round #" + status.round()
|
||||
+ ": &f" + status.tickets() + " &etickets from &f" + status.players()
|
||||
+ " &eplayers. Pot: &f" + CoalMoney.format(status.pot())));
|
||||
return;
|
||||
}
|
||||
if (args.length == 2 && args[1].equalsIgnoreCase("buy")) {
|
||||
buyLottery(player, 1);
|
||||
return;
|
||||
}
|
||||
if (args.length == 3 && args[1].equalsIgnoreCase("buy")) {
|
||||
int tickets = (int) parsePositive(args[2]);
|
||||
buyLottery(player, tickets);
|
||||
return;
|
||||
}
|
||||
if (args.length == 2 && args[1].equalsIgnoreCase("draw")) {
|
||||
if (!player.hasPermission("coalgov.admin")) {
|
||||
player.sendMessage(plugin.messages().text("&cOnly CoalGov admins can draw the lottery."));
|
||||
return;
|
||||
}
|
||||
var result = plugin.lotteryService().draw();
|
||||
if (!result.success()) {
|
||||
player.sendMessage(plugin.messages().text("&c" + result.message()));
|
||||
return;
|
||||
}
|
||||
Bukkit.broadcastMessage(plugin.messages().text("&6Lottery round #" + result.round()
|
||||
+ " won by &f" + result.winnerName()
|
||||
+ " &6for &f" + CoalMoney.format(result.pot())
|
||||
+ " &6from &f" + result.tickets() + " &6tickets."));
|
||||
return;
|
||||
}
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /coal lottery <status|buy [tickets]|draw>"));
|
||||
}
|
||||
|
||||
private void buyLottery(Player player, int tickets) throws SQLException {
|
||||
var result = plugin.lotteryService().buy(player, tickets);
|
||||
if (!result.success()) {
|
||||
player.sendMessage(plugin.messages().text("&c" + result.message()));
|
||||
return;
|
||||
}
|
||||
player.sendMessage(plugin.messages().text("&aBought &f" + result.tickets()
|
||||
+ " &alottery ticket(s) for &f" + CoalMoney.format(result.cost())
|
||||
+ " &ain round #" + result.round() + "."));
|
||||
}
|
||||
|
||||
private boolean canFitCoal(Player player, long amount) {
|
||||
if (amount > Integer.MAX_VALUE) {
|
||||
return false;
|
||||
|
||||
@@ -43,6 +43,14 @@ public final class NpcCommand implements CommandExecutor {
|
||||
stock(sender, args);
|
||||
} else if (args.length >= 1 && args[0].equalsIgnoreCase("funds")) {
|
||||
funds(sender, args);
|
||||
} else if (args.length >= 1 && args[0].equalsIgnoreCase("role")) {
|
||||
role(sender, args);
|
||||
} else if (args.length >= 1 && args[0].equalsIgnoreCase("home")) {
|
||||
home(sender, args);
|
||||
} else if (args.length >= 1 && args[0].equalsIgnoreCase("work")) {
|
||||
work(sender, args);
|
||||
} else if (args.length >= 1 && args[0].equalsIgnoreCase("needs")) {
|
||||
needs(sender, args);
|
||||
} else if (args.length >= 1 && args[0].equalsIgnoreCase("haggle")) {
|
||||
haggle(sender, args);
|
||||
} else {
|
||||
@@ -117,7 +125,9 @@ public final class NpcCommand implements CommandExecutor {
|
||||
}
|
||||
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()));
|
||||
String role = npc.role() == null || npc.role().isBlank() ? "auto" : npc.role();
|
||||
sender.sendMessage(plugin.messages().raw("&7#" + npc.id() + " &f" + npc.name() + " " + npc.npcType()
|
||||
+ " role " + role + " hunger " + npc.hunger() + "/20 citizens " + npc.citizensId()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +162,73 @@ public final class NpcCommand implements CommandExecutor {
|
||||
sender.sendMessage(plugin.messages().text("&aNPC funded."));
|
||||
}
|
||||
|
||||
private void role(CommandSender sender, String[] args) throws SQLException {
|
||||
if (args.length != 3) {
|
||||
sender.sendMessage(plugin.messages().text("&eUsage: /cgnpc role <id> <role>"));
|
||||
return;
|
||||
}
|
||||
long id = parseLong(args[1]);
|
||||
if (!plugin.npcService().setRole(id, args[2])) {
|
||||
sender.sendMessage(plugin.messages().text("&cNo NPC with that id."));
|
||||
return;
|
||||
}
|
||||
sender.sendMessage(plugin.messages().text("&aNPC role set to &f" + args[2] + "&a."));
|
||||
}
|
||||
|
||||
private void home(CommandSender sender, String[] args) throws SQLException {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage("Players only.");
|
||||
return;
|
||||
}
|
||||
if (args.length != 2) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /cgnpc home <id>"));
|
||||
return;
|
||||
}
|
||||
if (!plugin.npcService().setHome(parseLong(args[1]), player.getLocation())) {
|
||||
player.sendMessage(plugin.messages().text("&cNo NPC with that id."));
|
||||
return;
|
||||
}
|
||||
player.sendMessage(plugin.messages().text("&aNPC home set to your location."));
|
||||
}
|
||||
|
||||
private void work(CommandSender sender, String[] args) throws SQLException {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage("Players only.");
|
||||
return;
|
||||
}
|
||||
if (args.length != 2) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /cgnpc work <id>"));
|
||||
return;
|
||||
}
|
||||
if (!plugin.npcService().setWork(parseLong(args[1]), player.getLocation())) {
|
||||
player.sendMessage(plugin.messages().text("&cNo NPC with that id."));
|
||||
return;
|
||||
}
|
||||
player.sendMessage(plugin.messages().text("&aNPC work location set to your location."));
|
||||
}
|
||||
|
||||
private void needs(CommandSender sender, String[] args) throws SQLException {
|
||||
if (args.length != 2) {
|
||||
sender.sendMessage(plugin.messages().text("&eUsage: /cgnpc needs <id>"));
|
||||
return;
|
||||
}
|
||||
Optional<CoalNpc> npc = plugin.npcService().findNpc(parseLong(args[1]));
|
||||
if (npc.isEmpty()) {
|
||||
sender.sendMessage(plugin.messages().text("&cNo NPC with that id."));
|
||||
return;
|
||||
}
|
||||
CoalNpc coalNpc = npc.get();
|
||||
sender.sendMessage(plugin.messages().text("&e" + coalNpc.name() + " needs: &f"
|
||||
+ coalNpc.hunger() + "/20 hunger &7Role: &f"
|
||||
+ (coalNpc.role() == null || coalNpc.role().isBlank() ? "auto" : coalNpc.role())));
|
||||
sender.sendMessage(plugin.messages().text("&7Home: &f" + coalNpc.world() + " "
|
||||
+ Math.round(coalNpc.x()) + " " + Math.round(coalNpc.y()) + " " + Math.round(coalNpc.z())));
|
||||
if (coalNpc.workWorld() != null) {
|
||||
sender.sendMessage(plugin.messages().text("&7Work: &f" + coalNpc.workWorld() + " "
|
||||
+ Math.round(coalNpc.workX()) + " " + Math.round(coalNpc.workY()) + " " + Math.round(coalNpc.workZ())));
|
||||
}
|
||||
}
|
||||
|
||||
private void haggle(CommandSender sender, String[] args) throws SQLException {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage("Players only.");
|
||||
@@ -183,13 +260,15 @@ public final class NpcCommand implements CommandExecutor {
|
||||
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;
|
||||
long tradePrice = acceptedTradePrice(quote, offer, finalPrice);
|
||||
boolean aiAccepted = ai.map(OpenRouterService.HaggleAiResult::accept).orElse(false) && offer == finalPrice;
|
||||
boolean accepted = quote.accepted() || aiAccepted;
|
||||
String line = ai.map(OpenRouterService.HaggleAiResult::message)
|
||||
.orElse(accepted ? "Deal." : "I can do " + CoalMoney.format(finalPrice) + ".");
|
||||
.orElse(accepted ? "Deal at " + CoalMoney.format(tradePrice) + "." : "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."));
|
||||
if (plugin.npcService().completeTrade(player, npc.get(), quote, tradePrice)) {
|
||||
player.sendMessage(plugin.messages().text("&aTrade complete for &f" + CoalMoney.format(tradePrice) + "&a."));
|
||||
} else {
|
||||
player.sendMessage(plugin.messages().text("&cTrade failed. Check balance, stock, and inventory."));
|
||||
}
|
||||
@@ -202,8 +281,18 @@ public final class NpcCommand implements CommandExecutor {
|
||||
}));
|
||||
}
|
||||
|
||||
private long acceptedTradePrice(NpcService.HaggleQuote quote, long offer, long counter) {
|
||||
if (!quote.accepted()) {
|
||||
return counter;
|
||||
}
|
||||
if (quote.mode().equals("buy")) {
|
||||
return Math.min(offer, quote.ceiling());
|
||||
}
|
||||
return Math.min(offer, quote.ceiling());
|
||||
}
|
||||
|
||||
private void usage(CommandSender sender) {
|
||||
sender.sendMessage(plugin.messages().text("&eUsage: /cgnpc <zone|create|remove|list|stock|funds|haggle>"));
|
||||
sender.sendMessage(plugin.messages().text("&eUsage: /cgnpc <zone|create|remove|list|stock|funds|role|home|work|needs|haggle>"));
|
||||
}
|
||||
|
||||
private long parseLong(String raw) {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.librewiki.coalgov.command;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public final class PreserveCommand implements CommandExecutor {
|
||||
private final CoalGovPlugin plugin;
|
||||
|
||||
public PreserveCommand(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (!(sender instanceof Player player)) {
|
||||
sender.sendMessage("Only players can use /preserve.");
|
||||
return true;
|
||||
}
|
||||
if (args.length > 0 && args[0].equalsIgnoreCase("return")) {
|
||||
World mainWorld = plugin.getServer().getWorld("world");
|
||||
if (mainWorld == null) {
|
||||
player.sendMessage(plugin.messages().text("&cThe main world is not loaded."));
|
||||
return true;
|
||||
}
|
||||
player.teleport(mainWorld.getSpawnLocation());
|
||||
player.sendMessage(plugin.messages().text("&aReturned to CoalGov Basin."));
|
||||
return true;
|
||||
}
|
||||
Location location = plugin.preserveWorldService().teleportLocation();
|
||||
if (location == null) {
|
||||
player.sendMessage(plugin.messages().text("&cThe wildlife preserve is not available yet."));
|
||||
return true;
|
||||
}
|
||||
player.teleport(location);
|
||||
player.sendMessage(plugin.messages().text("&aEntering the wildlife preserve. Building and mining are not allowed here."));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.librewiki.coalgov.listener;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.LivingEntity;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.EntityDeathEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public final class NameTagRecoveryListener implements Listener {
|
||||
private final CoalGovPlugin plugin;
|
||||
|
||||
public NameTagRecoveryListener(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onEntityDeath(EntityDeathEvent event) {
|
||||
if (!plugin.getConfig().getBoolean("name_tags.recover_named_entity_tags", true)) {
|
||||
return;
|
||||
}
|
||||
LivingEntity entity = event.getEntity();
|
||||
if (entity.customName() == null || !entity.isCustomNameVisible()) {
|
||||
return;
|
||||
}
|
||||
ItemStack tag = new ItemStack(Material.NAME_TAG);
|
||||
ItemMeta meta = tag.getItemMeta();
|
||||
meta.setDisplayName(plugin.messages().raw("&fRecovered Name Tag"));
|
||||
meta.setLore(List.of(plugin.messages().raw("&7Recovered from a named entity.")));
|
||||
tag.setItemMeta(meta);
|
||||
event.getDrops().add(tag);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package com.librewiki.coalgov.listener;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockBreakEvent;
|
||||
import org.bukkit.event.block.BlockBurnEvent;
|
||||
import org.bukkit.event.block.BlockExplodeEvent;
|
||||
import org.bukkit.event.block.BlockFadeEvent;
|
||||
import org.bukkit.event.block.BlockIgniteEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.block.EntityBlockFormEvent;
|
||||
import org.bukkit.event.entity.EntityChangeBlockEvent;
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||
import org.bukkit.event.entity.EntityExplodeEvent;
|
||||
import org.bukkit.event.player.PlayerBucketEmptyEvent;
|
||||
import org.bukkit.event.player.PlayerBucketFillEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
|
||||
public final class PreserveProtectionListener implements Listener {
|
||||
private final CoalGovPlugin plugin;
|
||||
|
||||
public PreserveProtectionListener(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onBlockBreak(BlockBreakEvent event) {
|
||||
if (denyPlayer(event.getPlayer(), event.getBlock().getLocation())) {
|
||||
event.setCancelled(true);
|
||||
event.getPlayer().sendMessage(plugin.messages().text("&cThe wildlife preserve is protected. Use /preserve return to leave."));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onBlockPlace(BlockPlaceEvent event) {
|
||||
if (denyPlayer(event.getPlayer(), event.getBlockPlaced().getLocation())) {
|
||||
event.setCancelled(true);
|
||||
event.getPlayer().sendMessage(plugin.messages().text("&cBuilding is not allowed in the wildlife preserve."));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onBucketEmpty(PlayerBucketEmptyEvent event) {
|
||||
if (denyPlayer(event.getPlayer(), event.getBlockClicked().getLocation())) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onBucketFill(PlayerBucketFillEvent event) {
|
||||
if (denyPlayer(event.getPlayer(), event.getBlockClicked().getLocation())) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onInteract(PlayerInteractEvent event) {
|
||||
if (event.getClickedBlock() != null && denyPlayer(event.getPlayer(), event.getClickedBlock().getLocation())) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onEntityDamage(EntityDamageByEntityEvent event) {
|
||||
Player player = attackingPlayer(event.getDamager());
|
||||
if (player != null && denyPlayer(player, event.getEntity().getLocation())) {
|
||||
event.setCancelled(true);
|
||||
player.sendMessage(plugin.messages().text("&cWildlife is protected in the preserve."));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onEntityChangeBlock(EntityChangeBlockEvent event) {
|
||||
if (isPreserve(event.getBlock().getLocation())) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onEntityBlockForm(EntityBlockFormEvent event) {
|
||||
if (isPreserve(event.getBlock().getLocation())) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onBlockFade(BlockFadeEvent event) {
|
||||
if (isPreserve(event.getBlock().getLocation())) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onBlockBurn(BlockBurnEvent event) {
|
||||
if (isPreserve(event.getBlock().getLocation())) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onBlockIgnite(BlockIgniteEvent event) {
|
||||
if (isPreserve(event.getBlock().getLocation())) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onEntityExplode(EntityExplodeEvent event) {
|
||||
if (isPreserve(event.getLocation())) {
|
||||
event.blockList().clear();
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onBlockExplode(BlockExplodeEvent event) {
|
||||
if (isPreserve(event.getBlock().getLocation())) {
|
||||
event.blockList().clear();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean denyPlayer(Player player, Location location) {
|
||||
return isPreserve(location) && !plugin.preserveWorldService().canBypass(player);
|
||||
}
|
||||
|
||||
private boolean isPreserve(Location location) {
|
||||
return plugin.preserveWorldService().isPreserveWorld(location);
|
||||
}
|
||||
|
||||
private Player attackingPlayer(org.bukkit.entity.Entity damager) {
|
||||
if (damager instanceof Player player) {
|
||||
return player;
|
||||
}
|
||||
if (damager instanceof org.bukkit.entity.Projectile projectile && projectile.getShooter() instanceof Player player) {
|
||||
return player;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,16 @@ package com.librewiki.coalgov.listener;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Furnace;
|
||||
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.FurnaceSmeltEvent;
|
||||
import org.bukkit.event.inventory.FurnaceStartSmeltEvent;
|
||||
import org.bukkit.inventory.FurnaceInventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
@@ -71,4 +75,28 @@ public final class SuperFurnaceListener implements Listener {
|
||||
plugin.getLogger().warning("Super furnace burn check failed: " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onFurnaceSmelt(FurnaceSmeltEvent event) {
|
||||
try {
|
||||
if (!plugin.superFurnaceService().syntheticDiamondsEnabled()
|
||||
|| !plugin.superFurnaceService().isSuperFurnace(event.getBlock())
|
||||
|| event.getSource().getType() != Material.COAL) {
|
||||
return;
|
||||
}
|
||||
int coalInput = plugin.superFurnaceService().syntheticDiamondCoalInput();
|
||||
if (event.getSource().getAmount() < coalInput || !(event.getBlock().getState() instanceof Furnace furnace)) {
|
||||
return;
|
||||
}
|
||||
FurnaceInventory inventory = furnace.getInventory();
|
||||
ItemStack fuel = inventory.getFuel();
|
||||
if (fuel == null || fuel.getType() != Material.COAL) {
|
||||
return;
|
||||
}
|
||||
event.setResult(new ItemStack(Material.DIAMOND));
|
||||
event.getSource().setAmount(event.getSource().getAmount() - coalInput + 1);
|
||||
} catch (SQLException exception) {
|
||||
plugin.getLogger().warning("Synthetic diamond smelt check failed: " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ public enum ClaimPermission {
|
||||
}
|
||||
|
||||
public static List<ClaimPermission> grantSet(String raw) {
|
||||
if (raw.equalsIgnoreCase("all")) {
|
||||
if (raw.equalsIgnoreCase("all") || raw.equalsIgnoreCase("any")) {
|
||||
return List.of(values());
|
||||
}
|
||||
return parse(raw).map(List::of).orElse(List.of());
|
||||
|
||||
@@ -12,8 +12,30 @@ public record CoalNpc(
|
||||
String world,
|
||||
double x,
|
||||
double y,
|
||||
double z
|
||||
double z,
|
||||
String role,
|
||||
String workWorld,
|
||||
Double workX,
|
||||
Double workY,
|
||||
Double workZ,
|
||||
int hunger,
|
||||
long lastFoodAt
|
||||
) {
|
||||
public CoalNpc(
|
||||
long id,
|
||||
int citizensId,
|
||||
UUID accountUuid,
|
||||
String npcType,
|
||||
String name,
|
||||
long zoneId,
|
||||
String world,
|
||||
double x,
|
||||
double y,
|
||||
double z
|
||||
) {
|
||||
this(id, citizensId, accountUuid, npcType, name, zoneId, world, x, y, z, "", null, null, null, null, 20, 0L);
|
||||
}
|
||||
|
||||
public boolean trader() {
|
||||
return npcType.equalsIgnoreCase("TRADER");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
package com.librewiki.coalgov.service;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.util.CoalMoney;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class AuctionService {
|
||||
private final CoalGovPlugin plugin;
|
||||
private Auction active;
|
||||
private int taskId = -1;
|
||||
|
||||
public AuctionService(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public StartResult start(Player seller, Material material, int amount, long minimumBid, int seconds) throws SQLException {
|
||||
if (active != null) {
|
||||
return StartResult.fail("Another auction is already running.");
|
||||
}
|
||||
if (amount <= 0 || minimumBid <= 0L || seconds < 15 || seconds > 600) {
|
||||
return StartResult.fail("Use a positive amount and bid. Duration must be 15-600 seconds.");
|
||||
}
|
||||
if (!removeItems(seller, material, amount)) {
|
||||
return StartResult.fail("You do not have enough " + readable(material) + ".");
|
||||
}
|
||||
active = new Auction(seller.getUniqueId(), seller.getName(), new ItemStack(material, amount),
|
||||
minimumBid, System.currentTimeMillis() + seconds * 1000L, null, "", 0L);
|
||||
taskId = Bukkit.getScheduler().runTaskLater(plugin, this::finish, seconds * 20L).getTaskId();
|
||||
Bukkit.broadcastMessage(plugin.messages().text("&6Auction started: &f" + amount + " " + readable(material)
|
||||
+ " &6by &f" + seller.getName() + " &6minimum &f" + CoalMoney.format(minimumBid)
|
||||
+ "&6. Bid with &f/auction bid <amount>&6."));
|
||||
return StartResult.ok();
|
||||
}
|
||||
|
||||
public BidResult bid(Player bidder, long amount) throws SQLException {
|
||||
if (active == null) {
|
||||
return BidResult.fail("No auction is running.");
|
||||
}
|
||||
if (active.sellerUuid().equals(bidder.getUniqueId())) {
|
||||
return BidResult.fail("You cannot bid on your own auction.");
|
||||
}
|
||||
long minimum = active.highBid() > 0L
|
||||
? active.highBid() + bidIncrement()
|
||||
: active.minimumBid();
|
||||
if (amount < minimum) {
|
||||
return BidResult.fail("Bid must be at least " + CoalMoney.format(minimum) + ".");
|
||||
}
|
||||
plugin.economyService().ensurePlayer(bidder);
|
||||
if (!plugin.economyService().charge(bidder.getUniqueId(), amount, "auction_bid_escrow")) {
|
||||
return BidResult.fail("Balance too low.");
|
||||
}
|
||||
if (active.highBidderUuid() != null) {
|
||||
plugin.economyService().credit(active.highBidderUuid(), active.highBid(), "auction_bid_refund");
|
||||
}
|
||||
active = new Auction(active.sellerUuid(), active.sellerName(), active.item(), active.minimumBid(),
|
||||
active.endsAt(), bidder.getUniqueId(), bidder.getName(), amount);
|
||||
Bukkit.broadcastMessage(plugin.messages().text("&6Auction bid: &f" + bidder.getName() + " &6bid &f"
|
||||
+ CoalMoney.format(amount) + " &6on &f" + active.item().getAmount() + " " + readable(active.item().getType()) + "&6."));
|
||||
return BidResult.ok();
|
||||
}
|
||||
|
||||
public Status status() {
|
||||
if (active == null) {
|
||||
return null;
|
||||
}
|
||||
long remainingSeconds = Math.max(0L, (active.endsAt() - System.currentTimeMillis()) / 1000L);
|
||||
return new Status(active.sellerName(), active.item().getType(), active.item().getAmount(),
|
||||
active.minimumBid(), active.highBidderName(), active.highBid(), remainingSeconds);
|
||||
}
|
||||
|
||||
public boolean cancel(Player actor) throws SQLException {
|
||||
if (active == null || (!active.sellerUuid().equals(actor.getUniqueId()) && !actor.hasPermission("coalgov.admin"))) {
|
||||
return false;
|
||||
}
|
||||
Auction cancelled = active;
|
||||
clearTask();
|
||||
active = null;
|
||||
if (cancelled.highBidderUuid() != null) {
|
||||
plugin.economyService().credit(cancelled.highBidderUuid(), cancelled.highBid(), "auction_cancel_refund");
|
||||
}
|
||||
giveOrDrop(cancelled.sellerUuid(), cancelled.item());
|
||||
Bukkit.broadcastMessage(plugin.messages().text("&cAuction cancelled by " + actor.getName() + "."));
|
||||
return true;
|
||||
}
|
||||
|
||||
public void cancelAll() {
|
||||
if (active == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Auction cancelled = active;
|
||||
active = null;
|
||||
clearTask();
|
||||
if (cancelled.highBidderUuid() != null) {
|
||||
plugin.economyService().credit(cancelled.highBidderUuid(), cancelled.highBid(), "auction_shutdown_refund");
|
||||
}
|
||||
giveOrDrop(cancelled.sellerUuid(), cancelled.item());
|
||||
} catch (SQLException exception) {
|
||||
plugin.getLogger().warning("Auction shutdown refund failed: " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void finish() {
|
||||
if (active == null) {
|
||||
return;
|
||||
}
|
||||
Auction ended = active;
|
||||
active = null;
|
||||
taskId = -1;
|
||||
try {
|
||||
if (ended.highBidderUuid() == null) {
|
||||
giveOrDrop(ended.sellerUuid(), ended.item());
|
||||
Bukkit.broadcastMessage(plugin.messages().text("&7Auction ended with no bids: &f"
|
||||
+ ended.item().getAmount() + " " + readable(ended.item().getType()) + "&7."));
|
||||
return;
|
||||
}
|
||||
plugin.economyService().credit(ended.sellerUuid(), ended.highBid(), "auction_sale");
|
||||
giveOrDrop(ended.highBidderUuid(), ended.item());
|
||||
Bukkit.broadcastMessage(plugin.messages().text("&6Auction won by &f" + ended.highBidderName()
|
||||
+ " &6for &f" + CoalMoney.format(ended.highBid()) + "&6."));
|
||||
} catch (SQLException exception) {
|
||||
plugin.getLogger().warning("Auction finish failed: " + exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private long bidIncrement() {
|
||||
return CoalMoney.fromCoalConfig(plugin.getConfig().getDouble("auction.min_increment", 0.25D));
|
||||
}
|
||||
|
||||
private void clearTask() {
|
||||
if (taskId >= 0) {
|
||||
Bukkit.getScheduler().cancelTask(taskId);
|
||||
taskId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean removeItems(Player player, Material material, int amount) {
|
||||
int found = 0;
|
||||
for (ItemStack item : player.getInventory().getStorageContents()) {
|
||||
if (item != null && item.getType() == material) {
|
||||
found += item.getAmount();
|
||||
}
|
||||
}
|
||||
if (found < amount) {
|
||||
return false;
|
||||
}
|
||||
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 true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void giveOrDrop(UUID uuid, ItemStack item) {
|
||||
Player player = Bukkit.getPlayer(uuid);
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
player.getInventory().addItem(item.clone()).values()
|
||||
.forEach(leftover -> player.getWorld().dropItemNaturally(player.getLocation(), leftover));
|
||||
}
|
||||
|
||||
private String readable(Material material) {
|
||||
return material.name().toLowerCase().replace('_', ' ');
|
||||
}
|
||||
|
||||
private record Auction(UUID sellerUuid, String sellerName, ItemStack item, long minimumBid,
|
||||
long endsAt, UUID highBidderUuid, String highBidderName, long highBid) {
|
||||
}
|
||||
|
||||
public record StartResult(boolean success, String message) {
|
||||
public static StartResult fail(String message) {
|
||||
return new StartResult(false, message);
|
||||
}
|
||||
|
||||
public static StartResult ok() {
|
||||
return new StartResult(true, "");
|
||||
}
|
||||
}
|
||||
|
||||
public record BidResult(boolean success, String message) {
|
||||
public static BidResult fail(String message) {
|
||||
return new BidResult(false, message);
|
||||
}
|
||||
|
||||
public static BidResult ok() {
|
||||
return new BidResult(true, "");
|
||||
}
|
||||
}
|
||||
|
||||
public record Status(String sellerName, Material material, int amount, long minimumBid,
|
||||
String highBidderName, long highBid, long remainingSeconds) {
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,9 @@ public final class ClaimMarkerService {
|
||||
}
|
||||
|
||||
private void placeMarker(World world, int x, int z) {
|
||||
if (!world.isChunkLoaded(x >> 4, z >> 4)) {
|
||||
return;
|
||||
}
|
||||
int y = world.getHighestBlockYAt(x, z);
|
||||
Block highest = world.getBlockAt(x, y, z);
|
||||
if (highest.getType() == MARKER_MATERIAL) {
|
||||
@@ -86,6 +89,9 @@ public final class ClaimMarkerService {
|
||||
}
|
||||
|
||||
private void removeMarkerColumn(World world, int x, int z) {
|
||||
if (!world.isChunkLoaded(x >> 4, z >> 4)) {
|
||||
return;
|
||||
}
|
||||
for (int y = world.getMinHeight(); y < world.getMaxHeight(); y++) {
|
||||
Block block = world.getBlockAt(x, y, z);
|
||||
if (block.getType() == MARKER_MATERIAL) {
|
||||
|
||||
@@ -10,12 +10,15 @@ import com.librewiki.coalgov.util.Cuboid2D;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class ClaimService {
|
||||
private static final UUID GOVERNMENT_OWNER_UUID = UUID.nameUUIDFromBytes("CoalGov Ministry".getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
private final ClaimRepository repository;
|
||||
private final LandService landService;
|
||||
private final EconomyService economyService;
|
||||
@@ -106,6 +109,85 @@ public final class ClaimService {
|
||||
return repository.findOwnedById(owner, id);
|
||||
}
|
||||
|
||||
public Optional<Claim> findById(long id) throws SQLException {
|
||||
return repository.findById(id);
|
||||
}
|
||||
|
||||
public UUID governmentOwnerUuid() {
|
||||
return GOVERNMENT_OWNER_UUID;
|
||||
}
|
||||
|
||||
public boolean isGovernmentClaim(Claim claim) {
|
||||
return claim.ownerUuid().equals(GOVERNMENT_OWNER_UUID);
|
||||
}
|
||||
|
||||
public BuyResult createGovernmentClaim(UUID adminUuid, 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
|
||||
);
|
||||
return createGovernmentClaim(adminUuid, area, type, List.of());
|
||||
}
|
||||
|
||||
public BuyResult createGovernmentPolygon(UUID adminUuid, 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.");
|
||||
}
|
||||
return createGovernmentClaim(adminUuid, area, type, vertices);
|
||||
}
|
||||
|
||||
private BuyResult createGovernmentClaim(UUID adminUuid, Cuboid2D area, String type, List<ClaimPoint> vertices) throws SQLException {
|
||||
if (repository.overlaps(area)) {
|
||||
return BuyResult.fail("That claim overlaps an existing claim.");
|
||||
}
|
||||
long id = repository.create(GOVERNMENT_OWNER_UUID, area, type.toUpperCase(), null, 0L, vertices);
|
||||
for (ClaimPermission permission : ClaimPermission.values()) {
|
||||
repository.grantPermission(id, adminUuid, permission);
|
||||
}
|
||||
return BuyResult.success(id, 0L);
|
||||
}
|
||||
|
||||
public TransferResult sellGovernmentClaim(long id, UUID newOwner, long price) throws SQLException {
|
||||
if (price < 0L) {
|
||||
return TransferResult.fail("Price cannot be negative.");
|
||||
}
|
||||
Optional<Claim> claim = repository.findById(id);
|
||||
if (claim.isEmpty() || !isGovernmentClaim(claim.get())) {
|
||||
return TransferResult.fail("No government-owned claim with that id.");
|
||||
}
|
||||
if (price > 0L && !economyService.charge(newOwner, price, "government_land_purchase")) {
|
||||
return TransferResult.fail("Buyer needs " + CoalMoney.format(price) + " for that claim.");
|
||||
}
|
||||
if (!repository.transferGovernmentOwned(GOVERNMENT_OWNER_UUID, id, newOwner, price)) {
|
||||
if (price > 0L) {
|
||||
economyService.credit(newOwner, price, "government_land_purchase_refund");
|
||||
}
|
||||
return TransferResult.fail("Could not transfer that government claim.");
|
||||
}
|
||||
return TransferResult.success(claim.get());
|
||||
}
|
||||
|
||||
public SellResult sell(UUID owner, long id, long refund) throws SQLException {
|
||||
Optional<Claim> claim = repository.findOwnedById(owner, id);
|
||||
if (claim.isEmpty()) {
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
package com.librewiki.coalgov.service;
|
||||
|
||||
import com.librewiki.coalgov.storage.LotteryRepository;
|
||||
import com.librewiki.coalgov.storage.LotteryRepository.TicketHolder;
|
||||
import com.librewiki.coalgov.util.CoalMoney;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public final class LotteryService {
|
||||
private final LotteryRepository repository;
|
||||
private final EconomyService economyService;
|
||||
private final GovernmentService governmentService;
|
||||
private final Random random = new Random();
|
||||
private FileConfiguration config;
|
||||
|
||||
public LotteryService(LotteryRepository repository, EconomyService economyService, GovernmentService governmentService, FileConfiguration config) {
|
||||
this.repository = repository;
|
||||
this.economyService = economyService;
|
||||
this.governmentService = governmentService;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public void setConfig(FileConfiguration config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public BuyResult buy(Player player, int tickets) throws SQLException {
|
||||
if (tickets <= 0 || tickets > maxTicketsPerBuy()) {
|
||||
return BuyResult.fail("Ticket count must be between 1 and " + maxTicketsPerBuy() + ".");
|
||||
}
|
||||
long cost = ticketCost() * tickets;
|
||||
if (!economyService.charge(player.getUniqueId(), cost, "lottery_ticket")) {
|
||||
return BuyResult.fail("You need " + CoalMoney.format(cost) + ".");
|
||||
}
|
||||
int round = repository.activeRound();
|
||||
repository.addTickets(round, player.getUniqueId(), player.getName(), tickets);
|
||||
return BuyResult.ok(round, tickets, cost);
|
||||
}
|
||||
|
||||
public Status status() throws SQLException {
|
||||
int round = repository.activeRound();
|
||||
List<TicketHolder> holders = repository.tickets(round);
|
||||
int tickets = holders.stream().mapToInt(TicketHolder::tickets).sum();
|
||||
return new Status(round, holders.size(), tickets, tickets * ticketCost());
|
||||
}
|
||||
|
||||
public DrawResult draw() throws SQLException {
|
||||
int round = repository.activeRound();
|
||||
List<TicketHolder> holders = repository.tickets(round);
|
||||
int totalTickets = holders.stream().mapToInt(TicketHolder::tickets).sum();
|
||||
if (totalTickets <= 0) {
|
||||
return DrawResult.fail("No tickets have been sold for this round.");
|
||||
}
|
||||
int winningTicket = random.nextInt(totalTickets);
|
||||
TicketHolder winner = null;
|
||||
int cursor = 0;
|
||||
for (TicketHolder holder : holders) {
|
||||
cursor += holder.tickets();
|
||||
if (winningTicket < cursor) {
|
||||
winner = holder;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (winner == null) {
|
||||
return DrawResult.fail("Lottery draw failed.");
|
||||
}
|
||||
long gross = totalTickets * ticketCost();
|
||||
long pot = Math.max(0L, Math.round(gross * payoutPercent() / 100.0D));
|
||||
if (gross > 0L) {
|
||||
pot = Math.min(pot, Math.max(0L, gross - 1L));
|
||||
}
|
||||
economyService.credit(winner.uuid(), pot, "lottery_win");
|
||||
governmentService.collectTax(Math.max(0L, gross - pot));
|
||||
repository.markDrawn(round, winner.uuid(), pot);
|
||||
repository.activeRound();
|
||||
return DrawResult.ok(round, winner.name(), pot, totalTickets);
|
||||
}
|
||||
|
||||
private long ticketCost() {
|
||||
return CoalMoney.fromCoalConfig(config.getDouble("lottery.ticket_cost", 1.0D));
|
||||
}
|
||||
|
||||
private int maxTicketsPerBuy() {
|
||||
return Math.max(1, config.getInt("lottery.max_tickets_per_buy", 64));
|
||||
}
|
||||
|
||||
private double payoutPercent() {
|
||||
return Math.max(0.0D, Math.min(95.0D, config.getDouble("lottery.payout_percent", 90.0D)));
|
||||
}
|
||||
|
||||
public record BuyResult(boolean success, String message, int round, int tickets, long cost) {
|
||||
public static BuyResult fail(String message) {
|
||||
return new BuyResult(false, message, 0, 0, 0L);
|
||||
}
|
||||
|
||||
public static BuyResult ok(int round, int tickets, long cost) {
|
||||
return new BuyResult(true, "", round, tickets, cost);
|
||||
}
|
||||
}
|
||||
|
||||
public record Status(int round, int players, int tickets, long pot) {
|
||||
}
|
||||
|
||||
public record DrawResult(boolean success, String message, int round, String winnerName, long pot, int tickets) {
|
||||
public static DrawResult fail(String message) {
|
||||
return new DrawResult(false, message, 0, "", 0L, 0);
|
||||
}
|
||||
|
||||
public static DrawResult ok(int round, String winnerName, long pot, int tickets) {
|
||||
return new DrawResult(true, "", round, winnerName, pot, tickets);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.librewiki.coalgov.service;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.storage.MarketRepository;
|
||||
import com.librewiki.coalgov.storage.MarketRepository.BuybackLot;
|
||||
import com.librewiki.coalgov.storage.MarketRepository.Flow;
|
||||
import com.librewiki.coalgov.util.CoalMoney;
|
||||
import org.bukkit.Bukkit;
|
||||
@@ -58,7 +59,11 @@ public final class MarketService {
|
||||
Map<Material, Double> prices = basePrices();
|
||||
Map<Material, Long> simulatedStock = new LinkedHashMap<>();
|
||||
Map<Material, Long> sold = new LinkedHashMap<>();
|
||||
Map<Material, Long> buybackSold = new LinkedHashMap<>();
|
||||
Map<Long, Long> buybackLotUse = new LinkedHashMap<>();
|
||||
long grossCredit = 0L;
|
||||
long buybackCredit = 0L;
|
||||
long now = System.currentTimeMillis();
|
||||
for (ItemStack item : player.getInventory().getStorageContents()) {
|
||||
if (item == null || item.getType().isAir()) {
|
||||
continue;
|
||||
@@ -73,17 +78,40 @@ public final class MarketService {
|
||||
: repository.stock(material, initialStock(material));
|
||||
Flow flow = repository.flow(material, flowHalfLifeMillis(material));
|
||||
long alreadySold = sold.getOrDefault(material, 0L);
|
||||
grossCredit += sellCredit(material, basePrice, stock, flow, alreadySold, item.getAmount());
|
||||
int regularAmount = item.getAmount();
|
||||
long protectedAmount = 0L;
|
||||
if (plugin.getConfig().getBoolean("market.buyback_protection.enabled", true)) {
|
||||
for (BuybackLot lot : repository.buybackLots(player.getUniqueId(), material, now)) {
|
||||
if (regularAmount <= 0) {
|
||||
break;
|
||||
}
|
||||
long available = lot.amount() - buybackLotUse.getOrDefault(lot.id(), 0L);
|
||||
if (available <= 0L) {
|
||||
continue;
|
||||
}
|
||||
long used = Math.min(regularAmount, available);
|
||||
buybackLotUse.merge(lot.id(), used, Long::sum);
|
||||
protectedAmount += used;
|
||||
buybackCredit += used * lot.unitPrice();
|
||||
regularAmount -= (int) used;
|
||||
}
|
||||
}
|
||||
if (regularAmount > 0) {
|
||||
grossCredit += sellCredit(material, basePrice, stock + protectedAmount, flow, alreadySold, regularAmount);
|
||||
}
|
||||
simulatedStock.put(material, stock + item.getAmount());
|
||||
sold.merge(material, (long) item.getAmount(), Long::sum);
|
||||
if (protectedAmount > 0L) {
|
||||
buybackSold.merge(material, protectedAmount, Long::sum);
|
||||
}
|
||||
}
|
||||
if (grossCredit <= 0L) {
|
||||
if (grossCredit <= 0L && buybackCredit <= 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);
|
||||
if (!plugin.governmentService().spendMarketTransaction(grossCredit)) {
|
||||
long credit = Math.max(0L, grossCredit - tax) + buybackCredit;
|
||||
if (!plugin.governmentService().spendMarketTransaction(grossCredit + buybackCredit)) {
|
||||
player.sendMessage(plugin.messages().text("&cThe Ministry treasury cannot fund that market purchase right now."));
|
||||
return;
|
||||
}
|
||||
@@ -98,7 +126,12 @@ public final class MarketService {
|
||||
repository.addStock(entry.getKey(), entry.getValue());
|
||||
repository.recordSell(entry.getKey(), entry.getValue(), flowHalfLifeMillis(entry.getKey()));
|
||||
}
|
||||
if (tax > 0L) {
|
||||
for (var entry : buybackSold.entrySet()) {
|
||||
repository.consumeBuybacks(player.getUniqueId(), entry.getKey(), entry.getValue());
|
||||
}
|
||||
if (buybackCredit > 0L) {
|
||||
player.sendMessage(plugin.messages().text("&aSold resources for &f" + CoalMoney.format(credit) + " &aincluding &f" + CoalMoney.format(buybackCredit) + " &asame-day buyback protection."));
|
||||
} else 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."));
|
||||
@@ -146,6 +179,7 @@ public final class MarketService {
|
||||
}
|
||||
plugin.governmentService().collectMarketRevenue(total);
|
||||
repository.recordBuy(material, amount, flowHalfLifeMillis(material));
|
||||
recordBuyback(player, material, amount, total);
|
||||
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 {
|
||||
@@ -225,6 +259,15 @@ public final class MarketService {
|
||||
return Math.max(1L, CoalMoney.fromCoal(quote.buyUnitPrice() * amount));
|
||||
}
|
||||
|
||||
private void recordBuyback(Player player, Material material, int amount, long total) throws SQLException {
|
||||
if (!plugin.getConfig().getBoolean("market.buyback_protection.enabled", true) || amount <= 0) {
|
||||
return;
|
||||
}
|
||||
long hours = Math.max(1L, plugin.getConfig().getLong("market.buyback_protection.duration_hours", 24L));
|
||||
long unitPrice = Math.max(1L, (long) Math.ceil(total / (double) amount));
|
||||
repository.recordBuyback(player.getUniqueId(), material, amount, unitPrice, System.currentTimeMillis() + hours * 3_600_000L);
|
||||
}
|
||||
|
||||
private double coalEquivalentFloor(Material material) {
|
||||
if (material == Material.COAL || material == Material.CHARCOAL) {
|
||||
return 1.0D;
|
||||
@@ -374,7 +417,7 @@ public final class MarketService {
|
||||
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.BAKED_POTATO, 0.30D);
|
||||
prices.put(Material.BEETROOT, 0.20D);
|
||||
prices.put(Material.MELON_SLICE, 0.15D);
|
||||
prices.put(Material.SWEET_BERRIES, 0.20D);
|
||||
|
||||
@@ -94,6 +94,18 @@ public final class NpcService {
|
||||
repository.addInventory(npcId, material, amount);
|
||||
}
|
||||
|
||||
public boolean setRole(long npcId, String role) throws SQLException {
|
||||
return repository.setRole(npcId, role);
|
||||
}
|
||||
|
||||
public boolean setHome(long npcId, Location location) throws SQLException {
|
||||
return repository.setHome(npcId, location.getWorld().getName(), location.getX(), location.getY(), location.getZ());
|
||||
}
|
||||
|
||||
public boolean setWork(long npcId, Location location) throws SQLException {
|
||||
return repository.setWork(npcId, location.getWorld().getName(), location.getX(), location.getY(), location.getZ());
|
||||
}
|
||||
|
||||
public long balance(CoalNpc npc) throws SQLException {
|
||||
return plugin.economyService().balance(npc.accountUuid());
|
||||
}
|
||||
@@ -163,8 +175,22 @@ public final class NpcService {
|
||||
npc.spawn(new Location(world, coalNpc.x(), coalNpc.y(), coalNpc.z()));
|
||||
}
|
||||
}
|
||||
if (resting(coalNpc)) {
|
||||
returnHome(coalNpc, npc);
|
||||
continue;
|
||||
}
|
||||
if (!maintainNeeds(coalNpc, npcs)) {
|
||||
returnHome(coalNpc, npc);
|
||||
continue;
|
||||
}
|
||||
Location work = workLocation(coalNpc);
|
||||
if (work != null && npc.isSpawned()
|
||||
&& npc.getEntity().getLocation().distanceSquared(work) > workArrivalDistanceSquared()) {
|
||||
npc.getNavigator().setTarget(work);
|
||||
continue;
|
||||
}
|
||||
if (npc.isSpawned() && !npc.getNavigator().isNavigating()) {
|
||||
Location target = randomPoint(zone.get());
|
||||
Location target = randomWorkPoint(coalNpc, zone.get());
|
||||
if (target != null) {
|
||||
npc.getNavigator().setTarget(target);
|
||||
}
|
||||
@@ -190,6 +216,13 @@ public final class NpcService {
|
||||
private WorkerRecipe recipeFor(CoalNpc npc) {
|
||||
ConfigurationSection roles = plugin.getConfig().getConfigurationSection("npc.worker.roles");
|
||||
if (roles != null) {
|
||||
String explicitRole = npc.role() == null ? "" : npc.role();
|
||||
if (!explicitRole.isBlank()) {
|
||||
WorkerRecipe recipe = recipe(explicitRole, roles.getConfigurationSection(explicitRole));
|
||||
if (recipe != null) {
|
||||
return recipe;
|
||||
}
|
||||
}
|
||||
String npcName = npc.name().toLowerCase();
|
||||
for (String role : roles.getKeys(false)) {
|
||||
if (npcName.contains(role.toLowerCase())) {
|
||||
@@ -208,6 +241,152 @@ public final class NpcService {
|
||||
return material == null || amount <= 0 ? null : new WorkerRecipe("default", "PRODUCE", material, amount, Map.of(), List.of(), true);
|
||||
}
|
||||
|
||||
private boolean resting(CoalNpc coalNpc) {
|
||||
if (!plugin.getConfig().getBoolean("npc.schedule.enabled", true)) {
|
||||
return false;
|
||||
}
|
||||
World world = Bukkit.getWorld(coalNpc.world());
|
||||
if (world == null) {
|
||||
return false;
|
||||
}
|
||||
long dayTime = world.getTime() % 24000L;
|
||||
long start = Math.floorMod(plugin.getConfig().getLong("npc.schedule.rest_start_tick", 13000L), 24000L);
|
||||
long end = Math.floorMod(plugin.getConfig().getLong("npc.schedule.rest_end_tick", 23000L), 24000L);
|
||||
if (start == end) {
|
||||
return false;
|
||||
}
|
||||
if (start < end) {
|
||||
return dayTime >= start && dayTime < end;
|
||||
}
|
||||
return dayTime >= start || dayTime < end;
|
||||
}
|
||||
|
||||
private boolean maintainNeeds(CoalNpc npc, List<CoalNpc> npcs) throws SQLException {
|
||||
if (!plugin.getConfig().getBoolean("npc.needs.enabled", true)) {
|
||||
return true;
|
||||
}
|
||||
long now = System.currentTimeMillis();
|
||||
long intervalMillis = Math.max(1L, plugin.getConfig().getLong("npc.needs.hunger_interval_minutes", 20L)) * 60_000L;
|
||||
long lastFoodAt = npc.lastFoodAt() <= 0L ? now : npc.lastFoodAt();
|
||||
int hunger = Math.max(0, Math.min(20, npc.hunger()));
|
||||
if (now - lastFoodAt >= intervalMillis) {
|
||||
long intervals = Math.min(24L, (now - lastFoodAt) / intervalMillis);
|
||||
hunger = Math.max(0, hunger - (int) intervals);
|
||||
lastFoodAt += intervals * intervalMillis;
|
||||
}
|
||||
int eatBelow = Math.max(1, Math.min(20, plugin.getConfig().getInt("npc.needs.eat_below_hunger", 14)));
|
||||
if (hunger <= eatBelow) {
|
||||
Material food = consumeFood(npc, npcs);
|
||||
if (food != null) {
|
||||
hunger = Math.min(20, hunger + foodValue(food));
|
||||
lastFoodAt = now;
|
||||
}
|
||||
}
|
||||
repository.updateNeeds(npc.id(), hunger, lastFoodAt);
|
||||
return hunger > Math.max(0, plugin.getConfig().getInt("npc.needs.work_min_hunger", 6));
|
||||
}
|
||||
|
||||
private Material consumeFood(CoalNpc npc, List<CoalNpc> npcs) throws SQLException {
|
||||
for (Material food : foodPreferences(npc)) {
|
||||
if (repository.removeInventory(npc.id(), food, 1L)) {
|
||||
return food;
|
||||
}
|
||||
requestInput(npc, food, 1L, npcs);
|
||||
if (repository.removeInventory(npc.id(), food, 1L)) {
|
||||
return food;
|
||||
}
|
||||
if (buyFoodFromMarket(npc, food)) {
|
||||
return food;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean buyFoodFromMarket(CoalNpc npc, Material food) throws SQLException {
|
||||
if (!plugin.getConfig().getBoolean("npc.needs.buy_food_from_market", true)) {
|
||||
return false;
|
||||
}
|
||||
double price = plugin.marketService().basePrices().getOrDefault(food, 1.0D);
|
||||
double markup = Math.max(0.0D, plugin.getConfig().getDouble("npc.needs.market_food_markup", 1.25D));
|
||||
long cost = Math.max(1L, CoalMoney.fromCoal(price * markup));
|
||||
if (!plugin.economyService().charge(npc.accountUuid(), cost, "npc_food")) {
|
||||
return false;
|
||||
}
|
||||
plugin.governmentService().collectMarketRevenue(cost);
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<Material> foodPreferences(CoalNpc npc) {
|
||||
List<String> configured = plugin.getConfig().getStringList("npc.needs.food_preferences." + roleName(npc));
|
||||
if (configured.isEmpty()) {
|
||||
configured = plugin.getConfig().getStringList("npc.needs.default_food_preferences");
|
||||
}
|
||||
List<Material> foods = new ArrayList<>();
|
||||
for (String raw : configured) {
|
||||
Material material = Material.matchMaterial(raw);
|
||||
if (material != null) {
|
||||
foods.add(material);
|
||||
}
|
||||
}
|
||||
return foods.isEmpty() ? List.of(Material.BREAD, Material.BAKED_POTATO, Material.APPLE) : foods;
|
||||
}
|
||||
|
||||
private int foodValue(Material food) {
|
||||
return Math.max(1, plugin.getConfig().getInt("npc.needs.food_values." + food.name(),
|
||||
plugin.getConfig().getInt("npc.needs.default_food_value", 5)));
|
||||
}
|
||||
|
||||
private String roleName(CoalNpc npc) {
|
||||
if (npc.role() != null && !npc.role().isBlank()) {
|
||||
return npc.role();
|
||||
}
|
||||
return npc.name().toLowerCase().replace(' ', '_');
|
||||
}
|
||||
|
||||
private void returnHome(CoalNpc coalNpc, NPC npc) {
|
||||
if (!npc.isSpawned()) {
|
||||
return;
|
||||
}
|
||||
Location home = homeLocation(coalNpc);
|
||||
if (home == null) {
|
||||
return;
|
||||
}
|
||||
if (npc.getEntity().getLocation().distanceSquared(home) > 4.0D) {
|
||||
npc.getNavigator().setTarget(home);
|
||||
} else if (npc.getNavigator().isNavigating()) {
|
||||
npc.getNavigator().cancelNavigation();
|
||||
}
|
||||
}
|
||||
|
||||
private Location homeLocation(CoalNpc coalNpc) {
|
||||
World world = Bukkit.getWorld(coalNpc.world());
|
||||
if (world == null) {
|
||||
return null;
|
||||
}
|
||||
return new Location(world, coalNpc.x(), coalNpc.y(), coalNpc.z());
|
||||
}
|
||||
|
||||
private Location workLocation(CoalNpc npc) {
|
||||
if (npc.workWorld() == null || npc.workX() == null || npc.workY() == null || npc.workZ() == null) {
|
||||
return null;
|
||||
}
|
||||
World world = Bukkit.getWorld(npc.workWorld());
|
||||
return world == null ? null : new Location(world, npc.workX(), npc.workY(), npc.workZ());
|
||||
}
|
||||
|
||||
private Location randomWorkPoint(CoalNpc npc, NpcZone zone) {
|
||||
Location work = workLocation(npc);
|
||||
if (work != null && random.nextInt(4) != 0) {
|
||||
return work.clone().add(random.nextDouble() * 4.0D - 2.0D, 0.0D, random.nextDouble() * 4.0D - 2.0D);
|
||||
}
|
||||
return randomPoint(zone);
|
||||
}
|
||||
|
||||
private double workArrivalDistanceSquared() {
|
||||
double distance = Math.max(1.0D, plugin.getConfig().getDouble("npc.schedule.work_arrival_distance", 4.0D));
|
||||
return distance * distance;
|
||||
}
|
||||
|
||||
private WorkerRecipe recipe(String name, ConfigurationSection section) {
|
||||
if (section == null) {
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.librewiki.coalgov.service;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.GameRule;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.WorldCreator;
|
||||
import org.bukkit.WorldType;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public final class PreserveWorldService {
|
||||
private final CoalGovPlugin plugin;
|
||||
private FileConfiguration config;
|
||||
|
||||
public PreserveWorldService(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.config = plugin.getConfig();
|
||||
}
|
||||
|
||||
public void setConfig(FileConfiguration config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public boolean enabled() {
|
||||
return config.getBoolean("preserve.enabled", true);
|
||||
}
|
||||
|
||||
public String worldName() {
|
||||
return config.getString("preserve.world", "preserve");
|
||||
}
|
||||
|
||||
public boolean isPreserveWorld(World world) {
|
||||
return world != null && world.getName().equals(worldName());
|
||||
}
|
||||
|
||||
public boolean isPreserveWorld(Location location) {
|
||||
return location != null && isPreserveWorld(location.getWorld());
|
||||
}
|
||||
|
||||
public World loadPreserveWorld() {
|
||||
if (!enabled()) {
|
||||
return null;
|
||||
}
|
||||
World existing = Bukkit.getWorld(worldName());
|
||||
if (existing != null) {
|
||||
applyRules(existing);
|
||||
return existing;
|
||||
}
|
||||
WorldCreator creator = new WorldCreator(worldName());
|
||||
creator.environment(World.Environment.NORMAL);
|
||||
creator.type(WorldType.NORMAL);
|
||||
creator.generateStructures(false);
|
||||
World world = creator.createWorld();
|
||||
if (world != null) {
|
||||
applyRules(world);
|
||||
plugin.getLogger().info("Loaded preserve world: " + world.getName());
|
||||
} else {
|
||||
plugin.getLogger().warning("Could not load preserve world: " + worldName());
|
||||
}
|
||||
return world;
|
||||
}
|
||||
|
||||
public boolean canBypass(Player player) {
|
||||
return player != null && plugin.hasAdminBypass(player);
|
||||
}
|
||||
|
||||
public Location teleportLocation() {
|
||||
World world = loadPreserveWorld();
|
||||
if (world == null) {
|
||||
return null;
|
||||
}
|
||||
double x = config.getDouble("preserve.spawn.x", 0.5D);
|
||||
double z = config.getDouble("preserve.spawn.z", 0.5D);
|
||||
double y = config.getDouble("preserve.spawn.y", Double.NaN);
|
||||
if (Double.isNaN(y)) {
|
||||
y = world.getHighestBlockYAt((int) Math.floor(x), (int) Math.floor(z)) + 1.0D;
|
||||
}
|
||||
float yaw = (float) config.getDouble("preserve.spawn.yaw", 0.0D);
|
||||
float pitch = (float) config.getDouble("preserve.spawn.pitch", 0.0D);
|
||||
return new Location(world, x, y, z, yaw, pitch);
|
||||
}
|
||||
|
||||
private void applyRules(World world) {
|
||||
world.setGameRule(GameRule.MOB_GRIEFING, false);
|
||||
world.setGameRule(GameRule.DO_FIRE_TICK, false);
|
||||
world.setPVP(false);
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,9 @@ public final class SuperFurnaceService {
|
||||
|
||||
public void registerRecipe() {
|
||||
Bukkit.removeRecipe(recipeKey);
|
||||
if (!plugin.getConfig().getBoolean("super_furnace.recipe_enabled", false)) {
|
||||
return;
|
||||
}
|
||||
ShapedRecipe recipe = new ShapedRecipe(recipeKey, item());
|
||||
recipe.shape("SSS", "SFS", "SSS");
|
||||
recipe.setIngredient('S', Material.STONE);
|
||||
@@ -54,6 +57,14 @@ public final class SuperFurnaceService {
|
||||
return item;
|
||||
}
|
||||
|
||||
public boolean syntheticDiamondsEnabled() {
|
||||
return plugin.getConfig().getBoolean("synthetic_diamonds.enabled", true);
|
||||
}
|
||||
|
||||
public int syntheticDiamondCoalInput() {
|
||||
return Math.max(2, plugin.getConfig().getInt("synthetic_diamonds.coal_input", 40));
|
||||
}
|
||||
|
||||
public boolean isItem(ItemStack item) {
|
||||
if (item == null || item.getType() != Material.FURNACE || !item.hasItemMeta()) {
|
||||
return false;
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.librewiki.coalgov.service;
|
||||
|
||||
import com.librewiki.coalgov.storage.WebTokenRepository;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HexFormat;
|
||||
|
||||
public final class WebTokenService {
|
||||
private static final char[] TOKEN_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789".toCharArray();
|
||||
private final WebTokenRepository repository;
|
||||
private final SecureRandom random = new SecureRandom();
|
||||
private FileConfiguration config;
|
||||
|
||||
public WebTokenService(WebTokenRepository repository, FileConfiguration config) {
|
||||
this.repository = repository;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public void setConfig(FileConfiguration config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public TokenIssue issue(Player player) throws SQLException {
|
||||
String token = token();
|
||||
long minutes = Math.max(1L, config.getLong("web.tokens.expire_minutes", 10L));
|
||||
long expiresAt = System.currentTimeMillis() + minutes * 60_000L;
|
||||
repository.create(player.getUniqueId(), hash(token), expiresAt);
|
||||
return new TokenIssue(token, minutes);
|
||||
}
|
||||
|
||||
private String token() {
|
||||
return group() + "-" + group() + "-" + group();
|
||||
}
|
||||
|
||||
private String group() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
builder.append(TOKEN_CHARS[random.nextInt(TOKEN_CHARS.length)]);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String hash(String token) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
return HexFormat.of().formatHex(digest.digest(token.toUpperCase().replace(" ", "").getBytes()));
|
||||
} catch (NoSuchAlgorithmException exception) {
|
||||
throw new IllegalStateException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
public record TokenIssue(String token, long expiresMinutes) {
|
||||
}
|
||||
}
|
||||
@@ -165,6 +165,34 @@ public final class ClaimRepository {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean transferGovernmentOwned(UUID governmentOwner, long id, UUID newOwner, long purchaseCost) throws SQLException {
|
||||
boolean originalAutoCommit = database.connection().getAutoCommit();
|
||||
database.connection().setAutoCommit(false);
|
||||
try {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
UPDATE claims SET owner_uuid = ?, purchase_cost = ?
|
||||
WHERE id = ? AND owner_uuid = ?
|
||||
""")) {
|
||||
statement.setString(1, newOwner.toString());
|
||||
statement.setLong(2, purchaseCost);
|
||||
statement.setLong(3, id);
|
||||
statement.setString(4, governmentOwner.toString());
|
||||
if (statement.executeUpdate() != 1) {
|
||||
database.connection().rollback();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
clearPermissions(id);
|
||||
database.connection().commit();
|
||||
return true;
|
||||
} catch (SQLException exception) {
|
||||
database.connection().rollback();
|
||||
throw exception;
|
||||
} finally {
|
||||
database.connection().setAutoCommit(originalAutoCommit);
|
||||
}
|
||||
}
|
||||
|
||||
public void grantPermission(long claimId, UUID playerUuid, ClaimPermission permission) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
INSERT INTO claim_permissions(claim_id, player_uuid, permission, granted_at)
|
||||
|
||||
@@ -141,6 +141,44 @@ public final class Database {
|
||||
updated_at INTEGER NOT NULL
|
||||
)
|
||||
""");
|
||||
statement.execute("""
|
||||
CREATE TABLE IF NOT EXISTS market_buybacks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
player_uuid TEXT NOT NULL,
|
||||
material TEXT NOT NULL,
|
||||
amount INTEGER NOT NULL,
|
||||
unit_price INTEGER NOT NULL,
|
||||
expires_at INTEGER NOT NULL,
|
||||
created_at INTEGER NOT NULL
|
||||
)
|
||||
""");
|
||||
statement.execute("""
|
||||
CREATE TABLE IF NOT EXISTS lottery_rounds (
|
||||
round INTEGER PRIMARY KEY,
|
||||
drawn_at INTEGER,
|
||||
winner_uuid TEXT,
|
||||
pot INTEGER NOT NULL DEFAULT 0
|
||||
)
|
||||
""");
|
||||
statement.execute("""
|
||||
CREATE TABLE IF NOT EXISTS lottery_tickets (
|
||||
round INTEGER NOT NULL,
|
||||
player_uuid TEXT NOT NULL,
|
||||
player_name TEXT NOT NULL,
|
||||
tickets INTEGER NOT NULL DEFAULT 0,
|
||||
updated_at INTEGER NOT NULL,
|
||||
PRIMARY KEY (round, player_uuid)
|
||||
)
|
||||
""");
|
||||
statement.execute("""
|
||||
CREATE TABLE IF NOT EXISTS web_tokens (
|
||||
token_hash TEXT PRIMARY KEY,
|
||||
player_uuid TEXT NOT NULL,
|
||||
expires_at INTEGER NOT NULL,
|
||||
used_at INTEGER,
|
||||
created_at INTEGER NOT NULL
|
||||
)
|
||||
""");
|
||||
statement.execute("""
|
||||
CREATE TABLE IF NOT EXISTS treasury (
|
||||
id TEXT PRIMARY KEY,
|
||||
@@ -228,8 +266,19 @@ public final class Database {
|
||||
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)");
|
||||
statement.execute("CREATE INDEX IF NOT EXISTS idx_market_buybacks_player ON market_buybacks(player_uuid, material, expires_at)");
|
||||
statement.execute("CREATE INDEX IF NOT EXISTS idx_lottery_tickets_round ON lottery_tickets(round)");
|
||||
statement.execute("CREATE INDEX IF NOT EXISTS idx_web_tokens_player ON web_tokens(player_uuid, expires_at)");
|
||||
}
|
||||
ensureColumn("claims", "purchase_cost", "INTEGER NOT NULL DEFAULT -1");
|
||||
ensureColumn("claims", "display_name", "TEXT");
|
||||
ensureColumn("coalgov_npcs", "role", "TEXT NOT NULL DEFAULT ''");
|
||||
ensureColumn("coalgov_npcs", "work_world", "TEXT");
|
||||
ensureColumn("coalgov_npcs", "work_x", "REAL");
|
||||
ensureColumn("coalgov_npcs", "work_y", "REAL");
|
||||
ensureColumn("coalgov_npcs", "work_z", "REAL");
|
||||
ensureColumn("coalgov_npcs", "hunger", "INTEGER NOT NULL DEFAULT 20");
|
||||
ensureColumn("coalgov_npcs", "last_food_at", "INTEGER NOT NULL DEFAULT 0");
|
||||
migrateCoalBalancesToCents();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.librewiki.coalgov.storage;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class LotteryRepository {
|
||||
private final Database database;
|
||||
|
||||
public LotteryRepository(Database database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
public int activeRound() throws SQLException {
|
||||
try (PreparedStatement select = database.connection().prepareStatement(
|
||||
"SELECT round FROM lottery_rounds WHERE drawn_at IS NULL ORDER BY round DESC LIMIT 1")) {
|
||||
try (ResultSet result = select.executeQuery()) {
|
||||
if (result.next()) {
|
||||
return result.getInt("round");
|
||||
}
|
||||
}
|
||||
}
|
||||
int nextRound = lastRound() + 1;
|
||||
try (PreparedStatement insert = database.connection().prepareStatement(
|
||||
"INSERT INTO lottery_rounds(round, pot) VALUES (?, 0)")) {
|
||||
insert.setInt(1, nextRound);
|
||||
insert.executeUpdate();
|
||||
}
|
||||
return nextRound;
|
||||
}
|
||||
|
||||
public void addTickets(int round, UUID uuid, String name, int tickets) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
INSERT INTO lottery_tickets(round, player_uuid, player_name, tickets, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
ON CONFLICT(round, player_uuid) DO UPDATE SET
|
||||
player_name = excluded.player_name,
|
||||
tickets = tickets + excluded.tickets,
|
||||
updated_at = excluded.updated_at
|
||||
""")) {
|
||||
statement.setInt(1, round);
|
||||
statement.setString(2, uuid.toString());
|
||||
statement.setString(3, name);
|
||||
statement.setInt(4, tickets);
|
||||
statement.setLong(5, System.currentTimeMillis());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public List<TicketHolder> tickets(int round) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
SELECT player_uuid, player_name, tickets FROM lottery_tickets
|
||||
WHERE round = ? AND tickets > 0
|
||||
ORDER BY player_name
|
||||
""")) {
|
||||
statement.setInt(1, round);
|
||||
List<TicketHolder> holders = new ArrayList<>();
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
while (result.next()) {
|
||||
holders.add(new TicketHolder(
|
||||
UUID.fromString(result.getString("player_uuid")),
|
||||
result.getString("player_name"),
|
||||
result.getInt("tickets")
|
||||
));
|
||||
}
|
||||
}
|
||||
return holders;
|
||||
}
|
||||
}
|
||||
|
||||
public void markDrawn(int round, UUID winnerUuid, long pot) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
UPDATE lottery_rounds
|
||||
SET drawn_at = ?, winner_uuid = ?, pot = ?
|
||||
WHERE round = ? AND drawn_at IS NULL
|
||||
""")) {
|
||||
statement.setLong(1, System.currentTimeMillis());
|
||||
statement.setString(2, winnerUuid.toString());
|
||||
statement.setLong(3, pot);
|
||||
statement.setInt(4, round);
|
||||
statement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private int lastRound() throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement(
|
||||
"SELECT COALESCE(MAX(round), 0) AS last_round FROM lottery_rounds")) {
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
return result.next() ? result.getInt("last_round") : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public record TicketHolder(UUID uuid, String name, int tickets) {
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,9 @@ import org.bukkit.Material;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class MarketRepository {
|
||||
private final Database database;
|
||||
@@ -95,6 +98,73 @@ public final class MarketRepository {
|
||||
recordFlow(material, Math.max(0L, amount), 0L, halfLifeMillis);
|
||||
}
|
||||
|
||||
public void recordBuyback(UUID playerUuid, Material material, long amount, long unitPrice, long expiresAt) throws SQLException {
|
||||
if (amount <= 0L || unitPrice <= 0L) {
|
||||
return;
|
||||
}
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
INSERT INTO market_buybacks(player_uuid, material, amount, unit_price, expires_at, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
""")) {
|
||||
statement.setString(1, playerUuid.toString());
|
||||
statement.setString(2, material.name());
|
||||
statement.setLong(3, amount);
|
||||
statement.setLong(4, unitPrice);
|
||||
statement.setLong(5, expiresAt);
|
||||
statement.setLong(6, System.currentTimeMillis());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public List<BuybackLot> buybackLots(UUID playerUuid, Material material, long now) throws SQLException {
|
||||
try (PreparedStatement delete = database.connection().prepareStatement(
|
||||
"DELETE FROM market_buybacks WHERE expires_at <= ? OR amount <= 0")) {
|
||||
delete.setLong(1, now);
|
||||
delete.executeUpdate();
|
||||
}
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
SELECT id, amount, unit_price FROM market_buybacks
|
||||
WHERE player_uuid = ? AND material = ? AND expires_at > ?
|
||||
ORDER BY created_at, id
|
||||
""")) {
|
||||
statement.setString(1, playerUuid.toString());
|
||||
statement.setString(2, material.name());
|
||||
statement.setLong(3, now);
|
||||
List<BuybackLot> lots = new ArrayList<>();
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
while (result.next()) {
|
||||
lots.add(new BuybackLot(result.getLong("id"), result.getLong("amount"), result.getLong("unit_price")));
|
||||
}
|
||||
}
|
||||
return lots;
|
||||
}
|
||||
}
|
||||
|
||||
public void consumeBuybacks(UUID playerUuid, Material material, long amount) throws SQLException {
|
||||
long remaining = amount;
|
||||
for (BuybackLot lot : buybackLots(playerUuid, material, System.currentTimeMillis())) {
|
||||
if (remaining <= 0L) {
|
||||
return;
|
||||
}
|
||||
long used = Math.min(remaining, lot.amount());
|
||||
if (used >= lot.amount()) {
|
||||
try (PreparedStatement delete = database.connection().prepareStatement(
|
||||
"DELETE FROM market_buybacks WHERE id = ?")) {
|
||||
delete.setLong(1, lot.id());
|
||||
delete.executeUpdate();
|
||||
}
|
||||
} else {
|
||||
try (PreparedStatement update = database.connection().prepareStatement(
|
||||
"UPDATE market_buybacks SET amount = amount - ? WHERE id = ?")) {
|
||||
update.setLong(1, used);
|
||||
update.setLong(2, lot.id());
|
||||
update.executeUpdate();
|
||||
}
|
||||
}
|
||||
remaining -= used;
|
||||
}
|
||||
}
|
||||
|
||||
public void recordSell(Material material, long amount, long halfLifeMillis) throws SQLException {
|
||||
recordFlow(material, 0L, Math.max(0L, amount), halfLifeMillis);
|
||||
}
|
||||
@@ -133,4 +203,7 @@ public final class MarketRepository {
|
||||
|
||||
public record Flow(double recentBuys, double recentSells) {
|
||||
}
|
||||
|
||||
public record BuybackLot(long id, long amount, long unitPrice) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +148,55 @@ public final class NpcRepository {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean setRole(long id, String role) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement(
|
||||
"UPDATE coalgov_npcs SET role = ? WHERE id = ?")) {
|
||||
statement.setString(1, role == null ? "" : role);
|
||||
statement.setLong(2, id);
|
||||
return statement.executeUpdate() == 1;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean setHome(long id, String world, double x, double y, double z) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
UPDATE coalgov_npcs SET world = ?, x = ?, y = ?, z = ?
|
||||
WHERE id = ?
|
||||
""")) {
|
||||
statement.setString(1, world);
|
||||
statement.setDouble(2, x);
|
||||
statement.setDouble(3, y);
|
||||
statement.setDouble(4, z);
|
||||
statement.setLong(5, id);
|
||||
return statement.executeUpdate() == 1;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean setWork(long id, String world, double x, double y, double z) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
UPDATE coalgov_npcs SET work_world = ?, work_x = ?, work_y = ?, work_z = ?
|
||||
WHERE id = ?
|
||||
""")) {
|
||||
statement.setString(1, world);
|
||||
statement.setDouble(2, x);
|
||||
statement.setDouble(3, y);
|
||||
statement.setDouble(4, z);
|
||||
statement.setLong(5, id);
|
||||
return statement.executeUpdate() == 1;
|
||||
}
|
||||
}
|
||||
|
||||
public void updateNeeds(long id, int hunger, long lastFoodAt) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
UPDATE coalgov_npcs SET hunger = ?, last_food_at = ?
|
||||
WHERE id = ?
|
||||
""")) {
|
||||
statement.setInt(1, Math.max(0, Math.min(20, hunger)));
|
||||
statement.setLong(2, lastFoodAt);
|
||||
statement.setLong(3, id);
|
||||
statement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public void addInventory(long npcId, Material material, long amount) throws SQLException {
|
||||
if (amount <= 0L) {
|
||||
return;
|
||||
@@ -250,10 +299,22 @@ public final class NpcRepository {
|
||||
result.getString("world"),
|
||||
result.getDouble("x"),
|
||||
result.getDouble("y"),
|
||||
result.getDouble("z")
|
||||
result.getDouble("z"),
|
||||
result.getString("role"),
|
||||
result.getString("work_world"),
|
||||
nullableDouble(result, "work_x"),
|
||||
nullableDouble(result, "work_y"),
|
||||
nullableDouble(result, "work_z"),
|
||||
result.getInt("hunger"),
|
||||
result.getLong("last_food_at")
|
||||
);
|
||||
}
|
||||
|
||||
private Double nullableDouble(ResultSet result, String column) throws SQLException {
|
||||
double value = result.getDouble(column);
|
||||
return result.wasNull() ? null : value;
|
||||
}
|
||||
|
||||
private List<ClaimPoint> vertices(long zoneId) throws SQLException {
|
||||
List<ClaimPoint> points = new ArrayList<>();
|
||||
try (PreparedStatement statement = database.connection().prepareStatement(
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.librewiki.coalgov.storage;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class WebTokenRepository {
|
||||
private final Database database;
|
||||
|
||||
public WebTokenRepository(Database database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
public void create(UUID playerUuid, String tokenHash, long expiresAt) throws SQLException {
|
||||
try (PreparedStatement cleanup = database.connection().prepareStatement(
|
||||
"DELETE FROM web_tokens WHERE player_uuid = ? OR expires_at <= ? OR used_at IS NOT NULL")) {
|
||||
cleanup.setString(1, playerUuid.toString());
|
||||
cleanup.setLong(2, System.currentTimeMillis());
|
||||
cleanup.executeUpdate();
|
||||
}
|
||||
try (PreparedStatement insert = database.connection().prepareStatement("""
|
||||
INSERT INTO web_tokens(token_hash, player_uuid, expires_at, used_at, created_at)
|
||||
VALUES (?, ?, ?, NULL, ?)
|
||||
""")) {
|
||||
insert.setString(1, tokenHash);
|
||||
insert.setString(2, playerUuid.toString());
|
||||
insert.setLong(3, expiresAt);
|
||||
insert.setLong(4, System.currentTimeMillis());
|
||||
insert.executeUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,43 @@ npc:
|
||||
trader:
|
||||
max_discount_percent: 15
|
||||
max_surcharge_percent: 20
|
||||
schedule:
|
||||
enabled: true
|
||||
rest_start_tick: 13000
|
||||
rest_end_tick: 23000
|
||||
work_arrival_distance: 4.0
|
||||
needs:
|
||||
enabled: true
|
||||
hunger_interval_minutes: 20
|
||||
eat_below_hunger: 14
|
||||
work_min_hunger: 6
|
||||
buy_food_from_market: true
|
||||
market_food_markup: 1.25
|
||||
default_food_value: 5
|
||||
default_food_preferences:
|
||||
- BREAD
|
||||
- BAKED_POTATO
|
||||
- APPLE
|
||||
food_preferences:
|
||||
farmer:
|
||||
- BREAD
|
||||
- BAKED_POTATO
|
||||
- CARROT
|
||||
baker:
|
||||
- BREAD
|
||||
- APPLE
|
||||
trader:
|
||||
- BREAD
|
||||
- COOKED_BEEF
|
||||
- APPLE
|
||||
food_values:
|
||||
BREAD: 5
|
||||
BAKED_POTATO: 5
|
||||
APPLE: 4
|
||||
CARROT: 3
|
||||
COOKED_BEEF: 8
|
||||
COOKED_PORKCHOP: 8
|
||||
COOKED_CHICKEN: 6
|
||||
worker:
|
||||
tick_interval_seconds: 20
|
||||
allow_default_production: false
|
||||
@@ -89,6 +126,9 @@ divining_rod:
|
||||
market:
|
||||
treasury:
|
||||
transaction_budget_percent: 25.0
|
||||
buyback_protection:
|
||||
enabled: true
|
||||
duration_hours: 24
|
||||
dynamic:
|
||||
initial_stock: 1024
|
||||
target_stock: 1024
|
||||
@@ -154,7 +194,7 @@ market:
|
||||
APPLE: 0.75
|
||||
CARROT: 0.25
|
||||
POTATO: 0.20
|
||||
BAKED_POTATO: 0.45
|
||||
BAKED_POTATO: 0.30
|
||||
BEETROOT: 0.20
|
||||
MELON_SLICE: 0.15
|
||||
SWEET_BERRIES: 0.20
|
||||
@@ -194,3 +234,32 @@ spawn:
|
||||
|
||||
messages:
|
||||
prefix: "&8[&6CoalGov&8]&r "
|
||||
|
||||
super_furnace:
|
||||
recipe_enabled: false
|
||||
|
||||
synthetic_diamonds:
|
||||
enabled: true
|
||||
coal_input: 40
|
||||
|
||||
name_tags:
|
||||
recover_named_entity_tags: true
|
||||
|
||||
lottery:
|
||||
ticket_cost: 1.0
|
||||
max_tickets_per_buy: 64
|
||||
payout_percent: 90.0
|
||||
|
||||
web:
|
||||
tokens:
|
||||
expire_minutes: 10
|
||||
|
||||
preserve:
|
||||
enabled: true
|
||||
world: preserve
|
||||
spawn:
|
||||
x: 0.5
|
||||
y: 96.0
|
||||
z: 0.5
|
||||
yaw: 0.0
|
||||
pitch: 0.0
|
||||
|
||||
@@ -9,8 +9,12 @@ depend: [Citizens]
|
||||
commands:
|
||||
coal:
|
||||
description: CoalGov economy commands.
|
||||
usage: /coal <balance|deposit|withdraw|pay|fines>
|
||||
usage: /coal <balance|deposit|withdraw|pay|fines|networth|lottery>
|
||||
permission: coalgov.coal
|
||||
auction:
|
||||
description: CoalGov chat auctions.
|
||||
usage: /auction <start|bid|status|cancel>
|
||||
permission: coalgov.auction
|
||||
claim:
|
||||
description: CoalGov claim commands.
|
||||
usage: /claim <wand|buy|info|list|show|appraise|sell|transfer|abandon|trust|untrust|permissions>
|
||||
@@ -40,12 +44,15 @@ commands:
|
||||
permission: coalgov.police
|
||||
cgnpc:
|
||||
description: CoalGov NPC commands.
|
||||
usage: /cgnpc <zone|create|remove|list|stock|funds|haggle>
|
||||
usage: /cgnpc <zone|create|remove|list|stock|funds|role|home|work|needs|haggle>
|
||||
permission: coalgov.admin
|
||||
coalgov:
|
||||
description: CoalGov admin commands.
|
||||
usage: /coalgov admin <balance|grant|take|reload|spawn|bypass|rod|treasury|tax>
|
||||
permission: coalgov.admin
|
||||
description: CoalGov commands.
|
||||
usage: /coalgov <webtoken|admin>
|
||||
preserve:
|
||||
description: Teleport to the CoalGov wildlife preserve.
|
||||
usage: /preserve [return]
|
||||
permission: coalgov.preserve
|
||||
|
||||
permissions:
|
||||
coalgov.admin:
|
||||
@@ -54,6 +61,9 @@ permissions:
|
||||
coalgov.coal:
|
||||
description: Allows coal economy commands.
|
||||
default: true
|
||||
coalgov.auction:
|
||||
description: Allows CoalGov chat auctions.
|
||||
default: true
|
||||
coalgov.claim:
|
||||
description: Allows claim commands.
|
||||
default: true
|
||||
@@ -72,3 +82,6 @@ permissions:
|
||||
coalgov.police:
|
||||
description: Allows issuing police fines.
|
||||
default: op
|
||||
coalgov.preserve:
|
||||
description: Allows teleporting to the wildlife preserve.
|
||||
default: true
|
||||
|
||||
Reference in New Issue
Block a user