Skip to content

Commit 8b4dc1a

Browse files
cryptobenchclaude
andcommitted
Implement proper ECS block protection systems
ECS events (BreakBlockEvent, DamageBlockEvent, etc.) require actual EntityEventSystem implementations, not just EventRegistry listeners. Added four ECS systems registered via getEntityStoreRegistry(): - BlockDamageProtectionSystem - prevents mining progress - BlockBreakProtectionSystem - prevents block destruction - BlockPlaceProtectionSystem - prevents block placement - BlockUseProtectionSystem - prevents chest/door usage Each system implements getQuery() returning Query.any() to handle all entities, and uses the PlayerInteraction tracking from ClaimProtectionListener to identify the acting player. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent af289f9 commit 8b4dc1a

6 files changed

Lines changed: 296 additions & 204 deletions

File tree

src/main/java/com/landclaims/LandClaims.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
import com.landclaims.listeners.ClaimProtectionListener;
1414
import com.landclaims.managers.ClaimManager;
1515
import com.landclaims.managers.PlaytimeManager;
16+
import com.landclaims.systems.BlockBreakProtectionSystem;
17+
import com.landclaims.systems.BlockDamageProtectionSystem;
18+
import com.landclaims.systems.BlockPlaceProtectionSystem;
19+
import com.landclaims.systems.BlockUseProtectionSystem;
1620
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
1721
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
1822

@@ -60,9 +64,15 @@ public void setup() {
6064
getCommandRegistry().registerCommand(new PlaytimeCommand(this));
6165
getCommandRegistry().registerCommand(new ClaimHelpCommand(this));
6266

63-
// Register protection event listeners
67+
// Register protection event listeners (for PlayerInteractEvent)
6468
protectionListener = new ClaimProtectionListener(this);
6569
protectionListener.register(getEventRegistry());
70+
71+
// Register ECS block protection systems
72+
getEntityStoreRegistry().registerSystem(new BlockDamageProtectionSystem(claimManager));
73+
getEntityStoreRegistry().registerSystem(new BlockBreakProtectionSystem(claimManager));
74+
getEntityStoreRegistry().registerSystem(new BlockPlaceProtectionSystem(claimManager));
75+
getEntityStoreRegistry().registerSystem(new BlockUseProtectionSystem(claimManager));
6676
}
6777

6878
@Override

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

Lines changed: 29 additions & 203 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,12 @@
11
package com.landclaims.listeners;
22

3-
import com.hypixel.hytale.component.Ref;
4-
import com.hypixel.hytale.component.Store;
53
import com.hypixel.hytale.event.EventRegistry;
64
import com.hypixel.hytale.math.vector.Vector3i;
7-
import com.hypixel.hytale.server.core.entity.InteractionContext;
85
import com.hypixel.hytale.server.core.entity.entities.Player;
9-
import com.hypixel.hytale.server.core.event.events.ecs.BreakBlockEvent;
10-
import com.hypixel.hytale.server.core.event.events.ecs.DamageBlockEvent;
11-
import com.hypixel.hytale.server.core.event.events.ecs.PlaceBlockEvent;
12-
import com.hypixel.hytale.server.core.event.events.ecs.UseBlockEvent;
136
import com.hypixel.hytale.server.core.event.events.player.PlayerConnectEvent;
147
import com.hypixel.hytale.server.core.event.events.player.PlayerDisconnectEvent;
158
import com.hypixel.hytale.server.core.event.events.player.PlayerInteractEvent;
169
import com.hypixel.hytale.server.core.universe.PlayerRef;
17-
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
1810
import com.hypixel.hytale.protocol.InteractionType;
1911
import com.landclaims.LandClaims;
2012
import com.landclaims.managers.ClaimManager;
@@ -24,28 +16,18 @@
2416
import java.util.concurrent.ConcurrentHashMap;
2517

2618
/**
27-
* Listens for block and interaction events to protect claimed areas.
28-
*
29-
* Protection strategy:
30-
* 1. PlayerInteractEvent - Track which player is interacting with which block, cancel if protected
31-
* 2. UseBlockEvent.Pre - Prevent using blocks (chests, doors, etc.) in protected areas
32-
* 3. DamageBlockEvent - Prevent block damage in protected areas (blocks mining)
33-
* 4. BreakBlockEvent - Final safety net to prevent block breaking
34-
* 5. PlaceBlockEvent - Prevent block placing in protected areas
19+
* Listens for player interaction events to protect claimed areas.
20+
* Tracks player interactions to correlate with ECS block events handled by BlockProtectionSystems.
3521
*/
3622
public class ClaimProtectionListener {
3723
private final LandClaims plugin;
3824
private final ClaimManager claimManager;
3925

40-
// Track player interactions to correlate with ECS events
26+
// Track player interactions - shared with BlockProtectionSystems
4127
// Key: "x,y,z" block position, Value: PlayerInteraction data
42-
private final Map<String, PlayerInteraction> pendingInteractions = new ConcurrentHashMap<>();
28+
private static final Map<String, PlayerInteraction> pendingInteractions = new ConcurrentHashMap<>();
29+
private static final Map<UUID, PlayerInteraction> playerLastInteraction = new ConcurrentHashMap<>();
4330

44-
// Also track by player UUID for cases where block position differs (e.g., placing)
45-
// Key: player UUID, Value: PlayerInteraction with last interacted position
46-
private final Map<UUID, PlayerInteraction> playerLastInteraction = new ConcurrentHashMap<>();
47-
48-
// How long to keep interaction data (ms)
4931
private static final long INTERACTION_TIMEOUT_MS = 5000;
5032

5133
public ClaimProtectionListener(LandClaims plugin) {
@@ -54,26 +36,19 @@ public ClaimProtectionListener(LandClaims plugin) {
5436
}
5537

5638
/**
57-
* Register all protection event handlers.
39+
* Register player event handlers.
5840
*/
5941
public void register(EventRegistry eventRegistry) {
6042
// Player interaction - track who is interacting with what and cancel if protected
6143
eventRegistry.registerGlobal(PlayerInteractEvent.class, this::onPlayerInteract);
6244

63-
// ECS block events - these fire when blocks are actually modified
64-
eventRegistry.registerGlobal(UseBlockEvent.Pre.class, this::onUseBlock);
65-
eventRegistry.registerGlobal(DamageBlockEvent.class, this::onDamageBlock);
66-
eventRegistry.registerGlobal(BreakBlockEvent.class, this::onBreakBlock);
67-
eventRegistry.registerGlobal(PlaceBlockEvent.class, this::onPlaceBlock);
68-
6945
// Player join/leave for playtime tracking
7046
eventRegistry.register(PlayerConnectEvent.class, this::onPlayerConnect);
7147
eventRegistry.registerGlobal(PlayerDisconnectEvent.class, this::onPlayerDisconnect);
7248
}
7349

7450
/**
7551
* Track player interactions and cancel if in protected area.
76-
* This fires BEFORE ECS block events.
7752
*/
7853
private void onPlayerInteract(PlayerInteractEvent event) {
7954
Player player = event.getPlayer();
@@ -84,196 +59,40 @@ private void onPlayerInteract(PlayerInteractEvent event) {
8459

8560
UUID playerId = player.getUuid();
8661
String worldName = "default";
87-
InteractionType actionType = event.getActionType();
8862

8963
// Track this interaction for ECS event correlation
9064
String blockKey = getBlockKey(targetBlock);
9165
PlayerInteraction interaction = new PlayerInteraction(playerId, worldName, targetBlock, System.currentTimeMillis());
9266
pendingInteractions.put(blockKey, interaction);
9367
playerLastInteraction.put(playerId, interaction);
9468

95-
// Clean up old interactions periodically
69+
// Clean up old interactions
9670
cleanupOldInteractions();
9771

98-
// Check if this location is protected
72+
// Check if this location is protected - cancel ALL interactions in protected areas
9973
if (!claimManager.canInteract(playerId, worldName, targetBlock.getX(), targetBlock.getZ())) {
10074
event.setCancelled(true);
10175
}
10276
}
10377

104-
/**
105-
* Prevent using blocks (chests, doors, buttons, etc.) in protected areas.
106-
*/
107-
private void onUseBlock(UseBlockEvent.Pre event) {
108-
Vector3i targetBlock = event.getTargetBlock();
109-
if (targetBlock == null) return;
110-
111-
// Try to get player from interaction context
112-
InteractionContext context = event.getContext();
113-
UUID playerId = getPlayerFromContext(context);
114-
115-
if (playerId != null) {
116-
String worldName = "default";
117-
if (!claimManager.canInteract(playerId, worldName, targetBlock.getX(), targetBlock.getZ())) {
118-
event.setCancelled(true);
119-
}
120-
} else {
121-
// Fallback: check tracked interactions
122-
String blockKey = getBlockKey(targetBlock);
123-
PlayerInteraction interaction = pendingInteractions.get(blockKey);
124-
125-
if (interaction != null && !interaction.isExpired()) {
126-
if (!claimManager.canInteract(interaction.playerId, interaction.worldName,
127-
targetBlock.getX(), targetBlock.getZ())) {
128-
event.setCancelled(true);
129-
}
130-
} else {
131-
// No player info - block if claimed (conservative)
132-
String worldName = "default";
133-
if (isProtectedWithoutAccess(worldName, targetBlock)) {
134-
event.setCancelled(true);
135-
}
136-
}
137-
}
138-
}
139-
140-
/**
141-
* Prevent block damage in protected areas.
142-
*/
143-
private void onDamageBlock(DamageBlockEvent event) {
144-
Vector3i targetBlock = event.getTargetBlock();
145-
if (targetBlock == null) return;
146-
147-
String blockKey = getBlockKey(targetBlock);
148-
PlayerInteraction interaction = pendingInteractions.get(blockKey);
149-
150-
if (interaction != null && !interaction.isExpired()) {
151-
if (!claimManager.canInteract(interaction.playerId, interaction.worldName,
152-
targetBlock.getX(), targetBlock.getZ())) {
153-
event.setCancelled(true);
154-
}
155-
} else {
156-
// Try to find any recent interaction near this block
157-
interaction = findNearbyInteraction(targetBlock);
158-
if (interaction != null) {
159-
if (!claimManager.canInteract(interaction.playerId, interaction.worldName,
160-
targetBlock.getX(), targetBlock.getZ())) {
161-
event.setCancelled(true);
162-
}
163-
} else {
164-
// No player info - block if claimed
165-
String worldName = "default";
166-
if (isProtectedWithoutAccess(worldName, targetBlock)) {
167-
event.setCancelled(true);
168-
}
169-
}
170-
}
171-
}
172-
173-
/**
174-
* Final safety net - prevent block breaking in protected areas.
175-
*/
176-
private void onBreakBlock(BreakBlockEvent event) {
177-
Vector3i targetBlock = event.getTargetBlock();
178-
if (targetBlock == null) return;
179-
180-
String blockKey = getBlockKey(targetBlock);
181-
PlayerInteraction interaction = pendingInteractions.get(blockKey);
182-
183-
if (interaction != null && !interaction.isExpired()) {
184-
if (!claimManager.canInteract(interaction.playerId, interaction.worldName,
185-
targetBlock.getX(), targetBlock.getZ())) {
186-
event.setCancelled(true);
187-
}
188-
pendingInteractions.remove(blockKey);
189-
} else {
190-
interaction = findNearbyInteraction(targetBlock);
191-
if (interaction != null) {
192-
if (!claimManager.canInteract(interaction.playerId, interaction.worldName,
193-
targetBlock.getX(), targetBlock.getZ())) {
194-
event.setCancelled(true);
195-
}
196-
} else {
197-
String worldName = "default";
198-
if (isProtectedWithoutAccess(worldName, targetBlock)) {
199-
event.setCancelled(true);
200-
}
201-
}
202-
}
203-
}
204-
205-
/**
206-
* Prevent block placing in protected areas.
207-
*/
208-
private void onPlaceBlock(PlaceBlockEvent event) {
209-
Vector3i targetBlock = event.getTargetBlock();
210-
if (targetBlock == null) return;
211-
212-
String blockKey = getBlockKey(targetBlock);
78+
// Static accessors for BlockProtectionSystems
79+
public static PlayerInteraction getInteraction(String blockKey) {
21380
PlayerInteraction interaction = pendingInteractions.get(blockKey);
214-
215-
if (interaction == null || interaction.isExpired()) {
216-
interaction = findNearbyInteraction(targetBlock);
217-
}
218-
21981
if (interaction != null && !interaction.isExpired()) {
220-
if (!claimManager.canInteract(interaction.playerId, interaction.worldName,
221-
targetBlock.getX(), targetBlock.getZ())) {
222-
event.setCancelled(true);
223-
}
224-
} else {
225-
String worldName = "default";
226-
if (isProtectedWithoutAccess(worldName, targetBlock)) {
227-
event.setCancelled(true);
228-
}
229-
}
230-
}
231-
232-
/**
233-
* Try to extract player UUID from an InteractionContext.
234-
*/
235-
private UUID getPlayerFromContext(InteractionContext context) {
236-
if (context == null) return null;
237-
238-
try {
239-
Ref<EntityStore> entityRef = context.getEntity();
240-
if (entityRef == null) return null;
241-
242-
// The entity ref might give us access to player data
243-
// This is a best-effort attempt
244-
Ref<EntityStore> owningRef = context.getOwningEntity();
245-
if (owningRef != null) {
246-
// Try to get player component - this may not work directly
247-
// but we store the ref for potential future use
248-
}
249-
} catch (Exception e) {
250-
// Context access failed, fall back to tracking
82+
return interaction;
25183
}
252-
25384
return null;
25485
}
25586

256-
/**
257-
* Check if a block is in a claimed area.
258-
* Returns true if it should be blocked (claimed and no known accessor).
259-
*/
260-
private boolean isProtectedWithoutAccess(String worldName, Vector3i block) {
261-
UUID owner = claimManager.getOwnerAt(worldName, block.getX(), block.getZ());
262-
return owner != null;
263-
}
264-
265-
/**
266-
* Find a player interaction that was on or near a block.
267-
*/
268-
private PlayerInteraction findNearbyInteraction(Vector3i targetBlock) {
269-
// First check exact match
87+
public static PlayerInteraction findNearbyInteraction(Vector3i targetBlock) {
88+
// Check exact match first
27089
String blockKey = getBlockKey(targetBlock);
27190
PlayerInteraction exact = pendingInteractions.get(blockKey);
27291
if (exact != null && !exact.isExpired()) {
27392
return exact;
27493
}
27594

276-
// Check all recent player interactions to find one adjacent to this block
95+
// Check adjacent blocks (for placing)
27796
for (PlayerInteraction interaction : playerLastInteraction.values()) {
27897
if (interaction.isExpired()) continue;
27998
if (interaction.blockPos == null) continue;
@@ -289,7 +108,11 @@ private PlayerInteraction findNearbyInteraction(Vector3i targetBlock) {
289108
return null;
290109
}
291110

292-
private String getBlockKey(Vector3i pos) {
111+
public static void removeInteraction(String blockKey) {
112+
pendingInteractions.remove(blockKey);
113+
}
114+
115+
public static String getBlockKey(Vector3i pos) {
293116
return pos.getX() + "," + pos.getY() + "," + pos.getZ();
294117
}
295118

@@ -315,20 +138,23 @@ private void onPlayerDisconnect(PlayerDisconnectEvent event) {
315138
}
316139
}
317140

318-
private static class PlayerInteraction {
319-
final UUID playerId;
320-
final String worldName;
321-
final Vector3i blockPos;
322-
final long timestamp;
141+
/**
142+
* Tracks a player's interaction with a block.
143+
*/
144+
public static class PlayerInteraction {
145+
public final UUID playerId;
146+
public final String worldName;
147+
public final Vector3i blockPos;
148+
public final long timestamp;
323149

324-
PlayerInteraction(UUID playerId, String worldName, Vector3i blockPos, long timestamp) {
150+
public PlayerInteraction(UUID playerId, String worldName, Vector3i blockPos, long timestamp) {
325151
this.playerId = playerId;
326152
this.worldName = worldName;
327153
this.blockPos = blockPos;
328154
this.timestamp = timestamp;
329155
}
330156

331-
boolean isExpired() {
157+
public boolean isExpired() {
332158
return System.currentTimeMillis() - timestamp > INTERACTION_TIMEOUT_MS;
333159
}
334160
}

0 commit comments

Comments
 (0)