Skip to content

Commit dc2022a

Browse files
committed
Add redstone permission bypass with cached checks
1 parent 6237278 commit dc2022a

File tree

4 files changed

+159
-1
lines changed

4 files changed

+159
-1
lines changed

src/main/java/me/crafter/mc/lockettepro/BlockEnvironmentListener.java

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package me.crafter.mc.lockettepro;
22

3-
import java.util.Iterator;
3+
import com.google.common.cache.Cache;
4+
import com.google.common.cache.CacheBuilder;
5+
6+
import java.util.concurrent.TimeUnit;
47

58
import org.bukkit.block.Block;
69
import org.bukkit.block.BlockState;
@@ -12,6 +15,7 @@
1215
import org.bukkit.event.EventHandler;
1316
import org.bukkit.event.EventPriority;
1417
import org.bukkit.event.Listener;
18+
import org.bukkit.event.block.BlockDispenseEvent;
1519
import org.bukkit.event.block.BlockExplodeEvent;
1620
import org.bukkit.event.block.BlockPistonExtendEvent;
1721
import org.bukkit.event.block.BlockPistonRetractEvent;
@@ -23,6 +27,10 @@
2327
import org.bukkit.event.world.StructureGrowEvent;
2428

2529
public class BlockEnvironmentListener implements Listener {
30+
private static final Cache<String, Boolean> redstoneDispenseLockCache = CacheBuilder.newBuilder()
31+
.maximumSize(4096)
32+
.expireAfterWrite(100, TimeUnit.MILLISECONDS)
33+
.build();
2634

2735
// Prevent explosion break block
2836
@EventHandler(priority = EventPriority.HIGH)
@@ -83,6 +91,37 @@ public void onBlockRedstoneChange(BlockRedstoneEvent event) {
8391
}
8492
}
8593

94+
// Prevent redstone-triggered dispensing from protected dispenser/dropper
95+
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
96+
public void onBlockDispense(BlockDispenseEvent event) {
97+
if (Config.isProtectionExempted("redstone")) return;
98+
if (isRedstoneDispenseLocked(event.getBlock())) {
99+
event.setCancelled(true);
100+
}
101+
}
102+
103+
private boolean isRedstoneDispenseLocked(Block block) {
104+
if (block == null) return false;
105+
106+
// PDC path already uses ContainerPdcLockManager runtime KV cache.
107+
if (ContainerPdcLockManager.isContainerBlock(block)) {
108+
ContainerPdcLockManager.LockData data = ContainerPdcLockManager.getLockData(block);
109+
if (data.hasPdcData()) {
110+
return LocketteProAPI.isContainerRedstoneEffectivelyLocked(block);
111+
}
112+
}
113+
114+
// Sign-only fallback path: keep a tiny local cache for high-frequency redstone ticks.
115+
String key = block.getWorld().getUID() + ":" + block.getX() + ":" + block.getY() + ":" + block.getZ();
116+
Boolean cached = redstoneDispenseLockCache.getIfPresent(key);
117+
if (cached != null) {
118+
return cached;
119+
}
120+
boolean locked = LocketteProAPI.isContainerRedstoneEffectivelyLocked(block);
121+
redstoneDispenseLockCache.put(key, locked);
122+
return locked;
123+
}
124+
86125
// Prevent villager open door
87126
@EventHandler(priority = EventPriority.HIGH)
88127
public void onVillagerOpenDoor(EntityInteractEvent event) {

src/main/java/me/crafter/mc/lockettepro/ContainerPdcLockManager.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public final class ContainerPdcLockManager {
5353
private static final String SUBJECT_HEX_ENCODING_PREFIX = "h_";
5454

5555
private static final String ENTITY_HOPPER = "#hopper";
56+
private static final String ENTITY_REDSTONE = "#redstone";
5657
private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();
5758
private static final String PLAYER_NAME_CACHE_MISS = "";
5859
private static volatile Cache<String, LockData> runtimeLockDataCache = createRuntimeLockDataCache();
@@ -372,6 +373,13 @@ public static boolean hasBypassTag(Block block) {
372373
return hasBypassTag(data.permissions);
373374
}
374375

376+
public static boolean hasRedstoneBypassTag(Block block) {
377+
if (!isContainerBlock(block)) return false;
378+
LockData data = getLockData(block);
379+
if (!data.hasPdcData() || !data.isLocked()) return false;
380+
return hasRedstoneBypassTag(data.permissions);
381+
}
382+
375383
public static boolean isContainerEffectivelyLocked(Block block) {
376384
if (!isContainerBlock(block)) return false;
377385
LockData data = getLockData(block);
@@ -592,6 +600,26 @@ private static boolean hasBypassTag(Map<String, PermissionAccess> permissions) {
592600
return false;
593601
}
594602

603+
private static boolean hasRedstoneBypassTag(Map<String, PermissionAccess> permissions) {
604+
for (Map.Entry<String, PermissionAccess> entry : permissions.entrySet()) {
605+
PermissionAccess access = entry.getValue();
606+
if (access == PermissionAccess.NONE) continue;
607+
if (!access.atLeast(PermissionAccess.READ_WRITE)) continue;
608+
String subject = entry.getKey();
609+
if (PermissionGroupStore.isGroupReference(subject)) {
610+
String groupName = PermissionGroupStore.extractGroupName(subject);
611+
if (groupName != null && PermissionGroupStore.groupAllowsEntity(groupName, ENTITY_REDSTONE)) {
612+
return true;
613+
}
614+
continue;
615+
}
616+
if (ENTITY_REDSTONE.equalsIgnoreCase(subject)) {
617+
return true;
618+
}
619+
}
620+
return false;
621+
}
622+
595623
private static void removePermissionKeys(PersistentDataContainer pdc) {
596624
List<NamespacedKey> toRemove = new ArrayList<>();
597625
for (NamespacedKey key : pdc.getKeys()) {

src/main/java/me/crafter/mc/lockettepro/LockettePro.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ private List<String> buildPermissionSubjectCandidates(CommandSender sender) {
190190
candidates.addAll(onlineNames);
191191

192192
candidates.add("#hopper");
193+
candidates.add("#redstone");
193194

194195
candidates.addAll(Config.getEveryoneSignStrings());
195196
candidates.addAll(Config.getContainerBypassSignStrings());

src/main/java/me/crafter/mc/lockettepro/LocketteProAPI.java

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
public class LocketteProAPI {
1616

17+
private static final String ENTITY_REDSTONE = "#redstone";
18+
1719
public static BlockFace[] newsfaces = {BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST};
1820
public static BlockFace[] allfaces = {BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST, BlockFace.UP, BlockFace.DOWN};
1921

@@ -323,6 +325,72 @@ private static boolean hasContainerBypassTagOnSign(Block block) {
323325
return false;
324326
}
325327

328+
public static boolean hasRedstoneBypassTag(Block block) {
329+
if (block == null) return false;
330+
if (ContainerPdcLockManager.isContainerBlock(block)) {
331+
ContainerPdcLockManager.LockData data = ContainerPdcLockManager.getLockData(block);
332+
if (data.hasPdcData()) {
333+
return data.isLocked() && ContainerPdcLockManager.hasRedstoneBypassTag(block);
334+
}
335+
}
336+
if (block.getBlockData() instanceof Door) {
337+
Block[] doors = getDoors(block);
338+
if (doors == null) return false;
339+
for (BlockFace doorface : newsfaces) {
340+
Block relative0 = doors[0].getRelative(doorface), relative1 = doors[1].getRelative(doorface);
341+
if (relative0.getType() == doors[0].getType() && relative1.getType() == doors[1].getType()) {
342+
if (hasRedstoneBypassTagSingleBlock(relative1.getRelative(BlockFace.UP), doorface.getOppositeFace()))
343+
return true;
344+
if (hasRedstoneBypassTagSingleBlock(relative1, doorface.getOppositeFace())) return true;
345+
if (hasRedstoneBypassTagSingleBlock(relative0, doorface.getOppositeFace())) return true;
346+
if (hasRedstoneBypassTagSingleBlock(relative0.getRelative(BlockFace.DOWN), doorface.getOppositeFace()))
347+
return true;
348+
}
349+
}
350+
if (hasRedstoneBypassTagSingleBlock(doors[1].getRelative(BlockFace.UP), null)) return true;
351+
if (hasRedstoneBypassTagSingleBlock(doors[1], null)) return true;
352+
if (hasRedstoneBypassTagSingleBlock(doors[0], null)) return true;
353+
if (hasRedstoneBypassTagSingleBlock(doors[0].getRelative(BlockFace.DOWN), null)) return true;
354+
} else if (block.getBlockData() instanceof Chest) {
355+
BlockFace chestface = getRelativeChestFace(block);
356+
if (chestface != null) {
357+
Block relativechest = block.getRelative(chestface);
358+
if (hasRedstoneBypassTagSingleBlock(relativechest, chestface.getOppositeFace())) return true;
359+
}
360+
}
361+
return hasRedstoneBypassTagSingleBlock(block, null);
362+
}
363+
364+
private static boolean hasRedstoneBypassTagSingleBlock(Block block, BlockFace exempt) {
365+
for (BlockFace blockface : newsfaces) {
366+
if (blockface == exempt) continue;
367+
Block relativeblock = block.getRelative(blockface);
368+
if (isLockSignOrAdditionalSign(relativeblock) && getFacing(relativeblock) == blockface) {
369+
if (hasRedstoneBypassTagOnSign(relativeblock)) {
370+
return true;
371+
}
372+
}
373+
}
374+
return false;
375+
}
376+
377+
private static boolean hasRedstoneBypassTagOnSign(Block block) {
378+
String[] lines = ((Sign) block.getState()).getSide(Side.FRONT).getLines();
379+
for (int i = 1; i < 4; i++) {
380+
String line = lines[i] == null ? "" : lines[i].trim();
381+
if (ENTITY_REDSTONE.equalsIgnoreCase(line)) {
382+
return true;
383+
}
384+
if (PermissionGroupStore.isGroupReference(line)) {
385+
String groupName = PermissionGroupStore.extractGroupName(line);
386+
if (groupName != null && PermissionGroupStore.groupAllowsEntity(groupName, ENTITY_REDSTONE)) {
387+
return true;
388+
}
389+
}
390+
}
391+
return false;
392+
}
393+
326394
public static boolean shouldBypassContainerRestriction(Block block) {
327395
if (block == null) return false;
328396
if (ContainerPdcLockManager.isContainerBlock(block)) {
@@ -345,6 +413,17 @@ public static boolean shouldBypassContainerTransferRestriction(Block block) {
345413
return hasContainerBypassTag(block);
346414
}
347415

416+
public static boolean shouldBypassContainerRedstoneRestriction(Block block) {
417+
if (block == null) return false;
418+
if (ContainerPdcLockManager.isContainerBlock(block)) {
419+
ContainerPdcLockManager.LockData data = ContainerPdcLockManager.getLockData(block);
420+
if (data.hasPdcData()) {
421+
return data.isLocked() && ContainerPdcLockManager.hasRedstoneBypassTag(block);
422+
}
423+
}
424+
return hasRedstoneBypassTag(block);
425+
}
426+
348427
public static boolean isContainerEffectivelyLocked(Block block) {
349428
if (block == null) return false;
350429
if (ContainerPdcLockManager.isContainerBlock(block)) {
@@ -367,6 +446,17 @@ public static boolean isContainerTransferEffectivelyLocked(Block block) {
367446
return isLocked(block) && !shouldBypassContainerTransferRestriction(block);
368447
}
369448

449+
public static boolean isContainerRedstoneEffectivelyLocked(Block block) {
450+
if (block == null) return false;
451+
if (ContainerPdcLockManager.isContainerBlock(block)) {
452+
ContainerPdcLockManager.LockData data = ContainerPdcLockManager.getLockData(block);
453+
if (data.hasPdcData()) {
454+
return data.isLocked() && !ContainerPdcLockManager.hasRedstoneBypassTag(block);
455+
}
456+
}
457+
return isLocked(block) && !shouldBypassContainerRedstoneRestriction(block);
458+
}
459+
370460
public static boolean isOwnerOfSign(Block block, Player player) { // Requires isSign
371461
Block protectedblock = getAttachedBlock(block);
372462
// Normal situation, that block is just locked by an adjacent sign

0 commit comments

Comments
 (0)