Skip to content

Commit 3f74c73

Browse files
cryptobenchclaude
andcommitted
Add PvP protection system and admin claims
Major new features for server administrators to create protected safe zones and configure server-wide PvP behavior. ## PvP Protection - Add PvPProtectionSystem that blocks player-vs-player damage in PvP-disabled zones - Configurable server mode: pvpinclaims true (PvP server) or false (PvE server) - Rate-limited "PvP is disabled here" message to avoid spam - Map shows safe zones with green tint and "[Safe]" indicator ## Admin Claims - New admin claim system for spawn, shops, and community areas - Create with /easyclaims admin claim [name] (e.g., "Spawn") - Admin claims use reserved UUID (all zeros) and bypass buffer zones - PvP disabled by default in admin claims - Shown in light blue color on the map - Custom display names appear on map instead of owner name ## Admin Commands - /easyclaims admin claim [name] - Create admin claim at current location - /easyclaims admin pvp [on/off] - Toggle PvP in any claim - /easyclaims admin set pvpinclaims true/false - Server PvP mode ## Admin Bypass - Admins with easyclaims.admin permission now bypass: - Claim limits and buffer zones when claiming chunks - All protection systems (block break/place/damage/use/pickup) ## Config Changes - claimsPerHour now supports decimal values (e.g., 0.25 = 1 claim every 4 hours) - New pvpInPlayerClaims setting with migration from deprecated allowPlayerPvpToggle - formatClaimRate() helper for user-friendly display ## Data Model Updates - Claim class extended with pvpEnabled, adminClaim, and displayName fields - ClaimStorage updated with getClaimAt() method and migration support - Full backward compatibility with existing claim data Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 0f78cd1 commit 3f74c73

29 files changed

Lines changed: 878 additions & 80 deletions

README.md

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ That's it! Your builds are now protected.
2323
- **Share With Friends** - Trust players with different permission levels
2424
- **Full Protection** - Blocks breaking, placing, and interactions from strangers
2525
- **Anti-Griefing Buffer** - 2-chunk buffer zone prevents others from claiming too close to you
26+
- **PvP Protection** - Configure PvP rules per-server or per-claim (safe zones)
27+
- **Admin Claims** - Server-owned protected areas for spawn, shops, etc.
2628

2729
---
2830

@@ -75,15 +77,20 @@ All commands use `/easyclaims`.
7577
| `/easyclaims admin reload` | Reload config from file |
7678
| `/easyclaims admin unclaim` | Remove claim at your location (any owner) |
7779
| `/easyclaims admin unclaim <player>` | Remove ALL claims from a player |
80+
| `/easyclaims admin claim [name]` | Create an admin claim (e.g., "Spawn") |
81+
| `/easyclaims admin pvp [on/off]` | Toggle PvP in the current claim |
7882

7983
**Settings you can change:**
8084
```
81-
/easyclaims admin set starting 6 # Starting claim slots for new players
82-
/easyclaims admin set perhour 3 # Extra claims earned per hour
83-
/easyclaims admin set max 100 # Maximum claims any player can have
84-
/easyclaims admin set buffer 2 # Buffer zone in chunks (0 = disabled)
85+
/easyclaims admin set starting 6 # Starting claim slots for new players
86+
/easyclaims admin set perhour 0.25 # Claims earned per hour (0.25 = 1 every 4 hours)
87+
/easyclaims admin set max 100 # Maximum claims any player can have
88+
/easyclaims admin set buffer 2 # Buffer zone in chunks (0 = disabled)
89+
/easyclaims admin set pvpinclaims false # PvP in player claims (true=PvP, false=PvE)
8590
```
8691

92+
**Admin Bypass:** Admins with `easyclaims.admin` permission bypass all protection checks and claim limits.
93+
8794
---
8895

8996
## Trust Levels Explained
@@ -111,6 +118,7 @@ When you trust someone, you can choose how much access they get:
111118
- Open your **world map (M)** to see claims highlighted in color
112119
- Your claims show in your unique color, others show in theirs
113120
- **Buffer zone**: Other players can't claim within 2 chunks of your claims (prevents griefing)
121+
- **Admin claims** (light blue) are server-protected areas like spawn
114122

115123
### Earning More Claims
116124

@@ -125,6 +133,27 @@ The longer you play, the more land you can claim:
125133

126134
Use `/easyclaims playtime` to check your progress!
127135

136+
### PvP Protection
137+
138+
Server owners can configure PvP behavior:
139+
140+
- **PvP Servers** (`pvpinclaims true`) - Fighting is allowed everywhere including player claims
141+
- **PvE Servers** (`pvpinclaims false`) - Player claims become safe zones where PvP is disabled
142+
143+
On the map, safe zones show with a green tint and `[Safe]` label.
144+
145+
### Admin Claims
146+
147+
Admin claims are special server-owned protected areas for spawn, shops, arenas, etc.
148+
149+
- Created with `/easyclaims admin claim [name]` (e.g., `/easyclaims admin claim Spawn`)
150+
- PvP disabled by default (toggle with `/easyclaims admin pvp on/off`)
151+
- Shown in **light blue** on the map
152+
- Not counted against any player's claim limit
153+
- Bypass buffer zone restrictions
154+
155+
Use admin claims to create protected community areas!
156+
128157
---
129158

130159
## Installation

src/main/java/com/easyclaims/EasyClaims.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import com.easyclaims.systems.BlockUseProtectionSystem;
1717
import com.easyclaims.systems.ClaimTitleSystem;
1818
import com.easyclaims.systems.ItemPickupProtectionSystem;
19+
import com.easyclaims.systems.PvPProtectionSystem;
1920
import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent;
2021
import com.hypixel.hytale.server.core.event.events.player.PlayerDisconnectEvent;
2122
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
@@ -73,7 +74,7 @@ public void setup() {
7374
playtimeStorage = new PlaytimeStorage(getDataDirectory());
7475

7576
// Initialize static accessor for map system
76-
EasyClaimsAccess.init(claimStorage);
77+
EasyClaimsAccess.init(claimStorage, config);
7778

7879
// Initialize managers
7980
claimManager = new ClaimManager(claimStorage, playtimeStorage, config, blockGroups);
@@ -114,6 +115,7 @@ public void setup() {
114115
getEntityStoreRegistry().registerSystem(new BlockPlaceProtectionSystem(claimManager, getLogger()));
115116
getEntityStoreRegistry().registerSystem(new BlockUseProtectionSystem(claimManager, getLogger()));
116117
getEntityStoreRegistry().registerSystem(new ItemPickupProtectionSystem(claimManager, getLogger()));
118+
getEntityStoreRegistry().registerSystem(new PvPProtectionSystem(claimManager, getLogger()));
117119

118120
// Register claim title system (shows banner when entering/leaving claims)
119121
claimTitleSystem = new ClaimTitleSystem(claimStorage);
@@ -136,8 +138,13 @@ private void onWorldAdd(AddWorldEvent event) {
136138
// Set our custom world map provider for persistent worlds
137139
try {
138140
if (!world.getWorldConfig().isDeleteOnRemove()) {
141+
// Set custom world map provider for map tiles
139142
world.getWorldConfig().setWorldMapProvider(new EasyClaimsWorldMapProvider());
140143
getLogger().atWarning().log("[Map] Set EasyClaimsWorldMapProvider for world: %s", world.getName());
144+
145+
// Register the marker provider for POI markers (enables live updates)
146+
world.getWorldMapManager().addMarkerProvider("easyclaims", mapOverlayProvider);
147+
getLogger().atInfo().log("[Map] Registered ClaimMapOverlayProvider for world: %s", world.getName());
141148
}
142149
} catch (Exception e) {
143150
getLogger().atSevere().withCause(e).log("[Map] Failed to set map provider for world: %s", world.getName());

src/main/java/com/easyclaims/EasyClaimsAccess.java

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.easyclaims;
22

3+
import com.easyclaims.config.PluginConfig;
4+
import com.easyclaims.data.AdminClaims;
5+
import com.easyclaims.data.Claim;
36
import com.easyclaims.data.ClaimStorage;
47
import com.easyclaims.data.PlayerClaims;
58
import com.easyclaims.data.TrustedPlayer;
@@ -16,13 +19,15 @@
1619
*/
1720
public class EasyClaimsAccess {
1821
private static ClaimStorage claimStorage;
22+
private static PluginConfig pluginConfig;
1923

2024
/**
21-
* Initializes the accessor with the claim storage instance.
25+
* Initializes the accessor with the claim storage and config instances.
2226
* Called during plugin startup.
2327
*/
24-
public static void init(ClaimStorage storage) {
28+
public static void init(ClaimStorage storage, PluginConfig config) {
2529
claimStorage = storage;
30+
pluginConfig = config;
2631
System.out.println("[EasyClaimsAccess] Initialized with claimStorage: " + (storage != null ? "OK" : "NULL"));
2732
}
2833

@@ -86,4 +91,52 @@ public static List<String> getTrustedPlayerNames(String worldName, int chunkX, i
8691

8792
return names;
8893
}
94+
95+
/**
96+
* Checks if PvP is disabled at a location (for map rendering).
97+
* - Unclaimed: PvP enabled (returns false)
98+
* - Admin claims: Use claim's pvpEnabled setting
99+
* - Player claims: Use server config (pvpInPlayerClaims)
100+
*/
101+
public static boolean isPvPDisabled(String worldName, int chunkX, int chunkZ) {
102+
if (claimStorage == null) return false;
103+
Claim claim = claimStorage.getClaimAt(worldName, chunkX, chunkZ);
104+
if (claim == null) {
105+
return false; // Unclaimed = PvP enabled
106+
}
107+
108+
// Admin claims use their own per-claim setting
109+
if (claim.isAdminClaim()) {
110+
return !claim.isPvpEnabled();
111+
}
112+
113+
// Player claims use the global server setting
114+
if (pluginConfig == null) return false;
115+
return !pluginConfig.isPvpInPlayerClaims();
116+
}
117+
118+
/**
119+
* Checks if a chunk is an admin claim.
120+
*/
121+
public static boolean isAdminClaim(String worldName, int chunkX, int chunkZ) {
122+
if (claimStorage == null) return false;
123+
UUID owner = claimStorage.getClaimOwner(worldName, chunkX, chunkZ);
124+
return AdminClaims.isAdminClaim(owner);
125+
}
126+
127+
/**
128+
* Gets the display name for a claim (for admin claims with custom names).
129+
* Returns the custom display name if set, otherwise returns the owner name.
130+
*/
131+
public static String getClaimDisplayName(String worldName, int chunkX, int chunkZ) {
132+
if (claimStorage == null) return null;
133+
134+
Claim claim = claimStorage.getClaimAt(worldName, chunkX, chunkZ);
135+
if (claim != null && claim.getDisplayName() != null && !claim.getDisplayName().isEmpty()) {
136+
return claim.getDisplayName();
137+
}
138+
139+
// Fall back to owner name
140+
return getOwnerName(worldName, chunkX, chunkZ);
141+
}
89142
}

src/main/java/com/easyclaims/commands/subcommands/ClaimSubcommand.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.hypixel.hytale.server.core.Message;
77
import com.hypixel.hytale.server.core.command.system.CommandContext;
88
import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand;
9+
import com.hypixel.hytale.server.core.entity.entities.Player;
910
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
1011
import com.hypixel.hytale.server.core.universe.PlayerRef;
1112
import com.hypixel.hytale.server.core.universe.world.World;
@@ -51,7 +52,11 @@ protected void execute(@Nonnull CommandContext ctx,
5152
int currentClaims = claimManager.getPlayerClaims(playerId).getClaimCount();
5253
int maxClaims = claimManager.getMaxClaims(playerId);
5354

54-
ClaimManager.ClaimResult result = claimManager.claimChunk(playerId, worldName, position.getX(), position.getZ());
55+
// Check if player is admin - bypass claim limits and buffer zones
56+
Player player = store.getComponent(playerRef, Player.getComponentType());
57+
boolean isAdmin = player != null && player.hasPermission("easyclaims.admin");
58+
59+
ClaimManager.ClaimResult result = claimManager.claimChunk(playerId, worldName, position.getX(), position.getZ(), isAdmin);
5560

5661
switch (result) {
5762
case SUCCESS:

src/main/java/com/easyclaims/commands/subcommands/SettingsSubcommand.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
import com.hypixel.hytale.component.Ref;
44
import com.hypixel.hytale.component.Store;
5+
import com.hypixel.hytale.math.vector.Vector3d;
56
import com.hypixel.hytale.server.core.command.system.CommandContext;
67
import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand;
78
import com.hypixel.hytale.server.core.entity.entities.Player;
9+
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
810
import com.hypixel.hytale.server.core.universe.PlayerRef;
911
import com.hypixel.hytale.server.core.universe.world.World;
1012
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
@@ -17,7 +19,7 @@ public class SettingsSubcommand extends AbstractPlayerCommand {
1719
private final EasyClaims plugin;
1820

1921
public SettingsSubcommand(EasyClaims plugin) {
20-
super("settings", "Manage your trusted players");
22+
super("settings", "Manage your claim settings and trusted players");
2123
this.plugin = plugin;
2224
requirePermission("easyclaims.use");
2325
}
@@ -31,13 +33,22 @@ protected void execute(@Nonnull CommandContext ctx,
3133
Player player = store.getComponent(playerRef, Player.getComponentType());
3234
if (player == null) return;
3335

36+
// Get player's current position for claim lookup
37+
TransformComponent transform = store.getComponent(playerRef, TransformComponent.getComponentType());
38+
Vector3d position = transform != null ? transform.getPosition() : new Vector3d(0, 0, 0);
39+
String worldName = world.getName();
40+
3441
world.execute(() -> {
3542
player.getPageManager().openCustomPage(playerRef, store,
3643
new ClaimSettingsGui(
3744
playerData,
3845
plugin.getClaimManager(),
3946
plugin.getPlaytimeManager(),
40-
(playerId) -> plugin.refreshPlayerClaimChunks(playerId)
47+
(playerId) -> plugin.refreshPlayerClaimChunks(playerId),
48+
plugin,
49+
worldName,
50+
position.getX(),
51+
position.getZ()
4152
)
4253
);
4354
});
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package com.easyclaims.commands.subcommands.admin;
2+
3+
import com.hypixel.hytale.component.Ref;
4+
import com.hypixel.hytale.component.Store;
5+
import com.hypixel.hytale.math.vector.Vector3d;
6+
import com.hypixel.hytale.server.core.command.system.CommandContext;
7+
import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg;
8+
import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes;
9+
import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand;
10+
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
11+
import com.hypixel.hytale.server.core.universe.PlayerRef;
12+
import com.hypixel.hytale.server.core.universe.world.World;
13+
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
14+
import com.hypixel.hytale.math.util.ChunkUtil;
15+
import com.easyclaims.EasyClaims;
16+
import com.easyclaims.data.AdminClaims;
17+
import com.easyclaims.data.Claim;
18+
import com.easyclaims.util.Messages;
19+
20+
import javax.annotation.Nonnull;
21+
import java.util.UUID;
22+
23+
/**
24+
* Admin command to create server-owned admin claims.
25+
* Admin claims have PvP disabled by default and bypass buffer zones.
26+
*/
27+
public class AdminClaimSubcommand extends AbstractPlayerCommand {
28+
private final EasyClaims plugin;
29+
private final OptionalArg<String> displayNameArg;
30+
31+
public AdminClaimSubcommand(EasyClaims plugin) {
32+
super("claim", "Create an admin claim at your location");
33+
this.plugin = plugin;
34+
this.displayNameArg = withOptionalArg("name", "Display name for the claim (e.g., Spawn)", ArgTypes.STRING);
35+
requirePermission("easyclaims.admin");
36+
}
37+
38+
@Override
39+
protected void execute(@Nonnull CommandContext ctx,
40+
@Nonnull Store<EntityStore> store,
41+
@Nonnull Ref<EntityStore> playerRef,
42+
@Nonnull PlayerRef playerData,
43+
@Nonnull World world) {
44+
TransformComponent transform = store.getComponent(playerRef, TransformComponent.getComponentType());
45+
Vector3d position = transform.getPosition();
46+
String worldName = world.getName();
47+
48+
int chunkX = ChunkUtil.chunkCoordinate((int) position.getX());
49+
int chunkZ = ChunkUtil.chunkCoordinate((int) position.getZ());
50+
51+
// Check if already claimed
52+
UUID existingOwner = plugin.getClaimStorage().getClaimOwner(worldName, chunkX, chunkZ);
53+
if (existingOwner != null) {
54+
String ownerName = plugin.getClaimStorage().getPlayerName(existingOwner);
55+
if (AdminClaims.isAdminClaim(existingOwner)) {
56+
playerData.sendMessage(Messages.alreadyOwnChunk());
57+
} else {
58+
playerData.sendMessage(Messages.chunkClaimedByOther(ownerName));
59+
}
60+
return;
61+
}
62+
63+
// Get optional display name
64+
String displayName = displayNameArg.get(ctx);
65+
66+
// Register admin name for map display
67+
plugin.getClaimStorage().setPlayerName(AdminClaims.ADMIN_UUID,
68+
displayName != null ? displayName : AdminClaims.DEFAULT_DISPLAY_NAME);
69+
70+
// Create admin claim (PvP disabled by default)
71+
Claim claim = Claim.createAdminClaim(worldName, chunkX, chunkZ, displayName);
72+
plugin.getClaimStorage().addClaim(AdminClaims.ADMIN_UUID, claim);
73+
74+
// Refresh map
75+
plugin.refreshWorldMapChunk(worldName, chunkX, chunkZ);
76+
77+
playerData.sendMessage(Messages.adminClaimCreated(chunkX, chunkZ, displayName));
78+
playerData.sendMessage(Messages.pvpStatusDisabled());
79+
}
80+
}

src/main/java/com/easyclaims/commands/subcommands/admin/AdminConfigSubcommand.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
1111
import com.easyclaims.EasyClaims;
1212
import com.easyclaims.config.PluginConfig;
13+
import com.easyclaims.util.Messages;
1314

1415
import javax.annotation.Nonnull;
1516
import java.awt.Color;
@@ -38,7 +39,7 @@ protected void execute(@Nonnull CommandContext ctx,
3839
playerData.sendMessage(Message.raw("=== Server Claim Settings ===").color(GOLD));
3940
playerData.sendMessage(Message.raw("").color(GRAY));
4041
playerData.sendMessage(Message.raw("New players start with: " + config.getStartingClaims() + " claims").color(AQUA));
41-
playerData.sendMessage(Message.raw("Players earn: " + config.getClaimsPerHour() + " extra claims per hour played").color(AQUA));
42+
playerData.sendMessage(Message.raw("Players earn: " + Messages.formatClaimRate(config.getClaimsPerHour())).color(AQUA));
4243
playerData.sendMessage(Message.raw("Maximum claims allowed: " + config.getMaxClaims()).color(AQUA));
4344
int buffer = config.getClaimBufferSize();
4445
String bufferText = buffer > 0 ? buffer + " chunks" : "disabled";

0 commit comments

Comments
 (0)