From 12c7d434a6112df2f69aa711b093735d737eda5e Mon Sep 17 00:00:00 2001 From: CJ Burkey Date: Wed, 28 Aug 2024 21:51:15 -0400 Subject: [PATCH] Start integrating flags with event handler --- .../com/cjburkey/claimchunk/ClaimChunk.java | 6 +- .../config/ClaimChunkWorldProfileHandler.java | 9 +- .../event/WorldProfileEventHandler.java | 224 +++++++++++------- .../com/cjburkey/claimchunk/flag/CCFlags.java | 8 +- .../cjburkey/claimchunk/flag/CCPermFlags.java | 69 +++--- .../cjburkey/claimchunk/flag/FlagHandler.java | 177 +++++++++++++- src/main/resources/flags.yml | 3 + 7 files changed, 359 insertions(+), 137 deletions(-) diff --git a/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java b/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java index 2efce07..f118876 100644 --- a/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java +++ b/src/main/java/com/cjburkey/claimchunk/ClaimChunk.java @@ -103,8 +103,6 @@ public final class ClaimChunk extends JavaPlugin implements IClaimChunkPlugin { @Getter private ChunkHandler chunkHandler; // An instance of the player handler @Getter private PlayerHandler playerHandler; - // An instance of the permission flag handler - @Getter private FlagHandler flagHandler; // An instance of the rank handler @Getter private RankHandler rankHandler; // An instance of the world permissions manager @@ -116,6 +114,8 @@ public final class ClaimChunk extends JavaPlugin implements IClaimChunkPlugin { @Getter private ChunkOutlineHandler chunkOutlineHandler; @Getter private CCGuiHandler guiHandler; + // An instance of the permission flag handler + @Getter private FlagHandler flagHandler; @Getter private CCInteractClasses interactClasses; @Getter private CCPermFlags permFlags; @@ -288,7 +288,7 @@ public void onEnable() { chunkHandler = new ChunkHandler(dataHandler, this); playerHandler = new PlayerHandler(dataHandler, this); - flagHandler = new FlagHandler(dataHandler, this); + flagHandler = new FlagHandler(permFlags, dataHandler); // As of version 0.0.23, the `ranks.json` file will be located in // `/plugins/ClaimChunk` instead of `/plugins/ClaimChunk/data` to make diff --git a/src/main/java/com/cjburkey/claimchunk/config/ClaimChunkWorldProfileHandler.java b/src/main/java/com/cjburkey/claimchunk/config/ClaimChunkWorldProfileHandler.java index 88f46fe..fe3bd61 100644 --- a/src/main/java/com/cjburkey/claimchunk/config/ClaimChunkWorldProfileHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/config/ClaimChunkWorldProfileHandler.java @@ -247,6 +247,7 @@ public void unloadAllProfiles() { && Monster.class.isAssignableFrom( entityType.getEntityClass())) .forEach(monsters::add); + entityAccessMapping.put("MONSTERS", monsters); // Add the hanging entities (item frames, leads, paintings) HashSet hangingEntities = new HashSet<>(); @@ -258,6 +259,7 @@ public void unloadAllProfiles() { && Hanging.class.isAssignableFrom( entityType.getEntityClass())) .forEach(hangingEntities::add); + entityAccessMapping.put("HANGING_ENTITIES", hangingEntities); // Add all animals HashSet animals = new HashSet<>(); @@ -268,6 +270,7 @@ public void unloadAllProfiles() { && Animals.class.isAssignableFrom( entityType.getEntityClass())) .forEach(animals::add); + entityAccessMapping.put("ANIMALS", animals); // Add mine-carts and boats HashSet vehicles = new HashSet<>(); @@ -280,6 +283,7 @@ public void unloadAllProfiles() { || Boat.class.isAssignableFrom( entityType.getEntityClass()))) .forEach(vehicles::add); + entityAccessMapping.put("VEHICLES", vehicles); // Containers (Keep up to date? Need to work on this) HashSet containers = new HashSet<>(); @@ -288,11 +292,6 @@ public void unloadAllProfiles() { EntityType.CHEST_BOAT, EntityType.CHEST_MINECART, EntityType.HOPPER_MINECART); - - entityAccessMapping.put("MONSTERS", monsters); - entityAccessMapping.put("HANGING_ENTITIES", hangingEntities); - entityAccessMapping.put("ANIMALS", animals); - entityAccessMapping.put("VEHICLES", vehicles); entityAccessMapping.put("CONTAINER_ENTITIES", containers); return entityAccessMapping; diff --git a/src/main/java/com/cjburkey/claimchunk/event/WorldProfileEventHandler.java b/src/main/java/com/cjburkey/claimchunk/event/WorldProfileEventHandler.java index 10770a8..8f08526 100644 --- a/src/main/java/com/cjburkey/claimchunk/event/WorldProfileEventHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/event/WorldProfileEventHandler.java @@ -8,6 +8,9 @@ import com.cjburkey.claimchunk.config.access.BlockAccess; import com.cjburkey.claimchunk.config.access.EntityAccess; import com.cjburkey.claimchunk.config.spread.SpreadProfile; +import com.cjburkey.claimchunk.flag.CCFlags; +import com.cjburkey.claimchunk.flag.CCPermFlags; +import com.cjburkey.claimchunk.flag.FlagHandler; import com.cjburkey.claimchunk.i18n.V2JsonMessages; import org.bukkit.*; @@ -36,6 +39,7 @@ import java.util.*; import java.util.function.Function; +import java.util.regex.Pattern; @SuppressWarnings("unused") public record WorldProfileEventHandler(ClaimChunk claimChunk) implements Listener { @@ -51,7 +55,8 @@ public void onEntityInteraction(PlayerInteractEntityEvent event) { () -> event.setCancelled(true), event.getPlayer(), event.getRightClicked(), - EntityAccess.EntityAccessType.INTERACT); + EntityAccess.EntityAccessType.INTERACT, + CCFlags.EntityFlagType.INTERACT); } } @@ -70,7 +75,8 @@ public void onEntityDamage(EntityDamageByEntityEvent event) { () -> event.setCancelled(true), player, event.getEntity(), - EntityAccess.EntityAccessType.DAMAGE); + EntityAccess.EntityAccessType.DAMAGE, + CCFlags.EntityFlagType.DAMAGE); } } } @@ -88,7 +94,8 @@ public void onPlayerFish(PlayerFishEvent event) { () -> event.setCancelled(true), event.getPlayer(), caught, - EntityAccess.EntityAccessType.DAMAGE); + EntityAccess.EntityAccessType.DAMAGE, + CCFlags.EntityFlagType.DAMAGE); } } } @@ -116,7 +123,8 @@ public void onEntityPush(VehicleEntityCollisionEvent event) { () -> event.setCancelled(true), player, event.getVehicle(), - EntityAccess.EntityAccessType.INTERACT); + EntityAccess.EntityAccessType.INTERACT, + CCFlags.EntityFlagType.INTERACT); } } } @@ -131,7 +139,8 @@ public void onBlockBreak(BlockBreakEvent event) { event.getPlayer(), event.getBlock().getType(), event.getBlock(), - BlockAccess.BlockAccessType.BREAK); + BlockAccess.BlockAccessType.BREAK, + CCFlags.BlockFlagType.BREAK); } } @@ -163,7 +172,8 @@ public void onBlockPlace(BlockPlaceEvent event) { event.getPlayer(), event.getBlock().getType(), event.getBlock(), - BlockAccess.BlockAccessType.PLACE); + BlockAccess.BlockAccessType.PLACE, + CCFlags.BlockFlagType.PLACE); } /** Event handler for when a player right-clicks on a block. */ @@ -183,7 +193,8 @@ public void onBlockInteraction(PlayerInteractEvent event) { event.getPlayer(), event.getClickedBlock().getType(), event.getClickedBlock(), - BlockAccess.BlockAccessType.INTERACT); + BlockAccess.BlockAccessType.INTERACT, + CCFlags.BlockFlagType.INTERACT); } } @@ -209,8 +220,11 @@ public void onPearlLaunch(ProjectileLaunchEvent event) { // Get the profile for this world var profile = claimChunk.getProfileHandler().getProfile(entity.getWorld().getName()); + CCPermFlags permFlags = claimChunk.getPermFlags(); + FlagHandler flagHandler = claimChunk.getFlagHandler(); + // check if the world profile is enabled - if (profile.enabled && profile.preventPearlFromClaims) { + if (profile.enabled && (profile.preventPearlFromClaims || permFlags.pearlFlag != null)) { final UUID ply = player.getUniqueId(); // check if the player has AdminOverride // do an early return @@ -219,29 +233,28 @@ public void onPearlLaunch(ProjectileLaunchEvent event) { final Chunk chunk = entity.getLocation().getChunk(); // If the chunk is unowned, allow the event to pass final UUID chunkOwner = claimChunk.getChunkHandler().getOwner(chunk); - if (chunkOwner == null) { - return; - } - // If the launcher is the owner or has access to this chunk, allow the event to pass - final boolean isOwner = chunkOwner.equals(ply); - // TODO: - /*if (isOwner - || claimChunk - .getPlayerHandler() - .hasPermission("interactEntities", new ChunkPos(chunk), ply)) { + if (chunkOwner == null || chunkOwner.equals(ply)) { return; - }*/ + } // Otherwise, if the owner's chunks aren't susceptible to damage, don't allow players to // bypass this. - if (shouldProtectOwnerChunks(chunkOwner, claimChunk.getServer(), profile)) { - // Cancel event - event.setCancelled(true); + if (!shouldProtectOwnerChunks(chunkOwner, claimChunk.getServer(), profile)) { + return; + } - // Send cancellation message - V2JsonMessages.sendAccessDeniedPearlMessage(player, claimChunk, chunkOwner); + final ChunkPos chunkPos = new ChunkPos(chunk); + if (!flagHandler.queryProtectionSimple( + chunkOwner, ply, chunkPos, permFlags.pearlFlag)) { + return; } + + // Cancel event + event.setCancelled(true); + + // Send cancellation message + V2JsonMessages.sendAccessDeniedPearlMessage(player, claimChunk, chunkOwner); } } @@ -268,7 +281,8 @@ public void onHangingBreak(HangingBreakByEntityEvent event) { () -> event.setCancelled(true), player, event.getEntity(), - EntityAccess.EntityAccessType.DAMAGE); + EntityAccess.EntityAccessType.DAMAGE, + CCFlags.EntityFlagType.DAMAGE); } } } @@ -283,7 +297,8 @@ public void onHangingPlace(HangingPlaceEvent event) { () -> event.setCancelled(true), event.getPlayer(), event.getEntity(), - EntityAccess.EntityAccessType.INTERACT); + EntityAccess.EntityAccessType.INTERACT, + CCFlags.EntityFlagType.INTERACT); } } @@ -300,7 +315,8 @@ public void onLiquidPickup(PlayerBucketFillEvent event) { event.getPlayer(), event.getBlock().getType(), event.getBlock(), - BlockAccess.BlockAccessType.BREAK); + BlockAccess.BlockAccessType.BREAK, + CCFlags.BlockFlagType.BREAK); } /** Event handler for when players put down a liquid with a bucket. */ @@ -320,7 +336,8 @@ public void onLiquidPlace(PlayerBucketEmptyEvent event) { event.getPlayer(), bucketLiquid, event.getBlock(), - BlockAccess.BlockAccessType.PLACE); + BlockAccess.BlockAccessType.PLACE, + CCFlags.BlockFlagType.PLACE); } /** Event handler for when players capture entities (fish and stuff) in a bucket. */ @@ -333,7 +350,8 @@ public void onFishCapture(PlayerBucketEntityEvent event) { () -> event.setCancelled(true), event.getPlayer(), event.getEntity(), - EntityAccess.EntityAccessType.INTERACT); + EntityAccess.EntityAccessType.INTERACT, + CCFlags.EntityFlagType.INTERACT); } /* Leads */ @@ -348,7 +366,8 @@ public void onLeadCreate(PlayerLeashEntityEvent event) { () -> event.setCancelled(true), event.getPlayer(), event.getEntity(), - EntityAccess.EntityAccessType.INTERACT); + EntityAccess.EntityAccessType.INTERACT, + CCFlags.EntityFlagType.INTERACT); } /** Event handler for when players break a lead. */ @@ -361,7 +380,8 @@ public void onLeadDestroy(PlayerUnleashEntityEvent event) { () -> event.setCancelled(true), event.getPlayer(), event.getEntity(), - EntityAccess.EntityAccessType.INTERACT); + EntityAccess.EntityAccessType.INTERACT, + CCFlags.EntityFlagType.INTERACT); } // Armor Stands @@ -376,7 +396,8 @@ public void onArmorStandManipulate(PlayerArmorStandManipulateEvent event) { () -> event.setCancelled(true), event.getPlayer(), event.getRightClicked(), - EntityAccess.EntityAccessType.INTERACT); + EntityAccess.EntityAccessType.INTERACT, + CCFlags.EntityFlagType.INTERACT); } // Explosion protection for entities @@ -572,6 +593,7 @@ public void onBonemeal(BlockFertilizeEvent event) { blockState.getType(), blockState.getBlock(), BlockAccess.BlockAccessType.PLACE, + CCFlags.BlockFlagType.PLACE, false)); }) .forEach(remove::add); @@ -582,12 +604,12 @@ public void onBonemeal(BlockFertilizeEvent event) { // -- HELPER METHODS -- // - @Deprecated private void onEntityEvent( @NotNull Runnable cancel, @NotNull Player player, @NotNull Entity entity, - @NotNull EntityAccess.EntityAccessType accessType) { + @NotNull EntityAccess.EntityAccessType accessType, + @NotNull CCFlags.EntityFlagType entityAccessType) { // Get the profile for this world ClaimChunkWorldProfile profile = @@ -603,25 +625,22 @@ private void onEntityEvent( final Chunk chunk = entity.getLocation().getChunk(); final UUID chunkOwner = claimChunk.getChunkHandler().getOwner(chunk); - final String entityClass = profile.getEntityClass(entity.getType()); - final String permissionNeeded = - entityClass != null && entityClass.equalsIgnoreCase("VEHICLES") - ? "interactVehicles" - : "interactEntities"; + if (chunkOwner != null) { + if (player.getUniqueId().equals(chunkOwner)) { + return; + } - final boolean isOwner = (chunkOwner != null && chunkOwner.equals(ply)); - final boolean isOwnerOrAccess = - // TODO: - false - /*isOwner - || (chunkOwner != null - && claimChunk - .getPlayerHandler() - .hasPermission( - permissionNeeded, new ChunkPos(chunk), ply))*/ ; + final ChunkPos chunkPos = new ChunkPos(chunk); + CCPermFlags permFlags = claimChunk.getPermFlags(); + FlagHandler flagHandler = claimChunk.getFlagHandler(); + if (!flagHandler.queryEntityProtection( + chunkOwner, ply, chunkPos, entity.getType(), entityAccessType)) { + return; + } + } // Delegate event cancellation to the world profile - if (!profile.canAccessEntity(chunkOwner != null, isOwnerOrAccess, entity, accessType) + if (!profile.canAccessEntity(chunkOwner != null, true, entity, accessType) && (chunkOwner == null || shouldProtectOwnerChunks( chunkOwner, claimChunk.getServer(), profile))) { @@ -635,7 +654,6 @@ private void onEntityEvent( } } - @Deprecated private void onBlockAdjacentCheck( @NotNull Runnable cancel, @NotNull Player player, @NotNull Block block) { // Get the current world profile @@ -677,9 +695,9 @@ private void onBlockAdjacentCheck( if (neighbor.getType() == block.getType() && neighborOwner != null && neighborOwner != chunkOwner - && !isOwnerOrAccess + && !isOwner && shouldProtectOwnerChunks( - chunkOwner, claimChunk.getServer(), profile)) { + neighborOwner, claimChunk.getServer(), profile)) { // cancel event cancel.run(); @@ -695,7 +713,8 @@ && shouldProtectOwnerChunks( claimChunk .getMessages() .chunkCancelAdjacentPlace - .replaceAll("%%PLAYER%%", ownerName), + .replaceAll( + Pattern.quote("%%PLAYER%%"), ownerName), "%%BLOCK%%", "block." + block.getType().getKey().getNamespace() @@ -716,19 +735,20 @@ private void onBlockEvent( @NotNull Player player, @NotNull Material blockType, @NotNull Block block, - @NotNull BlockAccess.BlockAccessType accessType) { - if (onBlockEvent(player, blockType, block, accessType, true)) { + @NotNull BlockAccess.BlockAccessType accessType, + @NotNull CCFlags.BlockFlagType blockAccessType) { + if (onBlockEvent(player, blockType, block, accessType, blockAccessType, true)) { cancel.run(); } } // Returns whether the event should be cancelled - @Deprecated private boolean onBlockEvent( @NotNull Player player, @NotNull Material blockType, @NotNull Block block, @NotNull BlockAccess.BlockAccessType accessType, + @NotNull CCFlags.BlockFlagType blockAccessType, boolean message) { // Get the profile for this world ClaimChunkWorldProfile profile = @@ -744,6 +764,20 @@ private boolean onBlockEvent( final Chunk chunk = block.getChunk(); final UUID chunkOwner = claimChunk.getChunkHandler().getOwner(chunk); + if (chunkOwner != null) { + if (player.getUniqueId().equals(chunkOwner)) { + return false; + } + + final ChunkPos chunkPos = new ChunkPos(chunk); + CCPermFlags permFlags = claimChunk.getPermFlags(); + FlagHandler flagHandler = claimChunk.getFlagHandler(); + if (!flagHandler.queryBlockProtection( + chunkOwner, ply, chunkPos, blockType, blockAccessType)) { + return false; + } + } + // TODO: /*String permissionNeeded = ""; String blockClass = profile.getBlockClass(blockType); @@ -802,13 +836,21 @@ private void onExplosionForEntityEvent(@NotNull Runnable cancel, @NotNull Entity ClaimChunkWorldProfile profile = claimChunk.getProfileHandler().getProfile(worldName); UUID chunkOwner = claimChunk.getChunkHandler().getOwner(entity.getLocation().getChunk()); + final ChunkPos chunkPos = new ChunkPos(entity.getLocation().getChunk()); + CCPermFlags permFlags = claimChunk.getPermFlags(); + FlagHandler flagHandler = claimChunk.getFlagHandler(); // Delegate event cancellation to the world profile if (profile.enabled - && (chunkOwner == null - || shouldProtectOwnerChunks(chunkOwner, claimChunk.getServer(), profile)) - && !profile.getEntityAccess(chunkOwner != null, worldName, entity.getType()) - .allowExplosion) { + // TODO: WORLD PROFILE REWRITE + && chunkOwner != null + && shouldProtectOwnerChunks(chunkOwner, claimChunk.getServer(), profile) + && flagHandler.queryEntityProtection( + chunkOwner, + null, + chunkPos, + entity.getType(), + CCFlags.EntityFlagType.EXPLODE)) { cancel.run(); } } @@ -821,13 +863,20 @@ private void onExplosionForBlockEvent(@NotNull Runnable cancel, @NotNull Block b ClaimChunkWorldProfile profile = claimChunk.getProfileHandler().getProfile(worldName); UUID chunkOwner = claimChunk.getChunkHandler().getOwner(block.getChunk()); + final ChunkPos chunkPos = new ChunkPos(block.getChunk()); + CCPermFlags permFlags = claimChunk.getPermFlags(); + FlagHandler flagHandler = claimChunk.getFlagHandler(); // Delegate event cancellation to the world profile if (profile.enabled - && (chunkOwner == null - || shouldProtectOwnerChunks(chunkOwner, claimChunk.getServer(), profile)) - && !profile.getBlockAccess(chunkOwner != null, worldName, block.getType()) - .allowExplosion) { + && chunkOwner != null + && shouldProtectOwnerChunks(chunkOwner, claimChunk.getServer(), profile) + && flagHandler.queryBlockProtection( + chunkOwner, + null, + chunkPos, + block.getType(), + CCFlags.BlockFlagType.EXPLODE)) { cancel.run(); } } @@ -844,31 +893,30 @@ private void onExplosionEvent(@NotNull World world, @NotNull Collection b // Get chunk handler final ChunkHandler chunkHandler = claimChunk.getChunkHandler(); - // Cache chunks to avoid so many look-ups through the chunk handler. The value is a - // boolean representing whether to cancel the event. `true` means the event will be - // cancelled. - final HashMap cancelChunks = new HashMap<>(); final ArrayList blocksCopy = new ArrayList<>(blockList); + CCPermFlags permFlags = claimChunk.getPermFlags(); + FlagHandler flagHandler = claimChunk.getFlagHandler(); + // Loop through all of the blocks for (Block block : blocksCopy) { // Get the chunk this block is in - final Chunk chunk = block.getChunk(); + final ChunkPos chunkPos = new ChunkPos(block.getChunk()); + UUID owner = chunkHandler.getOwner(chunkPos); - // Check if this type of block should be protected - if (cancelChunks.computeIfAbsent( - chunk, - c -> { - // Google format why - UUID owner = chunkHandler.getOwner(c); - return (owner == null - || shouldProtectOwnerChunks( - owner, claimChunk.getServer(), worldProfile)) - && !worldProfile.getBlockAccess( - owner != null, worldName, block.getType()) - .allowExplosion; - })) { + // TODO: THIS + if (owner == null) { + continue; + } + // Check if this type of block should be protected + if (shouldProtectOwnerChunks(owner, claimChunk.getServer(), worldProfile) + && flagHandler.queryBlockProtection( + owner, + null, + chunkPos, + block.getType(), + CCFlags.BlockFlagType.EXPLODE)) { // Try to remove the block from the explosion list if (!blockList.remove(block)) { Utils.err( @@ -931,7 +979,6 @@ private void onSpreadEvent( } } - @Deprecated private void onPistonAction( @NotNull Runnable cancel, @NotNull Block piston, @@ -999,12 +1046,7 @@ private void onPistonAction( Chunk chunk = entry.getKey(); UUID owner = entry.getValue(); - if (owner != null && !owner.equals(sourceChunkOwner) - // TODO: - /*&& !claimChunk - .getPlayerHandler() - .hasPermission( - "break", new ChunkPos(chunk), sourceChunkOwner)*/ ) { + if (owner != null && !owner.equals(sourceChunkOwner)) { cancel.run(); return; } @@ -1094,7 +1136,7 @@ private static boolean isWaterlogged(@NotNull Block block) { // player chunks aren't // protected. private static boolean shouldProtectOwnerChunks( - UUID owner, Server server, ClaimChunkWorldProfile profile) { + @NotNull UUID owner, @NotNull Server server, @NotNull ClaimChunkWorldProfile profile) { boolean ownerOnline = server.getOfflinePlayer(owner).isOnline(); return (!ownerOnline || profile.protectOnline) && (ownerOnline || profile.protectOffline); } diff --git a/src/main/java/com/cjburkey/claimchunk/flag/CCFlags.java b/src/main/java/com/cjburkey/claimchunk/flag/CCFlags.java index 231791c..0f13a55 100644 --- a/src/main/java/com/cjburkey/claimchunk/flag/CCFlags.java +++ b/src/main/java/com/cjburkey/claimchunk/flag/CCFlags.java @@ -47,10 +47,14 @@ public boolean doesProtect(boolean isFlagEnabled) { } } - public record ProtectingFlag(String name, CCFlags.FlagData flagData) {} + public record SimpleFlag(@NotNull String name, @NotNull ProtectWhen protectWhen) {} + + public record ProtectingFlag(@NotNull String name, @NotNull CCFlags.FlagData flagData) {} public record FlagData( - ProtectWhen protectWhen, @NotNull Set include, @NotNull Set exclude) {} + @NotNull ProtectWhen protectWhen, + @NotNull Set include, + @NotNull Set exclude) {} public record BlockFlagData(@NotNull BlockFlagType flagType, @NotNull FlagData flagData) implements IFlagData {} diff --git a/src/main/java/com/cjburkey/claimchunk/flag/CCPermFlags.java b/src/main/java/com/cjburkey/claimchunk/flag/CCPermFlags.java index 6e7e0f8..c0dcf21 100644 --- a/src/main/java/com/cjburkey/claimchunk/flag/CCPermFlags.java +++ b/src/main/java/com/cjburkey/claimchunk/flag/CCPermFlags.java @@ -17,6 +17,7 @@ import java.io.InputStreamReader; import java.util.*; import java.util.function.BiFunction; +import java.util.function.Function; import java.util.function.Predicate; /** @@ -28,7 +29,8 @@ public class CCPermFlags { public final HashMap blockControls = new HashMap<>(); public final HashMap entityControls = new HashMap<>(); - public final HashSet pvpControls = new HashSet<>(); + public @Nullable CCFlags.SimpleFlag pvpFlag = null; + public @Nullable CCFlags.SimpleFlag pearlFlag = null; private final HashSet allFlags = new HashSet<>(); private final CCInteractClasses interactClasses; @@ -110,8 +112,6 @@ public void load( /** * Get which flag (if any) should protect the given entity based on whether the flag is enabled. - * This method also handles inverting protection based on the {@code protectWhen: ENABLED} flag - * option. * * @param entityType The Bukkit type of the entity to query. * @param interactionType The type of entity operation to check. @@ -140,7 +140,7 @@ public void load( return null; } - // Whether the given block applies to a flag, given its include and exclude sets + // Whether the given block/entity applies to a flag, given its include and exclude sets private boolean flagApplies( // Pass in the type to allow lambda references to `typeMatches` // Looks weird but makes me have to type less in two (2) other places! @@ -150,8 +150,8 @@ private boolean flagApplies( @Nullable Set exclude) { Predicate predicate = excludeStr -> typeMatchMethod.apply(t, excludeStr); - // Exclusions override inclusions; excluded blocks don't match, because - // they're excluded 😲 + // Exclusions override inclusions; excluded blocks/entities don't + // match, because they're excluded 😲 if (exclude != null) { if (exclude.stream().anyMatch(predicate)) { return false; @@ -166,31 +166,33 @@ private boolean flagApplies( // If we have reached this point, both of these must be true: // - Exclusions are not provided or don't match this block // - Include must be null, so everything matches. - // Therefore, this flag DOES match the given block! Yay! + // Therefore, this flag DOES match the given block/entity! Yay! return true; } private boolean typeMatches(@NotNull Material blockType, @NotNull String inputStr) { - String input = inputStr.trim(); - - if (input.startsWith("@")) { - String className = input.substring(1).trim(); - return interactClasses.getBlockClasses(blockType).contains(className); - } else { - // parsedType is null if material is not found, so no exceptions - return Utils.materialFromString(input) == blockType; - } + return typeMatch( + blockType, inputStr, interactClasses::getBlockClasses, Utils::materialFromString); } private boolean typeMatches(@NotNull EntityType entityType, @NotNull String inputStr) { + return typeMatch( + entityType, inputStr, interactClasses::getEntityClasses, Utils::entityFromString); + } + + private static boolean typeMatch( + @NotNull T type, + @NotNull String inputStr, + Function> getClasses, + Function fromString) { String input = inputStr.trim(); if (input.startsWith("@")) { String className = input.substring(1).trim(); - return interactClasses.getEntityClasses(entityType).contains(className); + return getClasses.apply(type).contains(className); } else { - // parsedType is null if material is not found, so no exceptions - return Utils.entityFromString(input) == entityType; + // parsedType is null if material/entity type is not found, so no exceptions + return fromString.apply(input) == type; } } @@ -284,12 +286,22 @@ public void loadFromConfig(@NotNull YamlConfiguration config) { entityControls.put(flagName, entityFlagData); } case "PLAYERS" -> { - if (pvpControls.contains(flagName)) { - Utils.err("Flag \"%s\" already has pvp protection", flagName); + if (pvpFlag != null) { + Utils.err("Flag \"%s\" already handles pvp protection", pvpFlag.name()); continue; } allFlags.add(flagName); - pvpControls.add(flagName); + pvpFlag = new CCFlags.SimpleFlag(flagName, readProtectWhen(flagMap)); + } + case "PEARLS" -> { + if (pearlFlag != null) { + Utils.err( + "Flag \"%s\" already handles ender pearl protection", + pearlFlag.name()); + continue; + } + allFlags.add(flagName); + pearlFlag = new CCFlags.SimpleFlag(flagName, readProtectWhen(flagMap)); } default -> Utils.err( @@ -386,11 +398,7 @@ public void loadFromConfig(@NotNull YamlConfiguration config) { private static @Nullable CCFlags.FlagData readFlagData(@NotNull Map flagMap) { try { - CCFlags.ProtectWhen protectWhen = - Optional.ofNullable(flagMap.get("protectWhen")) - .map(Object::toString) - .map(CCFlags.ProtectWhen::valueOf) - .orElse(CCFlags.ProtectWhen.DISABLED); + CCFlags.ProtectWhen protectWhen = readProtectWhen(flagMap); HashSet include = strSetFromStrList(flagMap.get("include")); HashSet exclude = strSetFromStrList(flagMap.get("exclude")); @@ -402,6 +410,13 @@ public void loadFromConfig(@NotNull YamlConfiguration config) { return null; } + private static CCFlags.ProtectWhen readProtectWhen(Map flagMap) { + return Optional.ofNullable(flagMap.get("protectWhen")) + .map(Object::toString) + .map(CCFlags.ProtectWhen::valueOf) + .orElse(CCFlags.ProtectWhen.DISABLED); + } + @SuppressWarnings("unchecked") private static @NotNull HashSet strSetFromStrList(@Nullable Object val) { return new HashSet<>( diff --git a/src/main/java/com/cjburkey/claimchunk/flag/FlagHandler.java b/src/main/java/com/cjburkey/claimchunk/flag/FlagHandler.java index e4a5f37..344569a 100644 --- a/src/main/java/com/cjburkey/claimchunk/flag/FlagHandler.java +++ b/src/main/java/com/cjburkey/claimchunk/flag/FlagHandler.java @@ -1,20 +1,179 @@ package com.cjburkey.claimchunk.flag; -import com.cjburkey.claimchunk.ClaimChunk; +import com.cjburkey.claimchunk.chunk.ChunkPos; import com.cjburkey.claimchunk.data.newdata.IClaimChunkDataHandler; -/** - * Interface between flag data and the rest of the plugin :) - * - * @since 0.0.26 - */ +import org.bukkit.Material; +import org.bukkit.entity.EntityType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + public class FlagHandler { + private final CCPermFlags permFlags; private final IClaimChunkDataHandler dataHandler; - private final ClaimChunk claimChunk; - public FlagHandler(IClaimChunkDataHandler dataHandler, ClaimChunk claimChunk) { + public FlagHandler( + @NotNull CCPermFlags permFlags, @NotNull IClaimChunkDataHandler dataHandler) { + this.permFlags = permFlags; this.dataHandler = dataHandler; - this.claimChunk = claimChunk; } + + public void setPermissionFlags( + @NotNull UUID owner, + @Nullable UUID accessor, + @Nullable ChunkPos chunk, + @NotNull HashMap newPerms) { + dataHandler.setPermissionFlags(owner, accessor, chunk, newPerms); + } + + public void clearPermissionFlags( + @NotNull UUID owner, + @Nullable UUID accessor, + @Nullable ChunkPos chunk, + @NotNull String... flagNames) { + dataHandler.clearPermissionFlags(owner, accessor, chunk, flagNames); + } + + public @NotNull Map getPlyFlags( + @NotNull UUID owner, @Nullable UUID accessor, @Nullable ChunkPos chunk) { + return dataHandler.getPlyFlags(owner, accessor, chunk); + } + + public boolean queryProtectionSimple( + @NotNull UUID chunkOwner, + @Nullable UUID accessor, + @NotNull ChunkPos chunkPos, + @Nullable CCFlags.SimpleFlag simpleFlag) { + if (simpleFlag != null) { + ApplicableFlags applicableFlags = getApplicableFlags(chunkOwner, accessor, chunkPos); + return doesProtect(applicableFlags, simpleFlag.name(), simpleFlag.protectWhen()); + } + return false; + } + + public boolean queryBlockProtection( + @NotNull UUID chunkOwner, + @Nullable UUID accessor, + @NotNull ChunkPos chunkPos, + @NotNull Material blockType, + @NotNull CCFlags.BlockFlagType interactionType) { + CCFlags.ProtectingFlag protectingFlag = + permFlags.getProtectingFlag(blockType, interactionType); + if (protectingFlag != null) { + ApplicableFlags applicableFlags = getApplicableFlags(chunkOwner, accessor, chunkPos); + return doesProtect( + applicableFlags, + protectingFlag.name(), + protectingFlag.flagData().protectWhen()); + } + return false; + } + + public boolean queryEntityProtection( + @NotNull UUID chunkOwner, + @Nullable UUID accessor, + @NotNull ChunkPos chunkPos, + @NotNull EntityType entityType, + @NotNull CCFlags.EntityFlagType interactionType) { + CCFlags.ProtectingFlag protectingFlag = + permFlags.getProtectingFlag(entityType, interactionType); + if (protectingFlag != null) { + ApplicableFlags applicableFlags = getApplicableFlags(chunkOwner, accessor, chunkPos); + return doesProtect( + applicableFlags, + protectingFlag.name(), + protectingFlag.flagData().protectWhen()); + } + + return false; + } + + private @NotNull FlagHandler.FlagProtectResult checkFlag( + @NotNull Map flagMap, + @NotNull String flagName, + @NotNull CCFlags.ProtectWhen protectWhen) { + Boolean flagValue = flagMap.get(flagName); + if (flagValue != null) { + return protectWhen.doesProtect(flagValue) + ? FlagProtectResult.Protected + : FlagProtectResult.Unprotected; + } + return FlagProtectResult.Unspecified; + } + + private ApplicableFlags getApplicableFlags( + @NotNull UUID chunkOwner, @Nullable UUID accessor, @NotNull ChunkPos chunkPos) { + Map chunkPlayerFlags = + accessor == null ? null : getPlyFlags(chunkOwner, accessor, chunkPos); + Map chunkFlags = getPlyFlags(chunkOwner, null, chunkPos); + Map playerFlags = + accessor == null ? null : getPlyFlags(chunkOwner, accessor, null); + Map globalFlags = getPlyFlags(chunkOwner, null, null); + + return new ApplicableFlags(chunkPlayerFlags, chunkFlags, playerFlags, globalFlags); + } + + private boolean doesProtect( + ApplicableFlags applicableFlags, String flagName, CCFlags.ProtectWhen protectWhen) { + FlagProtectResult result; + + if (applicableFlags.chunkPlayerFlags() != null) { + result = checkFlag(applicableFlags.chunkPlayerFlags(), flagName, protectWhen); + if (result.isSpecified()) { + return result.doesProtect(); + } + } + + result = checkFlag(applicableFlags.chunkFlags(), flagName, protectWhen); + if (result.isSpecified()) { + return result.doesProtect(); + } + + if (applicableFlags.playerFlags() != null) { + result = checkFlag(applicableFlags.playerFlags(), flagName, protectWhen); + if (result.isSpecified()) { + return result.doesProtect(); + } + } + + result = checkFlag(applicableFlags.globalFlags(), flagName, protectWhen); + if (result.isSpecified()) { + return result.doesProtect(); + } + + return false; + } + + public enum FlagProtectResult { + Protected, + Unprotected, + Unspecified; + + public boolean isSpecified() { + return this != Unspecified; + } + + public boolean doesProtect() { + return doesProtect(true); + } + + public boolean doesProtect(boolean ifUnspecified) { + return switch (this) { + case Protected -> true; + case Unprotected -> false; + case Unspecified -> ifUnspecified; + }; + } + } + + private record ApplicableFlags( + @Nullable Map chunkPlayerFlags, + @NotNull Map chunkFlags, + @Nullable Map playerFlags, + @NotNull Map globalFlags) {} } diff --git a/src/main/resources/flags.yml b/src/main/resources/flags.yml index 4e28ea6..35c37ca 100644 --- a/src/main/resources/flags.yml +++ b/src/main/resources/flags.yml @@ -70,6 +70,9 @@ permissionFlags: # PVP is unique, set the `for` to `PLAYERS`: - for: PLAYERS protectWhen: ENABLED + disablePearl: + - for: PEARLS + protectWhen: ENABLED # Can also handle both types with one flag containers: