Add Coal Cents economy

This commit is contained in:
CoalGov Deploy
2026-05-04 17:00:05 +00:00
commit c8cc200004
73 changed files with 6873 additions and 0 deletions

View File

@@ -0,0 +1,218 @@
package com.librewiki.coalgov.service;
import com.librewiki.coalgov.CoalGovPlugin;
import com.librewiki.coalgov.model.ClaimPoint;
import com.librewiki.coalgov.model.CoalNpc;
import com.librewiki.coalgov.model.NpcZone;
import com.librewiki.coalgov.storage.NpcRepository;
import com.librewiki.coalgov.util.CoalMoney;
import net.citizensnpcs.api.CitizensAPI;
import net.citizensnpcs.api.npc.NPC;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.UUID;
public final class NpcService {
private final CoalGovPlugin plugin;
private final NpcRepository repository;
private final Random random = new Random();
public NpcService(CoalGovPlugin plugin, NpcRepository repository) {
this.plugin = plugin;
this.repository = repository;
}
public long createZone(String name, String world, List<ClaimPoint> points) throws SQLException {
return repository.createZone(name, world, points);
}
public long createNpc(String type, String name, NpcZone zone, Location location) throws SQLException {
NPC npc = CitizensAPI.getNPCRegistry().createNPC(EntityType.VILLAGER, name);
npc.spawn(location);
npc.setProtected(true);
UUID accountUuid = UUID.nameUUIDFromBytes(("coalgov:npc:" + npc.getId()).getBytes(StandardCharsets.UTF_8));
plugin.economyService().ensureAccount(accountUuid, "NPC-" + name);
return repository.createNpc(npc.getId(), accountUuid, type, name, zone.id(),
location.getWorld().getName(), location.getX(), location.getY(), location.getZ());
}
public boolean removeNpc(long id) throws SQLException {
Optional<CoalNpc> coalNpc = repository.findNpc(id);
if (coalNpc.isPresent()) {
NPC npc = CitizensAPI.getNPCRegistry().getById(coalNpc.get().citizensId());
if (npc != null) {
npc.destroy();
}
}
return repository.deleteNpc(id);
}
public Optional<CoalNpc> findNpc(long id) throws SQLException {
return repository.findNpc(id);
}
public Optional<CoalNpc> findByCitizensId(int citizensId) throws SQLException {
return repository.findByCitizensId(citizensId);
}
public Optional<NpcZone> findZone(String name) throws SQLException {
return repository.findZone(name);
}
public List<CoalNpc> listNpcs() throws SQLException {
return repository.listNpcs();
}
public List<NpcZone> listZones() throws SQLException {
return repository.listZones();
}
public Map<Material, Long> inventory(long npcId) throws SQLException {
return repository.inventory(npcId);
}
public void addInventory(long npcId, Material material, long amount) throws SQLException {
repository.addInventory(npcId, material, amount);
}
public long balance(CoalNpc npc) throws SQLException {
return plugin.economyService().balance(npc.accountUuid());
}
public HaggleQuote quote(CoalNpc npc, String mode, Material material, int amount, long offer) throws SQLException {
double base = plugin.marketService().basePrices().getOrDefault(material, 1.0D);
long fair = Math.max(1L, CoalMoney.fromCoal(base * amount));
int maxDiscount = plugin.getConfig().getInt("npc.trader.max_discount_percent", 15);
int maxSurcharge = plugin.getConfig().getInt("npc.trader.max_surcharge_percent", 20);
if (mode.equalsIgnoreCase("buy")) {
long stock = repository.inventoryAmount(npc.id(), material);
long listPrice = Math.max(1L, Math.round(fair * 1.10D));
long minimum = Math.max(1L, Math.round(listPrice * (100.0D - maxDiscount) / 100.0D));
long counter = Math.max(minimum, Math.min(listPrice, offer <= 0L ? listPrice : Math.round((offer + listPrice) / 2.0D)));
boolean possible = stock >= amount;
boolean accepted = possible && offer >= minimum;
return new HaggleQuote(mode.toLowerCase(), material, amount, offer, minimum, listPrice, counter, accepted, possible ? "" : "I do not have enough stock.");
}
long maximum = Math.max(1L, Math.round(fair * (100.0D + maxSurcharge) / 100.0D));
long target = Math.max(1L, Math.round(fair * 0.90D));
long counter = Math.min(maximum, Math.max(target, offer <= 0L ? target : Math.round((offer + target) / 2.0D)));
boolean possible = balance(npc) >= offer;
boolean accepted = possible && offer <= maximum;
return new HaggleQuote(mode.toLowerCase(), material, amount, offer, target, maximum, counter, accepted, possible ? "" : "I do not have enough coal.");
}
public boolean completeTrade(Player player, CoalNpc npc, HaggleQuote quote, long finalPrice) throws SQLException {
if (quote.mode().equals("buy")) {
if (repository.inventoryAmount(npc.id(), quote.material()) < quote.amount()) {
return false;
}
if (!plugin.economyService().charge(player.getUniqueId(), finalPrice, "npc_purchase")) {
return false;
}
repository.removeInventory(npc.id(), quote.material(), quote.amount());
plugin.economyService().credit(npc.accountUuid(), finalPrice, "npc_sale");
player.getInventory().addItem(new ItemStack(quote.material(), quote.amount()));
return true;
}
if (!playerHas(player, quote.material(), quote.amount()) || balance(npc) < finalPrice) {
return false;
}
if (!plugin.economyService().charge(npc.accountUuid(), finalPrice, "npc_purchase")) {
return false;
}
removePlayerItems(player, quote.material(), quote.amount());
plugin.economyService().credit(player.getUniqueId(), finalPrice, "npc_sale");
repository.addInventory(npc.id(), quote.material(), quote.amount());
return true;
}
public void tickWorkers() {
try {
for (CoalNpc coalNpc : repository.listNpcs()) {
if (!coalNpc.worker()) {
continue;
}
NPC npc = CitizensAPI.getNPCRegistry().getById(coalNpc.citizensId());
Optional<NpcZone> zone = repository.findZone(coalNpc.zoneId());
if (npc == null || zone.isEmpty()) {
continue;
}
if (!npc.isSpawned()) {
World world = Bukkit.getWorld(coalNpc.world());
if (world != null) {
npc.spawn(new Location(world, coalNpc.x(), coalNpc.y(), coalNpc.z()));
}
}
if (npc.isSpawned() && !npc.getNavigator().isNavigating()) {
Location target = randomPoint(zone.get());
if (target != null) {
npc.getNavigator().setTarget(target);
}
}
Material material = Material.matchMaterial(plugin.getConfig().getString("npc.worker.produce_material", "BREAD"));
int amount = plugin.getConfig().getInt("npc.worker.produce_amount", 2);
if (material != null && amount > 0) {
repository.addInventory(coalNpc.id(), material, amount);
}
}
} catch (SQLException exception) {
plugin.getLogger().warning("NPC worker tick failed: " + exception.getMessage());
}
}
private Location randomPoint(NpcZone zone) {
World world = Bukkit.getWorld(zone.world());
if (world == null) {
return null;
}
for (int tries = 0; tries < 32; tries++) {
int x = zone.x1() + random.nextInt(Math.max(1, zone.x2() - zone.x1() + 1));
int z = zone.z1() + random.nextInt(Math.max(1, zone.z2() - zone.z1() + 1));
if (zone.contains(world.getName(), x, z)) {
return new Location(world, x + 0.5D, world.getHighestBlockYAt(x, z), z + 0.5D);
}
}
return new Location(world, zone.x1() + 0.5D, world.getHighestBlockYAt(zone.x1(), zone.z1()), zone.z1() + 0.5D);
}
private boolean playerHas(Player player, Material material, int amount) {
int found = 0;
for (ItemStack item : player.getInventory().getStorageContents()) {
if (item != null && item.getType() == material) {
found += item.getAmount();
}
}
return found >= amount;
}
private void removePlayerItems(Player player, Material material, int amount) {
int remaining = amount;
for (ItemStack item : player.getInventory().getStorageContents()) {
if (item == null || item.getType() != material) {
continue;
}
int take = Math.min(remaining, item.getAmount());
item.setAmount(item.getAmount() - take);
remaining -= take;
if (remaining <= 0) {
return;
}
}
}
public record HaggleQuote(String mode, Material material, int amount, long offer, long floor, long ceiling,
long counter, boolean accepted, String reason) {
}
}