Skip to content

Commit f0defd6

Browse files
cryptobenchclaude
andcommitted
Fix flower/bottle protection and add admin testing commands
Protection fixes: - Block Pickup interactions at UseBlockEvent.Pre level (ECS event) - Require BUILD trust for Pickup interactions (harvesting flowers/bottles) - Enhanced ClaimProtectionListener to check entity interactions - Show appropriate messages based on interaction type New admin testing commands: - /easyclaims admin fakeclaim - Claim chunk as fake player for testing - /easyclaims admin fakeclaim trust <level> - Trust yourself to test permissions - /easyclaims admin fakeclaim untrust - Remove trust to test blocking - /easyclaims admin fakeclaim remove - Clean up fake claims This fixes the issue where flowers and bottles could be picked up in protected claims by blocking the harvest at the ECS event level. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 76b44f0 commit f0defd6

5 files changed

Lines changed: 195 additions & 20 deletions

File tree

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

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,12 +181,104 @@ private void handleAdmin(PlayerRef playerData, String[] args, Store<EntityStore>
181181
case "map":
182182
handleGui(playerData, store, playerRef, world, true);
183183
break;
184+
case "fakeclaim":
185+
handleFakeClaim(playerData, args, store, playerRef, world);
186+
break;
184187
default:
185188
playerData.sendMessage(Message.raw("Unknown admin command: " + adminSubcmd).color(RED));
186189
showAdminHelp(playerData);
187190
}
188191
}
189192

193+
// Static fake player UUID for testing (consistent across sessions)
194+
private static final UUID FAKE_PLAYER_UUID = UUID.fromString("00000000-0000-0000-0000-000000000001");
195+
private static final String FAKE_PLAYER_NAME = "TestPlayer";
196+
197+
/**
198+
* Admin command to claim a chunk as a fake player for testing protection.
199+
* Usage:
200+
* /easyclaims admin fakeclaim - Claim current chunk as fake player
201+
* /easyclaims admin fakeclaim trust <level> - Trust yourself to fake claims
202+
* /easyclaims admin fakeclaim untrust - Remove your trust from fake claims
203+
* /easyclaims admin fakeclaim remove - Remove all fake player claims
204+
*/
205+
private void handleFakeClaim(PlayerRef playerData, String[] args, Store<EntityStore> store, Ref<EntityStore> playerRef, World world) {
206+
// args[0] = "admin", args[1] = "fakeclaim", args[2] = subcommand (optional)
207+
String subCmd = args.length > 2 ? args[2] : null;
208+
String arg1 = args.length > 3 ? args[3] : null;
209+
210+
if (subCmd == null) {
211+
// Claim current chunk as fake player
212+
TransformComponent transform = store.getComponent(playerRef, TransformComponent.getComponentType());
213+
Vector3d position = transform.getPosition();
214+
String worldName = world.getName();
215+
216+
int chunkX = ChunkUtil.chunkCoordinate((int) position.getX());
217+
int chunkZ = ChunkUtil.chunkCoordinate((int) position.getZ());
218+
219+
// Check if already claimed
220+
UUID existingOwner = plugin.getClaimStorage().getClaimOwner(worldName, chunkX, chunkZ);
221+
if (existingOwner != null) {
222+
String ownerName = plugin.getClaimStorage().getPlayerName(existingOwner);
223+
playerData.sendMessage(Message.raw("Chunk already claimed by " + ownerName).color(RED));
224+
return;
225+
}
226+
227+
// Register fake player name
228+
plugin.getClaimStorage().setPlayerName(FAKE_PLAYER_UUID, FAKE_PLAYER_NAME);
229+
230+
// Add claim for fake player
231+
plugin.getClaimStorage().addClaim(FAKE_PLAYER_UUID, new Claim(worldName, chunkX, chunkZ));
232+
plugin.refreshWorldMapChunk(worldName, chunkX, chunkZ);
233+
234+
playerData.sendMessage(Message.raw("=== Fake Claim Created ===").color(GOLD));
235+
playerData.sendMessage(Message.raw("Chunk [" + chunkX + ", " + chunkZ + "] claimed as " + FAKE_PLAYER_NAME).color(GREEN));
236+
playerData.sendMessage(Message.raw("You are NOT trusted - try to break/pickup items to test protection!").color(YELLOW));
237+
playerData.sendMessage(Message.raw("").color(GRAY));
238+
playerData.sendMessage(Message.raw("Commands:").color(AQUA));
239+
playerData.sendMessage(Message.raw(" /easyclaims admin fakeclaim trust <level> - Trust yourself").color(GRAY));
240+
playerData.sendMessage(Message.raw(" /easyclaims admin fakeclaim untrust - Remove your trust").color(GRAY));
241+
playerData.sendMessage(Message.raw(" /easyclaims admin fakeclaim remove - Remove all fake claims").color(GRAY));
242+
243+
} else if (subCmd.equalsIgnoreCase("trust")) {
244+
// Trust the player to fake claims
245+
TrustLevel level = TrustLevel.BUILD;
246+
if (arg1 != null && !arg1.isEmpty()) {
247+
level = TrustLevel.fromString(arg1);
248+
if (level == null || level == TrustLevel.NONE) {
249+
playerData.sendMessage(Message.raw("Invalid level. Use: use, container, workstation, damage, build").color(RED));
250+
return;
251+
}
252+
}
253+
254+
plugin.getClaimManager().addTrust(FAKE_PLAYER_UUID, playerData.getUuid(), playerData.getUsername(), level);
255+
playerData.sendMessage(Message.raw("You now have " + level.getDescription() + " trust in fake claims").color(GREEN));
256+
plugin.refreshPlayerClaimChunks(FAKE_PLAYER_UUID);
257+
258+
} else if (subCmd.equalsIgnoreCase("untrust")) {
259+
// Remove trust
260+
plugin.getClaimManager().removeTrust(FAKE_PLAYER_UUID, playerData.getUuid());
261+
playerData.sendMessage(Message.raw("Removed your trust from fake claims - you should be blocked now").color(GREEN));
262+
plugin.refreshPlayerClaimChunks(FAKE_PLAYER_UUID);
263+
264+
} else if (subCmd.equalsIgnoreCase("remove")) {
265+
// Remove all fake claims
266+
int count = plugin.getClaimManager().unclaimAll(FAKE_PLAYER_UUID);
267+
if (count > 0) {
268+
playerData.sendMessage(Message.raw("Removed " + count + " fake claim(s)").color(GREEN));
269+
for (String worldName : EasyClaims.WORLDS.keySet()) {
270+
plugin.refreshWorldMap(worldName);
271+
}
272+
} else {
273+
playerData.sendMessage(Message.raw("No fake claims to remove").color(YELLOW));
274+
}
275+
276+
} else {
277+
playerData.sendMessage(Message.raw("Unknown fakeclaim command: " + subCmd).color(RED));
278+
playerData.sendMessage(Message.raw("Use: trust <level>, untrust, or remove").color(GRAY));
279+
}
280+
}
281+
190282
private void showAdminHelp(PlayerRef playerData) {
191283
playerData.sendMessage(Message.raw("=== EasyClaims Admin Commands ===").color(GOLD));
192284
playerData.sendMessage(Message.raw("/easyclaims admin gui - Open claim manager (admin mode)").color(GRAY));
@@ -196,6 +288,12 @@ private void showAdminHelp(PlayerRef playerData) {
196288
playerData.sendMessage(Message.raw("/easyclaims admin unclaim - Remove claim at your location").color(GRAY));
197289
playerData.sendMessage(Message.raw("/easyclaims admin unclaim <player> - Remove all claims from player").color(GRAY));
198290
playerData.sendMessage(Message.raw("").color(GRAY));
291+
playerData.sendMessage(Message.raw("=== Testing Commands ===").color(GOLD));
292+
playerData.sendMessage(Message.raw("/easyclaims admin fakeclaim - Claim chunk as fake player (for testing)").color(GRAY));
293+
playerData.sendMessage(Message.raw("/easyclaims admin fakeclaim trust <level> - Trust yourself to test").color(GRAY));
294+
playerData.sendMessage(Message.raw("/easyclaims admin fakeclaim untrust - Remove trust to test blocking").color(GRAY));
295+
playerData.sendMessage(Message.raw("/easyclaims admin fakeclaim remove - Remove all fake claims").color(GRAY));
296+
playerData.sendMessage(Message.raw("").color(GRAY));
199297
playerData.sendMessage(Message.raw("Settings: starting, perhour, max, buffer").color(AQUA));
200298
}
201299

src/main/java/com/easyclaims/listeners/ClaimProtectionListener.java

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,19 @@
22

33
import com.hypixel.hytale.event.EventRegistry;
44
import com.hypixel.hytale.logger.HytaleLogger;
5+
import com.hypixel.hytale.math.vector.Vector3d;
56
import com.hypixel.hytale.math.vector.Vector3i;
67
import com.hypixel.hytale.server.core.entity.entities.Player;
78
import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent;
89
import com.hypixel.hytale.server.core.event.events.player.PlayerDisconnectEvent;
910
import com.hypixel.hytale.server.core.event.events.player.PlayerInteractEvent;
1011
import com.hypixel.hytale.server.core.universe.PlayerRef;
12+
import com.hypixel.hytale.server.core.entity.Entity;
1113
import com.hypixel.hytale.protocol.InteractionType;
1214
import com.easyclaims.EasyClaims;
15+
import com.easyclaims.data.TrustLevel;
1316
import com.easyclaims.managers.ClaimManager;
17+
import com.easyclaims.util.Messages;
1418

1519
import java.util.Map;
1620
import java.util.UUID;
@@ -45,34 +49,91 @@ public void register(EventRegistry eventRegistry) {
4549
eventRegistry.registerGlobal(PlayerDisconnectEvent.class, this::onPlayerDisconnect);
4650
}
4751

52+
// Rate limit messages - don't spam players
53+
private static final Map<UUID, Long> lastMessageTime = new ConcurrentHashMap<>();
54+
private static final long MESSAGE_COOLDOWN_MS = 2000; // 2 seconds
55+
56+
private boolean canSendMessage(UUID playerId) {
57+
long now = System.currentTimeMillis();
58+
Long lastTime = lastMessageTime.get(playerId);
59+
if (lastTime == null || now - lastTime > MESSAGE_COOLDOWN_MS) {
60+
lastMessageTime.put(playerId, now);
61+
return true;
62+
}
63+
return false;
64+
}
65+
4866
/**
4967
* Handle player interactions - check claim protection.
5068
*/
5169
private void onPlayerInteract(PlayerInteractEvent event) {
5270
Player player = event.getPlayer();
5371
if (player == null) return;
5472

55-
Vector3i targetBlock = event.getTargetBlock();
56-
if (targetBlock == null) return;
57-
5873
UUID playerId = player.getUuid();
5974
InteractionType actionType = event.getActionType();
6075
String worldName = player.getWorld().getName();
6176

62-
// Track interaction for ECS event correlation
63-
String blockKey = getBlockKey(targetBlock);
64-
PlayerInteraction interaction = new PlayerInteraction(playerId, worldName, targetBlock, System.currentTimeMillis());
65-
pendingInteractions.put(blockKey, interaction);
66-
playerLastInteraction.put(playerId, interaction);
77+
Vector3i targetBlock = event.getTargetBlock();
78+
Entity targetEntity = event.getTargetEntity();
79+
80+
// Determine the position to check - either from block or entity
81+
double checkX, checkZ;
82+
if (targetBlock != null) {
83+
checkX = targetBlock.getX();
84+
checkZ = targetBlock.getZ();
85+
} else if (targetEntity != null) {
86+
// For entity interactions (like picking up dropped items), use entity position
87+
var transformComponent = targetEntity.getTransformComponent();
88+
if (transformComponent == null) return;
89+
Vector3d entityPos = transformComponent.getPosition();
90+
if (entityPos == null) return;
91+
checkX = entityPos.getX();
92+
checkZ = entityPos.getZ();
93+
} else {
94+
return; // No target to check
95+
}
96+
97+
// Track interaction for ECS event correlation (only for block interactions)
98+
if (targetBlock != null) {
99+
String blockKey = getBlockKey(targetBlock);
100+
PlayerInteraction interaction = new PlayerInteraction(playerId, worldName, targetBlock, System.currentTimeMillis());
101+
pendingInteractions.put(blockKey, interaction);
102+
playerLastInteraction.put(playerId, interaction);
103+
}
67104

68105
cleanupOldInteractions();
69106

107+
// Determine required trust level based on action type
108+
// Pickup interactions on blocks (harvesting flowers, etc.) require BUILD trust
109+
// since they effectively destroy the block
110+
TrustLevel requiredLevel;
111+
if (actionType == InteractionType.Pickup) {
112+
requiredLevel = TrustLevel.BUILD;
113+
} else if (actionType == InteractionType.Primary) {
114+
requiredLevel = TrustLevel.BUILD; // Attacking/breaking
115+
} else {
116+
requiredLevel = TrustLevel.USE; // Default for other interactions
117+
}
118+
70119
// Check if this location is protected
71-
boolean canInteract = claimManager.canInteract(playerId, worldName, targetBlock.getX(), targetBlock.getZ());
120+
boolean hasPermission = claimManager.hasPermissionAt(playerId, worldName, checkX, checkZ, requiredLevel);
72121

73-
if (!canInteract) {
74-
logger.atFine().log("Blocked interaction: player=%s block=%s action=%s", playerId, targetBlock, actionType);
122+
if (!hasPermission) {
123+
logger.atFine().log("Blocked interaction: player=%s pos=[%.1f, %.1f] action=%s required=%s",
124+
playerId, checkX, checkZ, actionType, requiredLevel);
75125
event.setCancelled(true);
126+
127+
// Send appropriate message based on action type
128+
if (canSendMessage(playerId)) {
129+
if (actionType == InteractionType.Pickup) {
130+
player.sendMessage(Messages.cannotPickupItemsHere());
131+
} else if (actionType == InteractionType.Primary) {
132+
player.sendMessage(Messages.cannotBuildHere());
133+
} else {
134+
player.sendMessage(Messages.cannotInteractHere());
135+
}
136+
}
76137
}
77138
}
78139

src/main/java/com/easyclaims/systems/BlockUseProtectionSystem.java

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import com.hypixel.hytale.server.core.event.events.ecs.UseBlockEvent;
1616
import com.hypixel.hytale.server.core.universe.PlayerRef;
1717
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
18+
import com.hypixel.hytale.protocol.InteractionType;
1819
import com.easyclaims.config.BlockGroups;
1920
import com.easyclaims.data.TrustLevel;
2021
import com.easyclaims.managers.ClaimManager;
@@ -86,24 +87,39 @@ public void handle(int entityIndex, @Nonnull ArchetypeChunk<EntityStore> chunk,
8687

8788
UUID playerId = playerRef.getUuid();
8889
String worldName = player.getWorld().getName();
90+
InteractionType interactionType = event.getInteractionType();
8991

90-
// Determine required trust level based on block type
92+
// Determine required trust level based on interaction type and block type
9193
BlockType blockType = event.getBlockType();
92-
TrustLevel requiredLevel = getRequiredTrustLevel(blockType);
94+
TrustLevel requiredLevel = getRequiredTrustLevel(blockType, interactionType);
9395

9496
// Check if player has permission
9597
if (!claimManager.hasPermissionAt(playerId, worldName, targetBlock.getX(), targetBlock.getZ(), requiredLevel)) {
9698
event.setCancelled(true);
9799
if (canSendMessage(playerId)) {
98-
player.sendMessage(Messages.cannotUseBlock(requiredLevel));
100+
// Send appropriate message based on interaction type
101+
if (interactionType == InteractionType.Pickup) {
102+
player.sendMessage(Messages.cannotPickupItemsHere());
103+
} else {
104+
player.sendMessage(Messages.cannotUseBlock(requiredLevel));
105+
}
99106
}
107+
logger.atFine().log("Blocked UseBlockEvent: player=%s block=%s interaction=%s required=%s",
108+
playerId, targetBlock, interactionType, requiredLevel);
100109
}
101110
}
102111

103112
/**
104-
* Determines the required trust level based on block type.
113+
* Determines the required trust level based on block type and interaction type.
114+
* Pickup interactions (harvesting flowers, etc.) require BUILD trust since they destroy the block.
105115
*/
106-
private TrustLevel getRequiredTrustLevel(BlockType blockType) {
116+
private TrustLevel getRequiredTrustLevel(BlockType blockType, InteractionType interactionType) {
117+
// Pickup interactions (harvesting flowers, bottles, etc.) require BUILD trust
118+
// since they effectively destroy/take the block
119+
if (interactionType == InteractionType.Pickup) {
120+
return TrustLevel.BUILD;
121+
}
122+
107123
if (blockType == null) {
108124
return TrustLevel.USE; // Default to USE for unknown blocks
109125
}

src/main/resources/Common/UI/Custom/Pages/cryptobench_EasyClaims_ChunkEntry.ui

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
$C = "../Common.ui";
22
TextButton {
3-
Anchor: (Width: 32, Height: 32);
3+
Anchor: (Width: 56, Height: 56);
44
Background: (Color: #00aa0022);
5-
Padding: (Left: 12, Right: 6, Top: 6, Bottom: 6);
5+
Padding: (Left: 2, Right: 2, Top: 2, Bottom: 2);
66
TextTooltipShowDelay: 0.1;
77
Style: TextButtonStyle(
88
Default: (
99
LabelStyle: (
10-
FontSize: 14,
10+
FontSize: 10,
1111
TextColor: #ffffffff
1212
)
1313
),

src/main/resources/Common/UI/Custom/Pages/cryptobench_EasyClaims_ChunkVisualizer.ui

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ $C = "../Common.ui";
33
$C.@PageOverlay {
44

55
$C.@Container {
6-
Anchor: (Width: 620, Height: 700);
6+
Anchor: (Width: 1020, Height: 750);
77

88
#Title {
99
Group {

0 commit comments

Comments
 (0)