Skip to content

Commit ab8be25

Browse files
cryptobenchclaude
andcommitted
Add admin grant commands for player claim management
Add commands for server admins to manage individual player claim allowances: - /easyclaims admin grant claims <player> <amount> - Grant bonus claim slots (additive) - /easyclaims admin grant maxclaims <player> <amount> - Increase max claims cap (additive) - /easyclaims admin grant maxclaims <player> unlimited - Remove claims cap entirely - /easyclaims admin info <player> - View player's claim stats and bonuses Features: - All grant commands are additive (running twice adds more) - Works for both online and offline players - Supports username or UUID for player lookup - Bonus slots are added after the playtime cap, so they always apply - Data persists in player JSON files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 89666b6 commit ab8be25

10 files changed

Lines changed: 592 additions & 1 deletion

File tree

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ All commands use `/easyclaims`.
7979
| `/easyclaims admin unclaim <player>` | Remove ALL claims from a player |
8080
| `/easyclaims admin claim [name]` | Create an admin claim (e.g., "Spawn") |
8181
| `/easyclaims admin pvp [on/off]` | Toggle PvP in the current claim |
82+
| `/easyclaims admin grant claims <player> <amount>` | Grant bonus claim slots (additive) |
83+
| `/easyclaims admin grant maxclaims <player> <amount>` | Increase player's max claims cap (additive) |
84+
| `/easyclaims admin grant maxclaims <player> unlimited` | Remove claims cap entirely |
85+
| `/easyclaims admin info <player>` | View a player's claim stats and bonuses |
8286

8387
**Settings you can change:**
8488
```
@@ -89,6 +93,28 @@ All commands use `/easyclaims`.
8993
/easyclaims admin set pvpinclaims false # PvP in player claims (true=PvP, false=PvE)
9094
```
9195

96+
**Player Claim Management:**
97+
98+
Grant commands are **additive** - running them multiple times adds more each time:
99+
```
100+
# Grant 10 bonus claim slots (added on top of playtime-based limit)
101+
/easyclaims admin grant claims Steve 10
102+
103+
# Grant 10 more (Steve now has 20 bonus slots total)
104+
/easyclaims admin grant claims Steve 10
105+
106+
# Increase max claims cap by 50 (server default + 50)
107+
/easyclaims admin grant maxclaims Alex 50
108+
109+
# Remove the claims cap entirely (unlimited claims)
110+
/easyclaims admin grant maxclaims Alex unlimited
111+
112+
# View a player's claim stats and bonuses
113+
/easyclaims admin info Steve
114+
```
115+
116+
**Note:** `<player>` can be a username (if online or has previously claimed) or UUID. These commands work even when the target player is offline.
117+
92118
**Admin Bypass:** Admins with `easyclaims.admin` permission bypass all protection checks and claim limits.
93119

94120
---

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,4 +405,11 @@ public ClaimMapOverlayProvider getMapOverlayProvider() {
405405
public ClaimStorage getClaimStorage() {
406406
return claimStorage;
407407
}
408+
409+
/**
410+
* Gets the playtime storage for direct access.
411+
*/
412+
public PlaytimeStorage getPlaytimeStorage() {
413+
return playtimeStorage;
414+
}
408415
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
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.server.core.Message;
6+
import com.hypixel.hytale.server.core.NameMatching;
7+
import com.hypixel.hytale.server.core.command.system.CommandContext;
8+
import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg;
9+
import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes;
10+
import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand;
11+
import com.hypixel.hytale.server.core.universe.PlayerRef;
12+
import com.hypixel.hytale.server.core.universe.Universe;
13+
import com.hypixel.hytale.server.core.universe.world.World;
14+
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
15+
import com.easyclaims.EasyClaims;
16+
import com.easyclaims.data.PlayerClaims;
17+
import com.easyclaims.data.PlaytimeData;
18+
19+
import javax.annotation.Nonnull;
20+
import java.awt.Color;
21+
import java.util.UUID;
22+
23+
/**
24+
* Shows detailed claim information for a player.
25+
* Works for online and offline players.
26+
*
27+
* Usage: /easyclaims admin info <player>
28+
*
29+
* Displays:
30+
* - Current claims / max claims
31+
* - Playtime hours
32+
* - Bonus claim slots (admin-granted)
33+
* - Bonus max claims (admin-granted)
34+
* - Unlimited claims status
35+
*/
36+
public class AdminInfoSubcommand extends AbstractPlayerCommand {
37+
private final EasyClaims plugin;
38+
private final RequiredArg<String> playerArg;
39+
40+
private static final Color GREEN = new Color(85, 255, 85);
41+
private static final Color RED = new Color(255, 85, 85);
42+
private static final Color YELLOW = new Color(255, 255, 85);
43+
private static final Color GOLD = new Color(255, 170, 0);
44+
private static final Color GRAY = new Color(170, 170, 170);
45+
private static final Color AQUA = new Color(85, 255, 255);
46+
47+
public AdminInfoSubcommand(EasyClaims plugin) {
48+
super("info", "View a player's claim statistics and bonuses");
49+
this.plugin = plugin;
50+
this.playerArg = withRequiredArg("player", "Player name or UUID", ArgTypes.STRING);
51+
requirePermission("easyclaims.admin");
52+
}
53+
54+
@Override
55+
protected void execute(@Nonnull CommandContext ctx,
56+
@Nonnull Store<EntityStore> store,
57+
@Nonnull Ref<EntityStore> playerRef,
58+
@Nonnull PlayerRef playerData,
59+
@Nonnull World world) {
60+
String playerInput = playerArg.get(ctx);
61+
62+
// Resolve player (online or offline)
63+
UUID targetId = null;
64+
String targetName = playerInput;
65+
boolean isOnline = false;
66+
67+
// Try 1: Online player lookup by username
68+
PlayerRef targetPlayer = Universe.get().getPlayerByUsername(playerInput, NameMatching.EXACT_IGNORE_CASE);
69+
if (targetPlayer != null) {
70+
targetId = targetPlayer.getUuid();
71+
targetName = targetPlayer.getUsername();
72+
isOnline = true;
73+
} else {
74+
// Try 2: Parse as UUID (works for offline players)
75+
try {
76+
targetId = UUID.fromString(playerInput);
77+
String storedName = plugin.getClaimStorage().getPlayerName(targetId);
78+
if (storedName != null && !storedName.equals(targetId.toString().substring(0, 8))) {
79+
targetName = storedName;
80+
}
81+
} catch (IllegalArgumentException e) {
82+
// Try 3: Lookup by username in stored names
83+
targetId = plugin.getClaimStorage().getPlayerUUID(playerInput);
84+
if (targetId == null) {
85+
playerData.sendMessage(Message.raw("Player not found: " + playerInput).color(RED));
86+
playerData.sendMessage(Message.raw("Use a UUID for players who have never claimed.").color(YELLOW));
87+
return;
88+
}
89+
}
90+
}
91+
92+
// Get player data
93+
PlayerClaims claims = plugin.getClaimStorage().getPlayerClaims(targetId);
94+
PlaytimeData playtime = plugin.getPlaytimeStorage().getPlaytime(targetId);
95+
96+
// Calculate stats
97+
int currentClaims = claims.getClaimCount();
98+
int maxClaims = plugin.getClaimManager().getMaxClaims(targetId);
99+
double playtimeHours = playtime.getTotalHoursWithCurrentSession();
100+
int bonusSlots = claims.getBonusClaimSlots();
101+
int bonusMax = claims.getBonusMaxClaims();
102+
boolean unlimited = claims.hasUnlimitedClaims();
103+
104+
// Server config values
105+
int serverStarting = plugin.getPluginConfig().getStartingClaims();
106+
int serverMax = plugin.getPluginConfig().getMaxClaims();
107+
double claimsPerHour = plugin.getPluginConfig().getClaimsPerHour();
108+
109+
// Calculate breakdown
110+
int fromPlaytime = (int) (playtimeHours * claimsPerHour);
111+
int baseMax = serverStarting + fromPlaytime;
112+
int cappedMax = Math.min(baseMax, serverMax + bonusMax);
113+
114+
// Display header
115+
playerData.sendMessage(Message.raw("=== Claim Info: " + targetName + " ===").color(GOLD));
116+
String status = isOnline ? " (online)" : " (offline)";
117+
playerData.sendMessage(Message.raw("UUID: " + targetId.toString() + status).color(GRAY));
118+
119+
// Display claims
120+
String claimStatus = currentClaims + " / " + (unlimited ? "unlimited" : String.valueOf(maxClaims));
121+
playerData.sendMessage(Message.raw("Claims: " + claimStatus).color(currentClaims >= maxClaims && !unlimited ? YELLOW : GREEN));
122+
123+
// Display playtime
124+
String playtimeStr = String.format("%.1f hours", playtimeHours);
125+
playerData.sendMessage(Message.raw("Playtime: " + playtimeStr).color(AQUA));
126+
127+
// Display max claims calculation breakdown
128+
playerData.sendMessage(Message.raw("--- Max Claims Breakdown ---").color(GRAY));
129+
playerData.sendMessage(Message.raw(" Starting claims: " + serverStarting).color(GRAY));
130+
playerData.sendMessage(Message.raw(" From playtime: +" + fromPlaytime + " (" + String.format("%.1f", playtimeHours) + "h x " + claimsPerHour + "/h)").color(GRAY));
131+
playerData.sendMessage(Message.raw(" Base total: " + baseMax).color(GRAY));
132+
133+
if (!unlimited) {
134+
int effectiveCap = serverMax + bonusMax;
135+
playerData.sendMessage(Message.raw(" Cap: " + serverMax + " (server)" + (bonusMax > 0 ? " + " + bonusMax + " (bonus)" : "") + " = " + effectiveCap).color(GRAY));
136+
playerData.sendMessage(Message.raw(" After cap: " + cappedMax).color(GRAY));
137+
}
138+
139+
// Display admin-granted bonuses
140+
playerData.sendMessage(Message.raw("--- Admin Bonuses ---").color(GOLD));
141+
142+
if (bonusSlots > 0) {
143+
playerData.sendMessage(Message.raw(" Bonus claim slots: +" + bonusSlots).color(GREEN));
144+
} else {
145+
playerData.sendMessage(Message.raw(" Bonus claim slots: 0").color(GRAY));
146+
}
147+
148+
if (bonusMax > 0) {
149+
playerData.sendMessage(Message.raw(" Bonus max claims: +" + bonusMax).color(GREEN));
150+
} else {
151+
playerData.sendMessage(Message.raw(" Bonus max claims: 0").color(GRAY));
152+
}
153+
154+
if (unlimited) {
155+
playerData.sendMessage(Message.raw(" Unlimited claims: YES").color(GOLD));
156+
} else {
157+
playerData.sendMessage(Message.raw(" Unlimited claims: no").color(GRAY));
158+
}
159+
160+
// Final effective max
161+
if (!unlimited) {
162+
int effective = cappedMax + bonusSlots;
163+
playerData.sendMessage(Message.raw("Effective max: " + effective + " (" + cappedMax + " + " + bonusSlots + " bonus slots)").color(AQUA));
164+
}
165+
}
166+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection;
44
import com.easyclaims.EasyClaims;
5+
import com.easyclaims.commands.subcommands.admin.grant.AdminGrantSubcommand;
56

67
/**
78
* Admin command collection for EasyClaims.
@@ -22,5 +23,7 @@ public AdminSubcommand(EasyClaims plugin) {
2223
addSubCommand(new AdminFakeClaimSubcommand(plugin));
2324
addSubCommand(new AdminClaimSubcommand(plugin));
2425
addSubCommand(new AdminPvpSubcommand(plugin));
26+
addSubCommand(new AdminGrantSubcommand(plugin));
27+
addSubCommand(new AdminInfoSubcommand(plugin));
2528
}
2629
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.easyclaims.commands.subcommands.admin.grant;
2+
3+
import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection;
4+
import com.easyclaims.EasyClaims;
5+
6+
/**
7+
* Admin grant command collection for managing player claim allowances.
8+
* All grant commands are additive (running twice adds more).
9+
*
10+
* Usage:
11+
* /easyclaims admin grant claims <player> <amount> - Add bonus claim slots
12+
* /easyclaims admin grant maxclaims <player> <amount> - Add to max claims cap
13+
* /easyclaims admin grant maxclaims <player> unlimited - Set unlimited claims
14+
*/
15+
public class AdminGrantSubcommand extends AbstractCommandCollection {
16+
17+
public AdminGrantSubcommand(EasyClaims plugin) {
18+
super("grant", "Grant bonus claims to players");
19+
requirePermission("easyclaims.admin");
20+
21+
// Register grant subcommands
22+
addSubCommand(new GrantClaimsSubcommand(plugin));
23+
addSubCommand(new GrantMaxClaimsSubcommand(plugin));
24+
}
25+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package com.easyclaims.commands.subcommands.admin.grant;
2+
3+
import com.hypixel.hytale.component.Ref;
4+
import com.hypixel.hytale.component.Store;
5+
import com.hypixel.hytale.server.core.Message;
6+
import com.hypixel.hytale.server.core.NameMatching;
7+
import com.hypixel.hytale.server.core.command.system.CommandContext;
8+
import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg;
9+
import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes;
10+
import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand;
11+
import com.hypixel.hytale.server.core.universe.PlayerRef;
12+
import com.hypixel.hytale.server.core.universe.Universe;
13+
import com.hypixel.hytale.server.core.universe.world.World;
14+
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
15+
import com.easyclaims.EasyClaims;
16+
import com.easyclaims.data.PlayerClaims;
17+
18+
import javax.annotation.Nonnull;
19+
import java.awt.Color;
20+
import java.util.UUID;
21+
22+
/**
23+
* Grants bonus claim slots to a player (additive).
24+
* Works for online and offline players.
25+
*
26+
* Usage: /easyclaims admin grant claims <player> <amount>
27+
*
28+
* The bonus slots are added on top of the playtime-based calculation,
29+
* so they always apply regardless of the max claims cap.
30+
*/
31+
public class GrantClaimsSubcommand extends AbstractPlayerCommand {
32+
private final EasyClaims plugin;
33+
private final RequiredArg<String> playerArg;
34+
private final RequiredArg<Integer> amountArg;
35+
36+
private static final Color GREEN = new Color(85, 255, 85);
37+
private static final Color RED = new Color(255, 85, 85);
38+
private static final Color YELLOW = new Color(255, 255, 85);
39+
40+
public GrantClaimsSubcommand(EasyClaims plugin) {
41+
super("claims", "Grant bonus claim slots to a player (additive)");
42+
this.plugin = plugin;
43+
this.playerArg = withRequiredArg("player", "Player name or UUID", ArgTypes.STRING);
44+
this.amountArg = withRequiredArg("amount", "Number of bonus slots to add", ArgTypes.INTEGER);
45+
requirePermission("easyclaims.admin");
46+
}
47+
48+
@Override
49+
protected void execute(@Nonnull CommandContext ctx,
50+
@Nonnull Store<EntityStore> store,
51+
@Nonnull Ref<EntityStore> playerRef,
52+
@Nonnull PlayerRef playerData,
53+
@Nonnull World world) {
54+
String playerInput = playerArg.get(ctx);
55+
int amount = amountArg.get(ctx);
56+
57+
if (amount <= 0) {
58+
playerData.sendMessage(Message.raw("Amount must be positive.").color(RED));
59+
return;
60+
}
61+
62+
// Resolve player (online or offline)
63+
UUID targetId = null;
64+
String targetName = playerInput;
65+
66+
// Try 1: Online player lookup by username
67+
PlayerRef targetPlayer = Universe.get().getPlayerByUsername(playerInput, NameMatching.EXACT_IGNORE_CASE);
68+
if (targetPlayer != null) {
69+
targetId = targetPlayer.getUuid();
70+
targetName = targetPlayer.getUsername();
71+
} else {
72+
// Try 2: Parse as UUID (works for offline players)
73+
try {
74+
targetId = UUID.fromString(playerInput);
75+
String storedName = plugin.getClaimStorage().getPlayerName(targetId);
76+
if (storedName != null && !storedName.equals(targetId.toString().substring(0, 8))) {
77+
targetName = storedName;
78+
}
79+
} catch (IllegalArgumentException e) {
80+
// Try 3: Lookup by username in stored names
81+
targetId = plugin.getClaimStorage().getPlayerUUID(playerInput);
82+
if (targetId == null) {
83+
playerData.sendMessage(Message.raw("Player not found: " + playerInput).color(RED));
84+
playerData.sendMessage(Message.raw("Use a UUID for players who have never claimed.").color(YELLOW));
85+
return;
86+
}
87+
}
88+
}
89+
90+
// Get player claims and add bonus slots
91+
PlayerClaims claims = plugin.getClaimStorage().getPlayerClaims(targetId);
92+
int previousBonus = claims.getBonusClaimSlots();
93+
claims.addBonusClaimSlots(amount);
94+
int newBonus = claims.getBonusClaimSlots();
95+
96+
// Save changes
97+
plugin.getClaimStorage().savePlayerClaims(targetId);
98+
99+
// Report result
100+
playerData.sendMessage(Message.raw("Granted " + amount + " bonus claim slots to " + targetName).color(GREEN));
101+
playerData.sendMessage(Message.raw("Bonus slots: " + previousBonus + " -> " + newBonus).color(YELLOW));
102+
}
103+
}

0 commit comments

Comments
 (0)