Add claim access permissions
This commit is contained in:
@@ -93,9 +93,31 @@ CoalGov supports rectangular and polygon claims.
|
||||
- Claim corners receive protected marker blocks
|
||||
- `/claim appraise` estimates resale value from land condition and configured resources
|
||||
- `/claim sell` sells the claim back to the Ministry
|
||||
- `/showpropertylines` shows the property lines for the claim you are standing in
|
||||
- `/claim trust` and `/claim untrust` grant or revoke claim-scoped access for other players
|
||||
|
||||
The first small homestead can be free, depending on config.
|
||||
|
||||
Claim owners can grant granular access:
|
||||
|
||||
- `build`: place and break blocks
|
||||
- `interact`: use non-container interactable blocks such as doors, buttons, levers, gates, and similar blocks
|
||||
- `container`: open inventory blocks such as chests, barrels, furnaces, and hoppers
|
||||
- `animal`: damage animals inside the claim
|
||||
- `manage`: manage claim permissions
|
||||
- `all`: grant or revoke every permission above
|
||||
|
||||
Example:
|
||||
|
||||
```text
|
||||
/claim trust 12 Alex build
|
||||
/claim trust 12 Alex container
|
||||
/claim untrust 12 Alex build
|
||||
/claim permissions 12
|
||||
```
|
||||
|
||||
Unclaimed land does not block animal damage for now. Claimed land blocks animal damage unless the player owns the claim, has `animal`, has `manage`, or is using admin bypass.
|
||||
|
||||
## Land Classes And Mining
|
||||
|
||||
Admins can define land regions with classes such as freehold, government land, protected preserves, border zones, and mining concessions.
|
||||
@@ -138,9 +160,13 @@ CoalGov adds a persisted Super Furnace recipe. Craft a furnace with stone in all
|
||||
/claim sell [id]
|
||||
/claim transfer <id> <player>
|
||||
/claim abandon <id>
|
||||
/claim trust <id> <player> <build|interact|container|animal|manage|all>
|
||||
/claim untrust <id> <player> <build|interact|container|animal|manage|all>
|
||||
/claim permissions <id>
|
||||
/permit buy mining <regionName>
|
||||
/permit list
|
||||
/diviningrod
|
||||
/showpropertylines
|
||||
/land info
|
||||
```
|
||||
|
||||
@@ -229,6 +255,10 @@ Always back up the current plugin jar and `plugins/CoalGov/coalgov.db` before de
|
||||
- `/market` shows per-item and batch prices
|
||||
- Market buy/sell updates stock and balance
|
||||
- `/claim buy` charges the displayed Coal Cents amount
|
||||
- `/claim trust` allows another player to use only the granted access
|
||||
- A player without `animal` access cannot kill animals inside another player's claim
|
||||
- Animals on unclaimed land can still be killed
|
||||
- `/showpropertylines` displays polygon edges between claim marker points
|
||||
- `/claim appraise` and `/claim sell` display formatted money
|
||||
- Mining protected coal is denied
|
||||
- Mining concession coal requires a valid permit
|
||||
|
||||
@@ -9,13 +9,16 @@ 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.ShowPropertyLinesCommand;
|
||||
import com.librewiki.coalgov.listener.BlockBreakListener;
|
||||
import com.librewiki.coalgov.listener.BlockPlaceListener;
|
||||
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.NpcListener;
|
||||
import com.librewiki.coalgov.listener.PlayerJoinListener;
|
||||
import com.librewiki.coalgov.listener.PropertyInteractListener;
|
||||
import com.librewiki.coalgov.listener.SuperFurnaceListener;
|
||||
import com.librewiki.coalgov.service.BuildProtectionService;
|
||||
import com.librewiki.coalgov.service.ClaimMarkerService;
|
||||
@@ -125,12 +128,15 @@ public final class CoalGovPlugin extends JavaPlugin {
|
||||
Objects.requireNonNull(getCommand("land")).setExecutor(new LandCommand(this));
|
||||
Objects.requireNonNull(getCommand("market")).setExecutor(new MarketCommand(this));
|
||||
Objects.requireNonNull(getCommand("diviningrod")).setExecutor(new DiviningRodCommand(this));
|
||||
Objects.requireNonNull(getCommand("showpropertylines")).setExecutor(new ShowPropertyLinesCommand(this));
|
||||
Objects.requireNonNull(getCommand("police")).setExecutor(new PoliceCommand(this));
|
||||
Objects.requireNonNull(getCommand("cgnpc")).setExecutor(new NpcCommand(this));
|
||||
Objects.requireNonNull(getCommand("coalgov")).setExecutor(new AdminCommand(this));
|
||||
|
||||
getServer().getPluginManager().registerEvents(new BlockBreakListener(this), this);
|
||||
getServer().getPluginManager().registerEvents(new BlockPlaceListener(this), this);
|
||||
getServer().getPluginManager().registerEvents(new PropertyInteractListener(this), this);
|
||||
getServer().getPluginManager().registerEvents(new AnimalProtectionListener(this), this);
|
||||
getServer().getPluginManager().registerEvents(new ClaimWandListener(this), this);
|
||||
getServer().getPluginManager().registerEvents(new DiviningRodListener(this), this);
|
||||
getServer().getPluginManager().registerEvents(new MarketListener(this), this);
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.librewiki.coalgov.command;
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.model.Claim;
|
||||
import com.librewiki.coalgov.model.ClaimPoint;
|
||||
import com.librewiki.coalgov.model.ClaimPermission;
|
||||
import com.librewiki.coalgov.model.ClaimPermissionGrant;
|
||||
import com.librewiki.coalgov.model.LandRegion;
|
||||
import com.librewiki.coalgov.service.ClaimService;
|
||||
import com.librewiki.coalgov.service.LandAppraisalService;
|
||||
@@ -16,8 +18,11 @@ import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class ClaimCommand implements CommandExecutor {
|
||||
private final CoalGovPlugin plugin;
|
||||
@@ -33,7 +38,7 @@ public final class ClaimCommand implements CommandExecutor {
|
||||
return true;
|
||||
}
|
||||
if (args.length == 0) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /claim <wand|buy|info|list|show|appraise|sell|transfer|abandon>"));
|
||||
usage(player);
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
@@ -48,7 +53,10 @@ public final class ClaimCommand implements CommandExecutor {
|
||||
case "sell" -> sell(player, args);
|
||||
case "transfer" -> transfer(player, args);
|
||||
case "abandon" -> abandon(player, args);
|
||||
default -> player.sendMessage(plugin.messages().text("&eUsage: /claim <wand|buy|info|list|show|appraise|sell|transfer|abandon>"));
|
||||
case "trust" -> trust(player, args);
|
||||
case "untrust" -> untrust(player, args);
|
||||
case "permissions" -> permissions(player, args);
|
||||
default -> usage(player);
|
||||
}
|
||||
} catch (SQLException exception) {
|
||||
plugin.getLogger().warning("Claim command failed: " + exception.getMessage());
|
||||
@@ -57,6 +65,10 @@ public final class ClaimCommand implements CommandExecutor {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void usage(Player player) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /claim <wand|buy|info|list|show|appraise|sell|transfer|abandon|trust|untrust|permissions>"));
|
||||
}
|
||||
|
||||
private void buy(Player player, String[] args) throws SQLException {
|
||||
if (args.length != 2 && args.length != 3) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /claim buy <homestead|industrial> [radius]"));
|
||||
@@ -200,6 +212,96 @@ 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>"));
|
||||
return;
|
||||
}
|
||||
long id = parseLong(args[1]);
|
||||
List<ClaimPermission> permissions = ClaimPermission.grantSet(args[4]);
|
||||
if (id <= 0L || permissions.isEmpty()) {
|
||||
player.sendMessage(plugin.messages().text("&cInvalid claim id or permission."));
|
||||
return;
|
||||
}
|
||||
OfflinePlayer target = Bukkit.getOfflinePlayer(args[2]);
|
||||
plugin.economyService().ensurePlayer(target);
|
||||
for (ClaimPermission permission : permissions) {
|
||||
ClaimService.PermissionResult result = plugin.claimService()
|
||||
.grantPermission(player.getUniqueId(), id, target.getUniqueId(), permission);
|
||||
if (!result.success()) {
|
||||
player.sendMessage(plugin.messages().text("&c" + result.message()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
player.sendMessage(plugin.messages().text("&aGranted &f" + args[4].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>"));
|
||||
return;
|
||||
}
|
||||
long id = parseLong(args[1]);
|
||||
if (id <= 0L) {
|
||||
player.sendMessage(plugin.messages().text("&cInvalid claim id."));
|
||||
return;
|
||||
}
|
||||
OfflinePlayer target = Bukkit.getOfflinePlayer(args[2]);
|
||||
if (args[4].equalsIgnoreCase("all")) {
|
||||
ClaimService.PermissionResult result = plugin.claimService()
|
||||
.revokeAllPermissions(player.getUniqueId(), id, target.getUniqueId());
|
||||
if (!result.success()) {
|
||||
player.sendMessage(plugin.messages().text("&c" + result.message()));
|
||||
return;
|
||||
}
|
||||
player.sendMessage(plugin.messages().text("&aRemoved all claim access for &f" + targetName(target) + "&a."));
|
||||
return;
|
||||
}
|
||||
Optional<ClaimPermission> permission = ClaimPermission.parse(args[4]);
|
||||
if (permission.isEmpty()) {
|
||||
player.sendMessage(plugin.messages().text("&cInvalid permission."));
|
||||
return;
|
||||
}
|
||||
ClaimService.PermissionResult result = plugin.claimService()
|
||||
.revokePermission(player.getUniqueId(), id, target.getUniqueId(), permission.get());
|
||||
if (!result.success()) {
|
||||
player.sendMessage(plugin.messages().text("&c" + result.message()));
|
||||
return;
|
||||
}
|
||||
player.sendMessage(plugin.messages().text("&aRemoved &f" + permission.get().label() + " &aaccess for &f" + targetName(target) + "&a."));
|
||||
}
|
||||
|
||||
private void permissions(Player player, String[] args) throws SQLException {
|
||||
if (args.length != 2) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /claim permissions <id>"));
|
||||
return;
|
||||
}
|
||||
long id = parseLong(args[1]);
|
||||
if (id <= 0L) {
|
||||
player.sendMessage(plugin.messages().text("&cInvalid claim id."));
|
||||
return;
|
||||
}
|
||||
List<ClaimPermissionGrant> grants = plugin.claimService().permissions(player.getUniqueId(), id);
|
||||
if (grants.isEmpty()) {
|
||||
player.sendMessage(plugin.messages().text("&eNo permissions listed for claim #" + id + ", or you cannot manage that claim."));
|
||||
return;
|
||||
}
|
||||
Map<UUID, StringBuilder> byPlayer = new LinkedHashMap<>();
|
||||
for (ClaimPermissionGrant grant : grants) {
|
||||
byPlayer.computeIfAbsent(grant.playerUuid(), ignored -> new StringBuilder());
|
||||
StringBuilder builder = byPlayer.get(grant.playerUuid());
|
||||
if (builder.length() > 0) {
|
||||
builder.append(", ");
|
||||
}
|
||||
builder.append(grant.permission().label());
|
||||
}
|
||||
player.sendMessage(plugin.messages().text("&eClaim #" + id + " permissions:"));
|
||||
for (var entry : byPlayer.entrySet()) {
|
||||
OfflinePlayer target = Bukkit.getOfflinePlayer(entry.getKey());
|
||||
player.sendMessage(plugin.messages().raw("&7" + targetName(target) + ": &f" + entry.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<Claim> resolveOwnedClaim(Player player, String[] args, String action) throws SQLException {
|
||||
if (args.length > 2) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /claim " + action + " [id]"));
|
||||
@@ -261,6 +363,10 @@ public final class ClaimCommand implements CommandExecutor {
|
||||
return material.name().toLowerCase().replace('_', ' ');
|
||||
}
|
||||
|
||||
private String targetName(OfflinePlayer target) {
|
||||
return target.getName() == null ? target.getUniqueId().toString() : target.getName();
|
||||
}
|
||||
|
||||
private void abandon(Player player, String[] args) throws SQLException {
|
||||
if (args.length != 2) {
|
||||
player.sendMessage(plugin.messages().text("&eUsage: /claim abandon <id>"));
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.librewiki.coalgov.command;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.model.Claim;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class ShowPropertyLinesCommand implements CommandExecutor {
|
||||
private final CoalGovPlugin plugin;
|
||||
|
||||
public ShowPropertyLinesCommand(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 {
|
||||
Optional<Claim> claim = plugin.claimService().claimAt(player.getLocation());
|
||||
if (claim.isEmpty()) {
|
||||
player.sendMessage(plugin.messages().text("&eYou are not standing inside a claim."));
|
||||
return true;
|
||||
}
|
||||
plugin.claimVisualService().show(player, claim.get());
|
||||
player.sendMessage(plugin.messages().text("&aShowing property lines for claim #" + claim.get().id() + "."));
|
||||
} catch (SQLException exception) {
|
||||
plugin.getLogger().warning("Show property lines failed: " + exception.getMessage());
|
||||
player.sendMessage(plugin.messages().text("&cClaim registry unavailable."));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.librewiki.coalgov.listener;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.model.Claim;
|
||||
import com.librewiki.coalgov.model.ClaimPermission;
|
||||
import org.bukkit.entity.Animals;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class AnimalProtectionListener implements Listener {
|
||||
private final CoalGovPlugin plugin;
|
||||
|
||||
public AnimalProtectionListener(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onEntityDamage(EntityDamageByEntityEvent event) {
|
||||
if (!(event.getEntity() instanceof Animals)) {
|
||||
return;
|
||||
}
|
||||
Player player = attackingPlayer(event.getDamager());
|
||||
if (player == null || plugin.hasAdminBypass(player)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Optional<Claim> claim = plugin.claimService().claimAt(event.getEntity().getLocation());
|
||||
if (claim.isEmpty() || plugin.claimService().canUse(player.getUniqueId(), claim.get(), ClaimPermission.ANIMAL)) {
|
||||
return;
|
||||
}
|
||||
event.setCancelled(true);
|
||||
player.sendMessage(plugin.messages().text("&cYou do not have animal access on this claim."));
|
||||
} catch (SQLException exception) {
|
||||
event.setCancelled(true);
|
||||
plugin.getLogger().warning("Animal protection check failed: " + exception.getMessage());
|
||||
player.sendMessage(plugin.messages().text("&cLand registry unavailable. Try again later."));
|
||||
}
|
||||
}
|
||||
|
||||
private Player attackingPlayer(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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.librewiki.coalgov.listener;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.model.Claim;
|
||||
import com.librewiki.coalgov.model.ClaimPermission;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class PropertyInteractListener implements Listener {
|
||||
private final CoalGovPlugin plugin;
|
||||
|
||||
public PropertyInteractListener(CoalGovPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onPlayerInteract(PlayerInteractEvent event) {
|
||||
if (plugin.hasAdminBypass(event.getPlayer()) || event.getAction() != Action.RIGHT_CLICK_BLOCK) {
|
||||
return;
|
||||
}
|
||||
Block block = event.getClickedBlock();
|
||||
if (block == null || !block.getType().isInteractable()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Optional<Claim> claim = plugin.claimService().claimAt(block.getLocation());
|
||||
if (claim.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
ClaimPermission permission = isContainer(block) ? ClaimPermission.CONTAINER : ClaimPermission.INTERACT;
|
||||
if (plugin.claimService().canUse(event.getPlayer().getUniqueId(), claim.get(), permission)) {
|
||||
return;
|
||||
}
|
||||
event.setCancelled(true);
|
||||
event.getPlayer().sendMessage(plugin.messages().text("&cYou do not have " + permission.label() + " access on this claim."));
|
||||
} catch (SQLException exception) {
|
||||
event.setCancelled(true);
|
||||
plugin.getLogger().warning("Property interaction check failed: " + exception.getMessage());
|
||||
event.getPlayer().sendMessage(plugin.messages().text("&cLand registry unavailable. Try again later."));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isContainer(Block block) {
|
||||
return block.getState() instanceof InventoryHolder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.librewiki.coalgov.model;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public enum ClaimPermission {
|
||||
BUILD,
|
||||
INTERACT,
|
||||
CONTAINER,
|
||||
ANIMAL,
|
||||
MANAGE;
|
||||
|
||||
public static Optional<ClaimPermission> parse(String raw) {
|
||||
return Arrays.stream(values())
|
||||
.filter(permission -> permission.name().equalsIgnoreCase(raw))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
public static List<ClaimPermission> grantSet(String raw) {
|
||||
if (raw.equalsIgnoreCase("all")) {
|
||||
return List.of(values());
|
||||
}
|
||||
return parse(raw).map(List::of).orElse(List.of());
|
||||
}
|
||||
|
||||
public String label() {
|
||||
return name().toLowerCase();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.librewiki.coalgov.model;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record ClaimPermissionGrant(
|
||||
long claimId,
|
||||
UUID playerUuid,
|
||||
ClaimPermission permission,
|
||||
long grantedAt
|
||||
) {
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.librewiki.coalgov.service;
|
||||
|
||||
import com.librewiki.coalgov.CoalGovPlugin;
|
||||
import com.librewiki.coalgov.model.Claim;
|
||||
import com.librewiki.coalgov.model.ClaimPermission;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
@@ -25,7 +26,7 @@ public final class BuildProtectionService {
|
||||
if (claim.isEmpty()) {
|
||||
return BuildDecision.deny("This land is unclaimed and protected by the Ministry.");
|
||||
}
|
||||
if (!claim.get().ownerUuid().equals(player.getUniqueId())) {
|
||||
if (!claimService.canUse(player.getUniqueId(), claim.get(), ClaimPermission.BUILD)) {
|
||||
return BuildDecision.deny("This claim belongs to another citizen.");
|
||||
}
|
||||
return BuildDecision.allow();
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.librewiki.coalgov.service;
|
||||
|
||||
import com.librewiki.coalgov.model.Claim;
|
||||
import com.librewiki.coalgov.model.ClaimPoint;
|
||||
import com.librewiki.coalgov.model.ClaimPermission;
|
||||
import com.librewiki.coalgov.model.ClaimPermissionGrant;
|
||||
import com.librewiki.coalgov.storage.ClaimRepository;
|
||||
import com.librewiki.coalgov.util.CoalMoney;
|
||||
import com.librewiki.coalgov.util.Cuboid2D;
|
||||
@@ -142,6 +144,66 @@ public final class ClaimService {
|
||||
return TransferResult.success(claim.get());
|
||||
}
|
||||
|
||||
public boolean canUse(UUID playerUuid, Claim claim, ClaimPermission permission) throws SQLException {
|
||||
if (claim.ownerUuid().equals(playerUuid)) {
|
||||
return true;
|
||||
}
|
||||
return repository.hasPermission(claim.id(), playerUuid, permission)
|
||||
|| repository.hasPermission(claim.id(), playerUuid, ClaimPermission.MANAGE);
|
||||
}
|
||||
|
||||
public PermissionResult grantPermission(UUID actor, long claimId, UUID target, ClaimPermission permission) throws SQLException {
|
||||
Optional<Claim> claim = findManageableClaim(actor, claimId);
|
||||
if (claim.isEmpty()) {
|
||||
return PermissionResult.fail("No owned or manageable claim with that id.");
|
||||
}
|
||||
if (claim.get().ownerUuid().equals(target)) {
|
||||
return PermissionResult.fail("The owner already has all permissions.");
|
||||
}
|
||||
repository.grantPermission(claimId, target, permission);
|
||||
return PermissionResult.ok();
|
||||
}
|
||||
|
||||
public PermissionResult revokePermission(UUID actor, long claimId, UUID target, ClaimPermission permission) throws SQLException {
|
||||
Optional<Claim> claim = findManageableClaim(actor, claimId);
|
||||
if (claim.isEmpty()) {
|
||||
return PermissionResult.fail("No owned or manageable claim with that id.");
|
||||
}
|
||||
return repository.revokePermission(claimId, target, permission)
|
||||
? PermissionResult.ok()
|
||||
: PermissionResult.fail("That player did not have that permission.");
|
||||
}
|
||||
|
||||
public PermissionResult revokeAllPermissions(UUID actor, long claimId, UUID target) throws SQLException {
|
||||
Optional<Claim> claim = findManageableClaim(actor, claimId);
|
||||
if (claim.isEmpty()) {
|
||||
return PermissionResult.fail("No owned or manageable claim with that id.");
|
||||
}
|
||||
return repository.revokeAllPermissions(claimId, target)
|
||||
? PermissionResult.ok()
|
||||
: PermissionResult.fail("That player had no permissions on that claim.");
|
||||
}
|
||||
|
||||
public List<ClaimPermissionGrant> permissions(UUID actor, long claimId) throws SQLException {
|
||||
Optional<Claim> claim = findManageableClaim(actor, claimId);
|
||||
if (claim.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
return repository.permissions(claimId);
|
||||
}
|
||||
|
||||
private Optional<Claim> findManageableClaim(UUID actor, long claimId) throws SQLException {
|
||||
Optional<Claim> owned = findOwnedById(actor, claimId);
|
||||
if (owned.isPresent()) {
|
||||
return owned;
|
||||
}
|
||||
Optional<Claim> claim = repository.findById(claimId);
|
||||
if (claim.isPresent() && repository.hasPermission(claimId, actor, ClaimPermission.MANAGE)) {
|
||||
return claim;
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public long claimPurchaseCost(Claim claim) {
|
||||
if (claim.purchaseCost() >= 0L) {
|
||||
return claim.purchaseCost();
|
||||
@@ -222,4 +284,14 @@ public final class ClaimService {
|
||||
return new TransferResult(true, "", claim);
|
||||
}
|
||||
}
|
||||
|
||||
public record PermissionResult(boolean success, String message) {
|
||||
public static PermissionResult fail(String message) {
|
||||
return new PermissionResult(false, message);
|
||||
}
|
||||
|
||||
public static PermissionResult ok() {
|
||||
return new PermissionResult(true, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.librewiki.coalgov.storage;
|
||||
|
||||
import com.librewiki.coalgov.model.Claim;
|
||||
import com.librewiki.coalgov.model.ClaimPoint;
|
||||
import com.librewiki.coalgov.model.ClaimPermission;
|
||||
import com.librewiki.coalgov.model.ClaimPermissionGrant;
|
||||
import com.librewiki.coalgov.util.Cuboid2D;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
@@ -69,6 +71,11 @@ public final class ClaimRepository {
|
||||
deleteVertices.setLong(1, id);
|
||||
deleteVertices.executeUpdate();
|
||||
}
|
||||
try (PreparedStatement deletePermissions = database.connection().prepareStatement(
|
||||
"DELETE FROM claim_permissions WHERE claim_id = ?")) {
|
||||
deletePermissions.setLong(1, id);
|
||||
deletePermissions.executeUpdate();
|
||||
}
|
||||
try (PreparedStatement delete = database.connection().prepareStatement(
|
||||
"DELETE FROM claims WHERE id = ? AND owner_uuid = ?")) {
|
||||
delete.setLong(1, id);
|
||||
@@ -134,13 +141,109 @@ public final class ClaimRepository {
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<Claim> findById(long id) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement(
|
||||
"SELECT * FROM claims WHERE id = ?")) {
|
||||
statement.setLong(1, id);
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
return result.next() ? Optional.of(map(result)) : Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean transferOwned(UUID owner, long id, UUID newOwner) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement(
|
||||
"UPDATE claims SET owner_uuid = ? WHERE id = ? AND owner_uuid = ?")) {
|
||||
statement.setString(1, newOwner.toString());
|
||||
statement.setLong(2, id);
|
||||
statement.setString(3, owner.toString());
|
||||
return statement.executeUpdate() == 1;
|
||||
boolean transferred = statement.executeUpdate() == 1;
|
||||
if (transferred) {
|
||||
clearPermissions(id);
|
||||
}
|
||||
return transferred;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT(claim_id, player_uuid, permission) DO UPDATE SET granted_at = excluded.granted_at
|
||||
""")) {
|
||||
statement.setLong(1, claimId);
|
||||
statement.setString(2, playerUuid.toString());
|
||||
statement.setString(3, permission.name());
|
||||
statement.setLong(4, System.currentTimeMillis());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean revokePermission(long claimId, UUID playerUuid, ClaimPermission permission) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
DELETE FROM claim_permissions
|
||||
WHERE claim_id = ? AND player_uuid = ? AND permission = ?
|
||||
""")) {
|
||||
statement.setLong(1, claimId);
|
||||
statement.setString(2, playerUuid.toString());
|
||||
statement.setString(3, permission.name());
|
||||
return statement.executeUpdate() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean revokeAllPermissions(long claimId, UUID playerUuid) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
DELETE FROM claim_permissions
|
||||
WHERE claim_id = ? AND player_uuid = ?
|
||||
""")) {
|
||||
statement.setLong(1, claimId);
|
||||
statement.setString(2, playerUuid.toString());
|
||||
return statement.executeUpdate() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasPermission(long claimId, UUID playerUuid, ClaimPermission permission) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
SELECT 1 FROM claim_permissions
|
||||
WHERE claim_id = ? AND player_uuid = ? AND permission = ?
|
||||
LIMIT 1
|
||||
""")) {
|
||||
statement.setLong(1, claimId);
|
||||
statement.setString(2, playerUuid.toString());
|
||||
statement.setString(3, permission.name());
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
return result.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<ClaimPermissionGrant> permissions(long claimId) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement("""
|
||||
SELECT * FROM claim_permissions
|
||||
WHERE claim_id = ?
|
||||
ORDER BY player_uuid, permission
|
||||
""")) {
|
||||
statement.setLong(1, claimId);
|
||||
List<ClaimPermissionGrant> grants = new ArrayList<>();
|
||||
try (ResultSet result = statement.executeQuery()) {
|
||||
while (result.next()) {
|
||||
grants.add(new ClaimPermissionGrant(
|
||||
result.getLong("claim_id"),
|
||||
UUID.fromString(result.getString("player_uuid")),
|
||||
ClaimPermission.valueOf(result.getString("permission")),
|
||||
result.getLong("granted_at")
|
||||
));
|
||||
}
|
||||
}
|
||||
return grants;
|
||||
}
|
||||
}
|
||||
|
||||
private void clearPermissions(long claimId) throws SQLException {
|
||||
try (PreparedStatement statement = database.connection().prepareStatement(
|
||||
"DELETE FROM claim_permissions WHERE claim_id = ?")) {
|
||||
statement.setLong(1, claimId);
|
||||
statement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,15 @@ public final class Database {
|
||||
PRIMARY KEY (claim_id, vertex_order)
|
||||
)
|
||||
""");
|
||||
statement.execute("""
|
||||
CREATE TABLE IF NOT EXISTS claim_permissions (
|
||||
claim_id INTEGER NOT NULL,
|
||||
player_uuid TEXT NOT NULL,
|
||||
permission TEXT NOT NULL,
|
||||
granted_at INTEGER NOT NULL,
|
||||
PRIMARY KEY (claim_id, player_uuid, permission)
|
||||
)
|
||||
""");
|
||||
statement.execute("""
|
||||
CREATE TABLE IF NOT EXISTS land_regions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -204,6 +213,8 @@ public final class Database {
|
||||
""");
|
||||
statement.execute("CREATE INDEX IF NOT EXISTS idx_claims_location ON claims(world, x1, z1, x2, z2)");
|
||||
statement.execute("CREATE INDEX IF NOT EXISTS idx_claim_vertices_claim ON claim_vertices(claim_id)");
|
||||
statement.execute("CREATE INDEX IF NOT EXISTS idx_claim_permissions_claim ON claim_permissions(claim_id)");
|
||||
statement.execute("CREATE INDEX IF NOT EXISTS idx_claim_permissions_player ON claim_permissions(player_uuid)");
|
||||
statement.execute("CREATE INDEX IF NOT EXISTS idx_land_location ON land_regions(world, x1, z1, x2, z2)");
|
||||
statement.execute("CREATE INDEX IF NOT EXISTS idx_permits_owner ON permits(owner_uuid, permit_type, region_name)");
|
||||
statement.execute("CREATE INDEX IF NOT EXISTS idx_fines_player ON fines(player_uuid, paid_at)");
|
||||
|
||||
@@ -13,7 +13,7 @@ commands:
|
||||
permission: coalgov.coal
|
||||
claim:
|
||||
description: CoalGov claim commands.
|
||||
usage: /claim <wand|buy|info|list|show|appraise|sell|transfer|abandon>
|
||||
usage: /claim <wand|buy|info|list|show|appraise|sell|transfer|abandon|trust|untrust|permissions>
|
||||
permission: coalgov.claim
|
||||
permit:
|
||||
description: CoalGov permit commands.
|
||||
@@ -30,6 +30,10 @@ commands:
|
||||
description: Receive a CoalGov divining rod.
|
||||
usage: /diviningrod
|
||||
permission: coalgov.diviningrod
|
||||
showpropertylines:
|
||||
description: Show the property lines for the claim you are standing in.
|
||||
usage: /showpropertylines
|
||||
permission: coalgov.claim
|
||||
police:
|
||||
description: CoalGov police commands.
|
||||
usage: /police <fine>
|
||||
|
||||
Reference in New Issue
Block a user