diff --git a/build.gradle.kts b/build.gradle.kts index 0a69f26..65c55aa 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -310,6 +310,7 @@ dependencies { testImplementation("org.slf4j:slf4j-simple:${DepData.SLF4J_VERSION}") testImplementation("org.junit.jupiter:junit-jupiter:${DepData.JUNIT_VERSION}") + testImplementation("org.spigotmc:spigot-api:${DepData.SPIGOT_VERSION}") testRuntimeOnly("org.junit.platform:junit-platform-launcher:${DepData.JUNIT_LAUNCHER_VERSION}") } diff --git a/src/main/java/com/cjburkey/claimchunk/Utils.java b/src/main/java/com/cjburkey/claimchunk/Utils.java index ddf5f34..ec890c0 100644 --- a/src/main/java/com/cjburkey/claimchunk/Utils.java +++ b/src/main/java/com/cjburkey/claimchunk/Utils.java @@ -1,16 +1,20 @@ package com.cjburkey.claimchunk; import com.cjburkey.claimchunk.placeholder.ClaimChunkPlaceholders; + import lombok.Getter; + import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.TextComponent; + import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; + import java.util.AbstractMap; import java.util.HashMap; import java.util.Map; @@ -19,13 +23,14 @@ public final class Utils { - private static final Logger log = Logger.getLogger("Minecraft"); + private static Logger log; private static ClaimChunk claimChunk; @Getter static boolean debugEnableOverride = false; static void init(ClaimChunk claimChunk) { Utils.claimChunk = claimChunk; + Utils.log = claimChunk.getLogger(); } static void overrideDebugEnable() { @@ -36,8 +41,15 @@ static void overrideDebugDisable() { debugEnableOverride = false; } + private static Logger getLogger() { + if (log == null) { + log = Logger.getLogger("Minecraft"); + } + return log; + } + public static void log(String msg, Object... data) { - log.info(prepMsg(msg, data)); + getLogger().info(prepMsg(msg, data)); } public static void debug(String msg, Object... data) { @@ -45,16 +57,16 @@ public static void debug(String msg, Object... data) { || claimChunk != null && claimChunk.getConfigHandler() != null && claimChunk.getConfigHandler().getDebugSpam()) { - log.info(prepMsg("[DEBUG] " + msg, data)); + getLogger().info(prepMsg("[DEBUG]" + msg, data)); } } public static void err(String msg, Object... data) { - log.severe(prepMsg(msg, data)); + getLogger().severe(prepMsg(msg, data)); } public static void warn(String msg, Object... data) { - log.warning(prepMsg(msg, data)); + getLogger().warning(prepMsg(msg, data)); } public static int clamp(int val, int min, int max) { @@ -163,9 +175,7 @@ private static String prepMsg(String msg, Object... data) { String out = (msg == null) ? "null" : msg; // Output with the ClaimChunk prefix - return String.format( - "[%s] %s", - claimChunk.getDescription().getPrefix(), color(String.format(out, data))); + return "[ClaimChunk] " + color(String.format(out, data)); } public static Map getDefaultPermissionsMap() { diff --git a/src/main/java/com/cjburkey/claimchunk/flags/PermFlag.java b/src/main/java/com/cjburkey/claimchunk/flags/PermFlag.java deleted file mode 100644 index 09ec6c3..0000000 --- a/src/main/java/com/cjburkey/claimchunk/flags/PermFlag.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.cjburkey.claimchunk.flags; - -import java.util.HashMap; -import java.util.List; - -/** - * Permission flag stuff - * - * @since 0.0.26 - */ -public class PermFlag { - - - -} diff --git a/src/main/java/com/cjburkey/claimchunk/flags/PermFlags.java b/src/main/java/com/cjburkey/claimchunk/flags/PermFlags.java index c6a113a..3127123 100644 --- a/src/main/java/com/cjburkey/claimchunk/flags/PermFlags.java +++ b/src/main/java/com/cjburkey/claimchunk/flags/PermFlags.java @@ -1,49 +1,199 @@ package com.cjburkey.claimchunk.flags; +import com.cjburkey.claimchunk.Utils; +import com.google.common.base.Charsets; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.Nullable; import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; /** - * Keeps track of loading the permission flags specified in the flags.yml - * configuration file. + * Keeps track of loading the permission flags specified in the flags.yml configuration file. * * @since 0.0.26 */ public class PermFlags { - private final JavaPlugin plugin; - private final File defaultFlagsFile; - private final String defaultFlagsResource; - private final File flagsFile; - private final HashMap blockControls = new HashMap<>(); - private final HashMap entityControls = new HashMap<>(); - - public PermFlags(JavaPlugin plugin, File defaultFlagsFile, String defaultFlagsResource, File flagsFile) { - this.plugin= plugin; - this.defaultFlagsFile = defaultFlagsFile; - this.defaultFlagsResource = defaultFlagsResource; - this.flagsFile = flagsFile; + public final HashMap blockControls = new HashMap<>(); + public final HashMap entityControls = new HashMap<>(); + + /** Read the flags defined in the flag definitions file. */ + public void load(File flagsFile, JavaPlugin plugin, String defaultFlagsResource) { + // Load the flags.yml file while ensuring the default exists + YamlConfiguration config = readFlagFile(flagsFile, plugin, defaultFlagsResource); + if (config == null) { + throw new RuntimeException("Failed to load flag config file (see ClaimChunk errors)"); + } + + loadFromConfig(config); } - public void load() { - // TODO: + public void loadFromConfig(YamlConfiguration config) { + // Read the flag section + ConfigurationSection flagSection = config.getConfigurationSection("permissionFlags"); + if (flagSection == null) { + throw new RuntimeException("Flag config file missing permissionFlags section"); + } + + // Read each flag name + for (String flagName : flagSection.getKeys(false)) { + // Get the list of maps (see src/resources/defaultFlags.yml for format) + List> flagEntries = flagSection.getMapList(flagName); + if (flagEntries.isEmpty()) { + Utils.err("Flag \"%s\" has no protections", flagName); + continue; + } + + // Loop through each map + for (Map flagMap : flagEntries) { + String forType = (String) flagMap.get("for"); + String interactType = (String) flagMap.get("type"); + if (interactType == null) { + Utils.err( + "Missing interaction type in one of the flag protection maps in flag" + + " \"%s\"", + flagName); + } + + // Check if this is for blocks/entities + switch (forType) { + case "BLOCKS" -> { + if (blockControls.containsKey(flagName)) { + Utils.err( + "Flag \"%s\" already has block protections defined", flagName); + continue; + } + + // Get the type of interaction to block + BlockFlagType flagType; + try { + flagType = BlockFlagType.valueOf(interactType); + } catch (Exception ignored) { + Utils.err( + "Unknown block interaction type \"%s\" in flag \"%s\"", + interactType, flagName); + continue; + } + + // Get the includes/excludes + FlagData flagData = readIncludeExclude(flagMap); + if (flagData == null) { + Utils.err( + "Failed to load flag includes/excludes from flag \"%s\" for" + + " block protections", + flagName); + } + + // Add the protections + BlockFlagData blockFlagData = new BlockFlagData(flagType, flagData); + blockControls.put(flagName, blockFlagData); + } + case "ENTITIES" -> { + if (entityControls.containsKey(flagName)) { + Utils.err( + "Flag \"%s\" already has entity protections defined", flagName); + continue; + } + + // Get the type of interaction to block + EntityFlagType flagType; + try { + flagType = EntityFlagType.valueOf(interactType); + } catch (Exception ignored) { + Utils.err( + "Unknown entity interaction type \"%s\" in flag \"%s\"", + interactType, flagName); + continue; + } + + // Get the includes/excludes + FlagData flagData = readIncludeExclude(flagMap); + if (flagData == null) { + Utils.err( + "Failed to load flag includes/excludes from flag \"%s\" for" + + " entity protections", + flagName); + } + + // Add the protections + EntityFlagData entityFlagData = new EntityFlagData(flagType, flagData); + entityControls.put(flagName, entityFlagData); + } + default -> Utils.err( + "Invalid flag protection target \"%s\" for flag \"%s\"", + forType, flagName); + } + } + + // Player property CJ-made-error safety check :) + if (blockControls.isEmpty() && entityControls.isEmpty()) { + throw new RuntimeException( + "ClaimChunk failed to load any block/entity protection flags, make sure the" + + " /plugins/ClaimChunk/flags.yml file is set up correctly (or allow it" + + " to regenerate)"); + } + } } - // -- CLASSES -- // + // Please don't break :| + @SuppressWarnings("unchecked") + private FlagData readIncludeExclude(Map flagMap) { + try { + return new FlagData( + (List) flagMap.get("include"), (List) flagMap.get("exclude")); + } catch (Exception e) { + Utils.err("Failed to read include/exclude data: %s", e.getMessage()); + } + return null; + } - public static final class FlagData { - public boolean isListInclude; - public List list; + private YamlConfiguration readFlagFile( + File flagsFile, JavaPlugin plugin, String defaultFlagsResource) { + if (flagsFile.exists()) { + // Just load the config + return YamlConfiguration.loadConfiguration(flagsFile); + } else { + // Load the configuration from the defaultFlags.yml file + YamlConfiguration ymlConfig; + try { + InputStream resource = plugin.getResource(defaultFlagsResource); + ymlConfig = + YamlConfiguration.loadConfiguration( + new InputStreamReader( + Objects.requireNonNull( + resource, + "Failed to locate resource at " + + defaultFlagsResource), + Charsets.UTF_8)); + } catch (Exception e) { + Utils.err( + "Failed to load default flag config (Is your file UTF-8?): %s", + e.getMessage()); + return null; + } - public FlagData(boolean isListInclude, List list) { - this.isListInclude = isListInclude; - this.list = list; + // Save the defaults + try { + ymlConfig.options().copyDefaults(true); + ymlConfig.save(flagsFile); + } catch (Exception e) { + Utils.err("Failed to save default flags file: %s", e.getMessage()); + } + return ymlConfig; } } + // -- CLASSES -- // + public enum BlockFlagType { BREAK, PLACE, @@ -51,29 +201,15 @@ public enum BlockFlagType { EXPLODE, } - public static class BlockFlagData { - public BlockFlagType flagType; - public FlagData flagData; - - public BlockFlagData(BlockFlagType flagType, boolean isListInclude, List list) { - this.flagType = flagType; - this.flagData = new FlagData(isListInclude, list); - } - } - public enum EntityFlagType { DAMAGE, INTERACT, EXPLODE, } - public static class EntityFlagData { - public EntityFlagType flagType; - public FlagData flagData; + public record FlagData(@Nullable List include, @Nullable List exclude) {} - public EntityFlagData(EntityFlagType flagType, boolean isListInclude, List list) { - this.flagType = flagType; - this.flagData = new FlagData(isListInclude, list); - } - } + public record BlockFlagData(BlockFlagType flagType, FlagData flagData) {} + + public record EntityFlagData(EntityFlagType flagType, FlagData flagData) {} } diff --git a/src/main/resources/defaultFlags.yml b/src/main/resources/defaultFlags.yml index fd4f10c..e9e62ad 100644 --- a/src/main/resources/defaultFlags.yml +++ b/src/main/resources/defaultFlags.yml @@ -5,12 +5,14 @@ permissionFlags: # Blocks - for: BLOCKS # BLOCKS or ENTITIES type: BREAK # For blocks, can be BREAK, PLACE, INTERACT, or EXPLODE - # If no `include` or `exclude` (cannot be used together, btw) - # is present, the default is to include all blocks/entities. - # If `include` is present, only the provided entities/entity + # If no `include` or `exclude` is present, the default is to + # include all blocks/entities. + # If only `include` is present, only the provided entities/entity # classes will be included. # The opposite is true of `exclude`, which includes all # default blocks/items and excludes the provided ones. + # If both are provided, then exclusions are considered after + # inclusions. placeBlocks: - for: BLOCKS type: PLACE diff --git a/src/test/java/com/cjburkey/claimchunk/PermFlagTests.java b/src/test/java/com/cjburkey/claimchunk/PermFlagTests.java new file mode 100644 index 0000000..ad21f89 --- /dev/null +++ b/src/test/java/com/cjburkey/claimchunk/PermFlagTests.java @@ -0,0 +1,67 @@ +package com.cjburkey.claimchunk; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.cjburkey.claimchunk.flags.PermFlags; + +import org.bukkit.configuration.file.YamlConfiguration; +import org.junit.jupiter.api.Test; + +import java.io.StringReader; +import java.util.Objects; + +public class PermFlagTests { + + @Test + void testLoadFlags() { + YamlConfiguration config = + YamlConfiguration.loadConfiguration( + new StringReader( + """ + permissionFlags: + breakBlocks: + - for: BLOCKS + type: BREAK + damageEntities: + - for: ENTITIES + type: DAMAGE + redstone: + - for: BLOCKS + type: INTERACT + include: ['@REDSTONE'] + """)); + PermFlags permFlags = new PermFlags(); + permFlags.loadFromConfig(config); + + PermFlags.BlockFlagData breakBlocks = permFlags.blockControls.get("breakBlocks"); + assertEquals(breakBlocks.flagType(), PermFlags.BlockFlagType.BREAK); + + PermFlags.EntityFlagData damageEntities = permFlags.entityControls.get("damageEntities"); + assertEquals(damageEntities.flagType(), PermFlags.EntityFlagType.DAMAGE); + + assert Objects.requireNonNull( + permFlags.blockControls.get("redstone").flagData().include(), + "Missing include list") + .contains("@REDSTONE"); + } + + @Test + void testTwo() { + YamlConfiguration config = + YamlConfiguration.loadConfiguration( + new StringReader( + """ + permissionFlags: + ruinStuff: + - for: ENTITIES + type: DAMAGE + - for: BLOCKS + type: BREAK + """)); + PermFlags permFlags = new PermFlags(); + permFlags.loadFromConfig(config); + + assert permFlags.blockControls.containsKey("ruinStuff"); + assert permFlags.entityControls.containsKey("ruinStuff"); + } +} diff --git a/unbuilt_readme.md b/unbuilt_readme.md index 1cfc344..bf50278 100644 --- a/unbuilt_readme.md +++ b/unbuilt_readme.md @@ -20,7 +20,7 @@ Spigot plugin for 1.20+ allowing the claiming of chunks. Usage and more information can be found [on the wiki](https://github.com/cjburkey01/ClaimChunk/wiki). * **1.20-@LATEST_MC_VERSION@+** | The latest version works seamlessly (excluding bugs, of course). - * **Note for 0.0.23**: When updating the server from 1.20 to 1.21, ClaimChunk will throw errors that it can't find entities by whatever names due to the enum API change. + * **Note for 0.0.23+**! When updating the server from 1.20 to 1.21, ClaimChunk will throw errors that it can't find entities by whatever names due to the enum API change. * If you keep getting those errors on server start, stop the server, open your old profiles at `/plugins/ClaimChunk/worlds/.txt`, then copy these lines and replace the old (similar looking) ones in each world profile file: ``` _._@B_: