package net.knarcraft.dropper.config; import net.knarcraft.dropper.Dropper; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.Tag; import org.bukkit.configuration.file.FileConfiguration; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * The configuration keeping track of the player's current configuration */ public class DropperConfiguration { private FileConfiguration configuration; private final static String rootNode = "dropper."; private double verticalVelocity; private float horizontalVelocity; private int randomlyInvertedTimer; private boolean mustDoGroupedInSequence; private boolean ignoreRecordsUntilGroupBeatenOnce; private boolean mustDoNormalModeFirst; private boolean makePlayersInvisible; private boolean disableHitCollision; private double liquidHitBoxDepth; private double solidHitBoxDistance; private boolean blockSneaking; private boolean blockSprinting; private Set blockWhitelist; /** * Instantiates a new dropper configuration * * @param configuration

The YAML configuration to use internally

*/ public DropperConfiguration(FileConfiguration configuration) { this.configuration = configuration; } /** * Gets the default vertical velocity * * @return

The default vertical velocity

*/ public double getVerticalVelocity() { return this.verticalVelocity; } /** * Gets the default horizontal velocity * * @return

The default horizontal velocity

*/ public float getHorizontalVelocity() { return this.horizontalVelocity; } /** * Gets the number of seconds before the randomly inverted game-mode toggles * * @return

Number of seconds before the inversion toggles

*/ public int getRandomlyInvertedTimer() { return this.randomlyInvertedTimer; } /** * Gets whether grouped arenas must be done in the set sequence * * @return

Whether grouped arenas must be done in sequence

*/ public boolean mustDoGroupedInSequence() { return this.mustDoGroupedInSequence; } /** * Gets whether the normal/default mode must be beaten before playing another game-mode * * @return

Whether the normal game-mode must be beaten first

*/ public boolean mustDoNormalModeFirst() { return this.mustDoNormalModeFirst; } /** * Gets the types of block which should not trigger a loss * * @return

The materials that should not trigger a loss

*/ public Set getBlockWhitelist() { return new HashSet<>(this.blockWhitelist); } /** * Gets whether records should be discarded, unless the player has already beaten all arenas in the group * * @return

Whether to ignore records on the first play-through

*/ public boolean ignoreRecordsUntilGroupBeatenOnce() { return this.ignoreRecordsUntilGroupBeatenOnce; } /** * Gets whether players should be made invisible while in an arena * * @return

Whether players should be made invisible

*/ public boolean makePlayersInvisible() { return this.makePlayersInvisible; } /** * Gets whether entity hit-collision of players in an arena should be disabled * * @return

Whether to disable hit collision

*/ public boolean disableHitCollision() { return this.disableHitCollision; } /** * Gets the negative depth a player must reach in a liquid block for fail/win detection to trigger * *

This decides how far inside a non-solid block the player must go before detection triggers. The closer to -1 * it is, the more accurate it will seem to the player, but the likelihood of not detecting the hit increases.

* * @return

The liquid hit box depth to use

*/ public double getLiquidHitBoxDepth() { return this.liquidHitBoxDepth; } /** * Gets the positive distance a player must at most be from a block for fail/win detection to trigger * *

This decides the distance the player must be from a block below them before a hit triggers. If too low, the * likelihood of detecting the hit decreases, but it won't look like the player hit the block without being near.

* * @return

The solid hit box distance to use

*/ public double getSolidHitBoxDistance() { return this.solidHitBoxDistance; } /** * Gets whether players trying to sneak while in a dropper arena to increase their downwards speed should be blocked * * @return

Whether to block sneak to speed up

*/ public boolean blockSneaking() { return blockSneaking; } /** * Gets whether players trying to sprint to improve their horizontal speed while in a dropper arena should be blocked * * @return

Whether to block sprint to speed up

*/ public boolean blockSprinting() { return this.blockSprinting; } /** * Loads all configuration values from disk * * @param configuration

The configuration to load

*/ public void load(FileConfiguration configuration) { this.configuration = configuration; this.load(); } /** * Loads all configuration values from disk */ public void load() { this.verticalVelocity = configuration.getDouble(rootNode + "verticalVelocity", 1.0); this.horizontalVelocity = (float) configuration.getDouble(rootNode + "horizontalVelocity", 1.0); this.randomlyInvertedTimer = configuration.getInt(rootNode + "randomlyInvertedTimer", 7); this.mustDoGroupedInSequence = configuration.getBoolean(rootNode + "mustDoGroupedInSequence", true); this.ignoreRecordsUntilGroupBeatenOnce = configuration.getBoolean(rootNode + "ignoreRecordsUntilGroupBeatenOnce", false); this.mustDoNormalModeFirst = configuration.getBoolean(rootNode + "mustDoNormalModeFirst", true); this.makePlayersInvisible = configuration.getBoolean(rootNode + "makePlayersInvisible", false); this.disableHitCollision = configuration.getBoolean(rootNode + "disableHitCollision", true); this.liquidHitBoxDepth = configuration.getDouble(rootNode + "liquidHitBoxDepth", -0.8); this.solidHitBoxDistance = configuration.getDouble(rootNode + "solidHitBoxDistance", 0.2); this.blockSprinting = configuration.getBoolean(rootNode + "blockSprinting", true); this.blockSneaking = configuration.getBoolean(rootNode + "blockSneaking", true); sanitizeValues(); loadBlockWhitelist(); } /** * Sanitizes configuration values to ensure they are within expected bounds */ private void sanitizeValues() { if (this.liquidHitBoxDepth <= -1 || this.liquidHitBoxDepth > 0) { this.liquidHitBoxDepth = -0.8; } if (this.solidHitBoxDistance <= 0 || this.solidHitBoxDistance > 1) { this.solidHitBoxDistance = 0.2; } if (this.horizontalVelocity > 1 || this.horizontalVelocity <= 0) { this.horizontalVelocity = 1; } if (this.verticalVelocity <= 0 || this.verticalVelocity > 75) { this.verticalVelocity = 1; } if (this.randomlyInvertedTimer <= 0 || this.randomlyInvertedTimer > 3600) { this.randomlyInvertedTimer = 7; } } /** * Loads the materials specified in the block whitelist */ private void loadBlockWhitelist() { this.blockWhitelist = new HashSet<>(); List blockWhitelist = configuration.getList(rootNode + "blockWhitelist", new ArrayList<>()); for (Object value : blockWhitelist) { if (!(value instanceof String string)) { continue; } // Try to parse a material tag first if (parseMaterialTag(string)) { continue; } // Try to parse a material name Material matched = Material.matchMaterial(string); if (matched != null) { this.blockWhitelist.add(matched); } else { Dropper.log(Level.WARNING, "Unable to parse: " + string); } } } /** * Tries to parse the material tag in the specified material name * * @param materialName

The material name that might be a material tag

* @return

True if a tag was found

*/ private boolean parseMaterialTag(String materialName) { Pattern pattern = Pattern.compile("^\\+([a-zA-Z_]+)"); Matcher matcher = pattern.matcher(materialName); if (matcher.find()) { // The material is a material tag Tag tag = Bukkit.getTag(Tag.REGISTRY_BLOCKS, NamespacedKey.minecraft( matcher.group(1).toLowerCase()), Material.class); if (tag != null) { this.blockWhitelist.addAll(tag.getValues()); } else { Dropper.log(Level.WARNING, "Unable to parse: " + materialName); } return true; } return false; } @Override public String toString() { StringBuilder builder = new StringBuilder( "Current configuration:" + "\n" + "Vertical velocity: " + verticalVelocity + "\n" + "Horizontal velocity: " + horizontalVelocity + "\n" + "Randomly inverted timer: " + randomlyInvertedTimer + "\n" + "Must do groups in sequence: " + mustDoGroupedInSequence + "\n" + "Ignore records until group beaten once: " + ignoreRecordsUntilGroupBeatenOnce + "\n" + "Must do normal mode first: " + mustDoNormalModeFirst + "\n" + "Make players invisible: " + makePlayersInvisible + "\n" + "Disable hit collision: " + disableHitCollision + "\n" + "Liquid hit box depth: " + liquidHitBoxDepth + "\n" + "Solid hit box distance: " + solidHitBoxDistance + "\n" + "Block whitelist: "); for (Material material : blockWhitelist) { builder.append("\n - ").append(material.name()); } return builder.toString(); } }