Skip to content

Commit 6317fdb

Browse files
cryptobenchclaude
andcommitted
Add claim buffer zone and admin unclaim commands
- Add 2-chunk buffer zone around claims to prevent griefing - Players cannot claim within buffer distance of other players' claims - Buffer is configurable via /easyclaims admin set buffer <n> - Add /easyclaims admin unclaim to remove claim at current location - Add /easyclaims admin unclaim <player> to remove all player claims - Update README and CLAUDE.md documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 8274e02 commit 6317fdb

6 files changed

Lines changed: 151 additions & 11 deletions

File tree

CLAUDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# EasyClaims Plugin - Development Context
22

33
## Project Overview
4-
A chunk-based land claiming plugin for Hytale servers with playtime-based limits and trust system.
4+
A chunk-based land claiming plugin for Hytale servers with playtime-based limits, trust system, and anti-griefing buffer zones.
55

66
## How to Explore the Hytale Server API
77

@@ -336,7 +336,7 @@ Located at `Server/permissions.json`:
336336
| Permission | Description |
337337
|------------|-------------|
338338
| `easyclaims.use` | Basic access to /easyclaims command |
339-
| `easyclaims.admin` | Admin commands (config, set, reload) |
339+
| `easyclaims.admin` | Admin commands (config, set, reload, unclaim) |
340340

341341
## Messages (No Minecraft Color Codes!)
342342
```java

README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ That's it! Your builds are now protected.
1919
- **Playtime Rewards** - Play longer, claim more land
2020
- **Share With Friends** - Trust players with different permission levels
2121
- **Full Protection** - Blocks breaking, placing, and interactions from strangers
22+
- **Anti-Griefing Buffer** - 2-chunk buffer zone prevents others from claiming too close to you
2223

2324
---
2425

@@ -59,12 +60,15 @@ All commands use `/easyclaims`.
5960
| `/easyclaims admin config` | Show current settings |
6061
| `/easyclaims admin set <key> <value>` | Change a setting (saves immediately) |
6162
| `/easyclaims admin reload` | Reload config from file |
63+
| `/easyclaims admin unclaim` | Remove claim at your location (any owner) |
64+
| `/easyclaims admin unclaim <player>` | Remove ALL claims from a player |
6265

63-
**Example:**
66+
**Settings you can change:**
6467
```
65-
/easyclaims admin set max 100
66-
/easyclaims admin set starting 6
67-
/easyclaims admin set perhour 3
68+
/easyclaims admin set starting 6 # Starting claim slots for new players
69+
/easyclaims admin set perhour 3 # Extra claims earned per hour
70+
/easyclaims admin set max 100 # Maximum claims any player can have
71+
/easyclaims admin set buffer 2 # Buffer zone in chunks (0 = disabled)
6872
```
6973

7074
---
@@ -93,6 +97,7 @@ When you trust someone, you can choose how much access they get:
9397
- You start with **4 claim slots** and earn more by playing
9498
- Open your **world map (M)** to see claims highlighted in color
9599
- Your claims show in your unique color, others show in theirs
100+
- **Buffer zone**: Other players can't claim within 2 chunks of your claims (prevents griefing)
96101

97102
### Earning More Claims
98103

@@ -142,6 +147,7 @@ Try closing and reopening your map, or reconnect to the server.
142147
### Can't claim?
143148
- Check if you have available slots: `/easyclaims playtime`
144149
- Make sure the chunk isn't already claimed by someone else
150+
- You might be too close to another player's claim (2-chunk buffer zone)
145151

146152
---
147153

src/main/java/com/easyclaims/commands/EasyClaimsCommand.java

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ protected void execute(@Nonnull CommandContext ctx,
127127
showHelp(playerData);
128128
break;
129129
case "admin":
130-
handleAdmin(playerData, args);
130+
handleAdmin(playerData, args, store, playerRef, world);
131131
break;
132132
default:
133133
playerData.sendMessage(Message.raw("Unknown subcommand: " + subcommand).color(RED));
@@ -136,7 +136,7 @@ protected void execute(@Nonnull CommandContext ctx,
136136
}
137137

138138
// ===== ADMIN =====
139-
private void handleAdmin(PlayerRef playerData, String[] args) {
139+
private void handleAdmin(PlayerRef playerData, String[] args, Store<EntityStore> store, Ref<EntityStore> playerRef, World world) {
140140
if (!hasAdminPermission(playerData)) {
141141
return;
142142
}
@@ -161,6 +161,9 @@ private void handleAdmin(PlayerRef playerData, String[] args) {
161161
case "reload":
162162
handleReload(playerData);
163163
break;
164+
case "unclaim":
165+
handleAdminUnclaim(playerData, arg1, store, playerRef, world);
166+
break;
164167
default:
165168
playerData.sendMessage(Message.raw("Unknown admin command: " + adminSubcmd).color(RED));
166169
showAdminHelp(playerData);
@@ -172,8 +175,10 @@ private void showAdminHelp(PlayerRef playerData) {
172175
playerData.sendMessage(Message.raw("/easyclaims admin config - Show current settings").color(GRAY));
173176
playerData.sendMessage(Message.raw("/easyclaims admin set <key> <value> - Change a setting").color(GRAY));
174177
playerData.sendMessage(Message.raw("/easyclaims admin reload - Reload config from file").color(GRAY));
178+
playerData.sendMessage(Message.raw("/easyclaims admin unclaim - Remove claim at your location").color(GRAY));
179+
playerData.sendMessage(Message.raw("/easyclaims admin unclaim <player> - Remove all claims from player").color(GRAY));
175180
playerData.sendMessage(Message.raw("").color(GRAY));
176-
playerData.sendMessage(Message.raw("Settings: starting, perhour, max").color(AQUA));
181+
playerData.sendMessage(Message.raw("Settings: starting, perhour, max, buffer").color(AQUA));
177182
}
178183

179184
// ===== CLAIM =====
@@ -211,6 +216,11 @@ private void handleClaim(PlayerRef playerData, Store<EntityStore> store, Ref<Ent
211216
playerData.sendMessage(Message.raw("Claim limit reached! (" + currentClaims + "/" + maxClaims + ")").color(RED));
212217
playerData.sendMessage(Message.raw("Play more to earn additional claims.").color(GRAY));
213218
break;
219+
case TOO_CLOSE_TO_OTHER_CLAIM:
220+
playerData.sendMessage(Message.raw("Cannot claim here - too close to another player's claim!").color(RED));
221+
int buffer = plugin.getPluginConfig().getClaimBufferSize();
222+
playerData.sendMessage(Message.raw("Claims must be at least " + buffer + " chunks away from other players.").color(GRAY));
223+
break;
214224
}
215225
}
216226

@@ -430,11 +440,15 @@ private void showConfig(PlayerRef playerData) {
430440
playerData.sendMessage(Message.raw("New players start with: " + config.getStartingClaims() + " claims").color(AQUA));
431441
playerData.sendMessage(Message.raw("Players earn: " + config.getClaimsPerHour() + " extra claims per hour played").color(AQUA));
432442
playerData.sendMessage(Message.raw("Maximum claims allowed: " + config.getMaxClaims()).color(AQUA));
443+
int buffer = config.getClaimBufferSize();
444+
String bufferText = buffer > 0 ? buffer + " chunks" : "disabled";
445+
playerData.sendMessage(Message.raw("Claim buffer zone: " + bufferText).color(AQUA));
433446
playerData.sendMessage(Message.raw("").color(GRAY));
434447
playerData.sendMessage(Message.raw("To change these settings:").color(GRAY));
435448
playerData.sendMessage(Message.raw(" /easyclaims admin set starting <number>").color(YELLOW));
436449
playerData.sendMessage(Message.raw(" /easyclaims admin set perhour <number>").color(YELLOW));
437450
playerData.sendMessage(Message.raw(" /easyclaims admin set max <number>").color(YELLOW));
451+
playerData.sendMessage(Message.raw(" /easyclaims admin set buffer <number>").color(YELLOW));
438452
}
439453

440454
private void handleSet(PlayerRef playerData, String key, String valueStr) {
@@ -446,6 +460,8 @@ private void handleSet(PlayerRef playerData, String key, String valueStr) {
446460
playerData.sendMessage(Message.raw(" Extra claims earned per hour played").color(GRAY));
447461
playerData.sendMessage(Message.raw(" /easyclaims admin set max <number>").color(YELLOW));
448462
playerData.sendMessage(Message.raw(" Maximum claims any player can have").color(GRAY));
463+
playerData.sendMessage(Message.raw(" /easyclaims admin set buffer <number>").color(YELLOW));
464+
playerData.sendMessage(Message.raw(" Buffer zone in chunks around claims (0 = disabled)").color(GRAY));
449465
return;
450466
}
451467

@@ -474,8 +490,18 @@ private void handleSet(PlayerRef playerData, String key, String valueStr) {
474490
config.setMaxClaims(value);
475491
playerData.sendMessage(Message.raw("Maximum claims is now " + value + "!").color(GREEN));
476492
break;
493+
case "buffer":
494+
case "buffersize":
495+
case "claimbuffer":
496+
config.setClaimBufferSize(value);
497+
if (value == 0) {
498+
playerData.sendMessage(Message.raw("Claim buffer zone disabled!").color(GREEN));
499+
} else {
500+
playerData.sendMessage(Message.raw("Claim buffer zone set to " + value + " chunks!").color(GREEN));
501+
}
502+
break;
477503
default:
478-
playerData.sendMessage(Message.raw("Unknown setting! Try: starting, perhour, or max").color(RED));
504+
playerData.sendMessage(Message.raw("Unknown setting! Try: starting, perhour, max, or buffer").color(RED));
479505
}
480506
}
481507

@@ -484,4 +510,58 @@ private void handleReload(PlayerRef playerData) {
484510
playerData.sendMessage(Message.raw("Configuration reloaded!").color(GREEN));
485511
showConfig(playerData);
486512
}
513+
514+
// ===== ADMIN: UNCLAIM =====
515+
private void handleAdminUnclaim(PlayerRef playerData, String playerInput, Store<EntityStore> store, Ref<EntityStore> playerRef, World world) {
516+
if (playerInput == null || playerInput.isEmpty()) {
517+
// Unclaim chunk at current location
518+
TransformComponent transform = store.getComponent(playerRef, TransformComponent.getComponentType());
519+
Vector3d position = transform.getPosition();
520+
String worldName = world.getName();
521+
522+
int chunkX = ChunkUtil.chunkCoordinate((int) position.getX());
523+
int chunkZ = ChunkUtil.chunkCoordinate((int) position.getZ());
524+
525+
UUID owner = plugin.getClaimStorage().getClaimOwner(worldName, chunkX, chunkZ);
526+
if (owner == null) {
527+
playerData.sendMessage(Message.raw("This chunk is not claimed.").color(YELLOW));
528+
return;
529+
}
530+
531+
String ownerName = plugin.getClaimStorage().getPlayerName(owner);
532+
plugin.getClaimStorage().removeClaim(owner, worldName, chunkX, chunkZ);
533+
playerData.sendMessage(Message.raw("Removed claim [" + chunkX + ", " + chunkZ + "] from " + ownerName).color(GREEN));
534+
plugin.refreshWorldMapChunk(worldName, chunkX, chunkZ);
535+
} else {
536+
// Unclaim all chunks from a specific player
537+
UUID targetId = null;
538+
String targetName = playerInput;
539+
540+
// Try online player first
541+
PlayerRef targetPlayer = Universe.get().getPlayerByUsername(playerInput, NameMatching.EXACT_IGNORE_CASE);
542+
if (targetPlayer != null) {
543+
targetId = targetPlayer.getUuid();
544+
targetName = targetPlayer.getUsername();
545+
} else {
546+
// Try UUID
547+
try {
548+
targetId = UUID.fromString(playerInput);
549+
targetName = plugin.getClaimStorage().getPlayerName(targetId);
550+
} catch (IllegalArgumentException e) {
551+
playerData.sendMessage(Message.raw("Player not found: " + playerInput).color(RED));
552+
return;
553+
}
554+
}
555+
556+
int count = plugin.getClaimManager().unclaimAll(targetId);
557+
if (count > 0) {
558+
playerData.sendMessage(Message.raw("Removed " + count + " claim(s) from " + targetName).color(GREEN));
559+
for (String worldName : EasyClaims.WORLDS.keySet()) {
560+
plugin.refreshWorldMap(worldName);
561+
}
562+
} else {
563+
playerData.sendMessage(Message.raw(targetName + " doesn't have any claims.").color(YELLOW));
564+
}
565+
}
566+
}
487567
}

src/main/java/com/easyclaims/config/PluginConfig.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ public int getPlaytimeSaveInterval() {
113113
return config.playtimeSaveInterval;
114114
}
115115

116+
public int getClaimBufferSize() {
117+
return config.claimBufferSize;
118+
}
119+
116120
// ===== SETTERS (auto-save) =====
117121

118122
public void setClaimsPerHour(int value) {
@@ -135,6 +139,11 @@ public void setPlaytimeSaveInterval(int value) {
135139
save();
136140
}
137141

142+
public void setClaimBufferSize(int value) {
143+
config.claimBufferSize = Math.max(0, value); // 0 = disabled
144+
save();
145+
}
146+
138147
// ===== LEGACY GETTERS (for compatibility) =====
139148

140149
/** @deprecated Use getClaimsPerHour() */
@@ -194,5 +203,6 @@ private static class ConfigData {
194203
int claimsPerHour = 2;
195204
int maxClaims = 50;
196205
int playtimeSaveInterval = 60;
206+
int claimBufferSize = 2; // Buffer zone in chunks around claims where others can't claim
197207
}
198208
}

src/main/java/com/easyclaims/data/ClaimStorage.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,39 @@ public boolean isClaimed(String world, int chunkX, int chunkZ) {
355355
return getClaimOwner(world, chunkX, chunkZ) != null;
356356
}
357357

358+
/**
359+
* Finds claims by OTHER players within a radius of a target chunk.
360+
* Returns the first found claim owner that isn't the excluded player, or null if none found.
361+
*
362+
* @param world The world name
363+
* @param centerChunkX Center chunk X coordinate
364+
* @param centerChunkZ Center chunk Z coordinate
365+
* @param radius Radius in chunks to search (e.g., 2 = check 5x5 area)
366+
* @param excludePlayerId Player whose claims should be ignored (the one trying to claim)
367+
* @return UUID of first other player with a claim in range, or null if none
368+
*/
369+
public UUID findNearbyClaimByOtherPlayer(String world, int centerChunkX, int centerChunkZ,
370+
int radius, UUID excludePlayerId) {
371+
Map<String, UUID> worldClaims = claimIndex.get(world);
372+
if (worldClaims == null || radius <= 0) {
373+
return null;
374+
}
375+
376+
for (int dx = -radius; dx <= radius; dx++) {
377+
for (int dz = -radius; dz <= radius; dz++) {
378+
int checkX = centerChunkX + dx;
379+
int checkZ = centerChunkZ + dz;
380+
String key = ChunkUtil.chunkKey(checkX, checkZ);
381+
UUID owner = worldClaims.get(key);
382+
383+
if (owner != null && !owner.equals(excludePlayerId)) {
384+
return owner; // Found a claim by another player
385+
}
386+
}
387+
}
388+
return null; // No other player claims in range
389+
}
390+
358391
public void saveAll() {
359392
for (UUID playerId : cache.keySet()) {
360393
savePlayerClaims(playerId);

src/main/java/com/easyclaims/managers/ClaimManager.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,16 @@ public ClaimResult claimChunk(UUID playerId, String world, double x, double z) {
5959
return ClaimResult.CLAIMED_BY_OTHER;
6060
}
6161

62+
// Check buffer zone - is this chunk too close to another player's claim?
63+
int bufferSize = config.getClaimBufferSize();
64+
if (bufferSize > 0) {
65+
UUID nearbyOwner = claimStorage.findNearbyClaimByOtherPlayer(
66+
world, chunkX, chunkZ, bufferSize, playerId);
67+
if (nearbyOwner != null) {
68+
return ClaimResult.TOO_CLOSE_TO_OTHER_CLAIM;
69+
}
70+
}
71+
6272
// Check claim limit
6373
PlayerClaims claims = claimStorage.getPlayerClaims(playerId);
6474
PlaytimeData playtime = playtimeStorage.getPlaytime(playerId);
@@ -242,6 +252,7 @@ public enum ClaimResult {
242252
SUCCESS,
243253
ALREADY_OWN,
244254
CLAIMED_BY_OTHER,
245-
LIMIT_REACHED
255+
LIMIT_REACHED,
256+
TOO_CLOSE_TO_OTHER_CLAIM
246257
}
247258
}

0 commit comments

Comments
 (0)