package net.knarcraft.stargate.portal; import net.knarcraft.stargate.utility.EconomyHandler; import net.knarcraft.stargate.Stargate; import net.knarcraft.stargate.utility.MaterialHelper; import org.bukkit.Material; import org.bukkit.block.Block; import java.io.File; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Scanner; import java.util.Set; import java.util.logging.Level; /** * The gate handler keeps track of all gates */ public class GateHandler { private static final Character ANYTHING = ' '; private static final Character ENTRANCE = '.'; private static final Character EXIT = '*'; private static final Character CONTROL_BLOCK = '-'; private static final Material defaultPortalBlockOpen = Material.NETHER_PORTAL; private static final Material defaultPortalBlockClosed = Material.AIR; private static final Material defaultButton = Material.STONE_BUTTON; private static final HashMap gates = new HashMap<>(); private static final HashMap> controlBlocks = new HashMap<>(); private GateHandler() { } /** * Gets the character used for blocks that are not part of the gate * * @return

The character used for blocks that are not part of the gate

*/ public static Character getAnythingCharacter() { return ANYTHING; } /** * Gets the character used for defining the entrance * * @return

The character used for defining the entrance

*/ public static Character getEntranceCharacter() { return ENTRANCE; } /** * Gets the character used for defining the exit * * @return

The character used for defining the exit

*/ public static Character getExitCharacter() { return EXIT; } /** * Gets the character used for defining control blocks * * @return

The character used for defining control blocks

*/ public static Character getControlBlockCharacter() { return CONTROL_BLOCK; } /** * Register a gate into the list of available gates * * @param gate

The gate to register

*/ private static void registerGate(Gate gate) { gates.put(gate.getFilename(), gate); Material blockID = gate.getControlBlock(); if (!controlBlocks.containsKey(blockID)) { controlBlocks.put(blockID, new ArrayList<>()); } controlBlocks.get(blockID).add(gate); } /** * Loads a gate * * @param file

The file containing the gate's layout

* @return

The loaded gate or null if unable to load the gate

*/ private static Gate loadGate(File file) { try (Scanner scanner = new Scanner(file)) { return loadGate(file.getName(), file.getParent(), scanner); } catch (Exception ex) { Stargate.log.log(Level.SEVERE, "Could not load Gate " + file.getName() + " - " + ex.getMessage()); return null; } } /** * Loads a gate * * @param fileName

The name of the file containing the gate layout

* @param parentFolder

The parent folder of the layout file

* @param scanner

The scanner to use for reading the gate layout

* @return

The loaded gate or null if unable to load the gate

*/ private static Gate loadGate(String fileName, String parentFolder, Scanner scanner) { List> design = new ArrayList<>(); Map types = new HashMap<>(); Map config = new HashMap<>(); Set frameTypes = new HashSet<>(); //Initialize types map types.put(ENTRANCE, Material.AIR); types.put(EXIT, Material.AIR); types.put(ANYTHING, Material.AIR); //Read the file into appropriate lists and maps int cols = readGateFile(scanner, types, fileName, design, frameTypes, config); if (cols < 0) { return null; } Character[][] layout = generateLayoutMatrix(design, cols); //Create and validate the new gate Gate gate = createGate(config, fileName, layout, types); if (gate == null) { return null; } gate.save(parentFolder + "/"); // Updates format for version changes return gate; } /** * Creates a new gate * * @param config

The config map to get configuration values from

* @param fileName

The name of the saved gate config file

* @param layout

The layout matrix of the new gate

* @param types

The mapping for used gate material types

* @return

A new gate or null if the config is invalid

*/ private static Gate createGate(Map config, String fileName, Character[][] layout, Map types) { Material portalOpenBlock = readConfig(config, fileName, "portal-open", defaultPortalBlockOpen); Material portalClosedBlock = readConfig(config, fileName, "portal-closed", defaultPortalBlockClosed); Material portalButton = readConfig(config, fileName, "button", defaultButton); int useCost = readConfig(config, fileName, "usecost"); int createCost = readConfig(config, fileName, "createcost"); int destroyCost = readConfig(config, fileName, "destroycost"); boolean toOwner = (config.containsKey("toowner") ? Boolean.parseBoolean(config.get("toowner")) : EconomyHandler.toOwner); Gate gate = new Gate(fileName, new GateLayout(layout), types, portalOpenBlock, portalClosedBlock, portalButton, useCost, createCost, destroyCost, toOwner); if (!validateGate(gate, fileName)) { return null; } return gate; } /** * Validate that a gate is valid * * @param gate

The gate to validate

* @param fileName

The filename of the loaded gate file

* @return

True if the gate is valid. False otherwise

*/ private static boolean validateGate(Gate gate, String fileName) { if (gate.getLayout().getControls().length != 2) { Stargate.log.log(Level.SEVERE, "Could not load Gate " + fileName + " - Gates must have exactly 2 control points."); return false; } if (!MaterialHelper.isButtonCompatible(gate.getPortalButton())) { Stargate.log.log(Level.SEVERE, "Could not load Gate " + fileName + " - Gate button must be a type of button."); return false; } return true; } /** * Generates a matrix storing the gate layout * * @param design

The design of the gate layout

* @param cols

The largest amount of columns in the design

* @return

A matrix containing the gate's layout

*/ private static Character[][] generateLayoutMatrix(List> design, int cols) { Character[][] layout = new Character[design.size()][cols]; for (int lineIndex = 0; lineIndex < design.size(); lineIndex++) { List row = design.get(lineIndex); Character[] result = new Character[cols]; for (int rowIndex = 0; rowIndex < cols; rowIndex++) { if (rowIndex < row.size()) { result[rowIndex] = row.get(rowIndex); } else { //Add spaces to all lines which are too short result[rowIndex] = ' '; } } layout[lineIndex] = result; } return layout; } /** * Reads the gate file * * @param scanner

The scanner to read from

* @param types

The map of characters to store valid symbols in

* @param fileName

The filename of the loaded gate config file

* @param design

The list to store the loaded design to

* @param frameTypes

The set of gate frame types to store to

* @param config

The map of config values to store to

* @return

The column count/width of the loaded gate

*/ private static int readGateFile(Scanner scanner, Map types, String fileName, List> design, Set frameTypes, Map config) { boolean designing = false; int cols = 0; try { while (scanner.hasNextLine()) { String line = scanner.nextLine(); if (designing) { cols = readGateDesignLine(line, cols, types, fileName, design); if (cols < 0) { return -1; } } else { if (!line.isEmpty() && !line.startsWith("#")) { readGateConfigValue(line, types, frameTypes, config); } else if ((line.isEmpty()) || (!line.contains("=") && !line.startsWith("#"))) { designing = true; } } } } catch (Exception ex) { Stargate.log.log(Level.SEVERE, "Could not load Gate " + fileName + " - " + ex.getMessage()); return -1; } finally { if (scanner != null) { scanner.close(); } } return cols; } /** * Reads one design line of the gate layout file * * @param line

The line to read

* @param cols

The current max columns value of the design

* @param types

The map of characters to check for valid symbols

* @param fileName

The filename of the loaded gate config file

* @param design

The list to store the loaded design to

* @return

The new max columns value of the design

*/ private static int readGateDesignLine(String line, int cols, Map types, String fileName, List> design) { List row = new ArrayList<>(); if (line.length() > cols) { cols = line.length(); } for (Character symbol : line.toCharArray()) { if ((symbol.equals('?')) || (!types.containsKey(symbol))) { Stargate.log.log(Level.SEVERE, "Could not load Gate " + fileName + " - Unknown symbol '" + symbol + "' in diagram"); return -1; } row.add(symbol); } design.add(row); return cols; } /** * Reads one config value from the gate layout file * * @param line

The line to read

* @param types

The map of characters to materials to store to

* @param frameTypes

The set of gate frame types to store to

* @param config

The map of config values to store to

* @throws Exception

If an invalid material is encountered

*/ private static void readGateConfigValue(String line, Map types, Set frameTypes, Map config) throws Exception { String[] split = line.split("="); String key = split[0].trim(); String value = split[1].trim(); if (key.length() == 1) { Character symbol = key.charAt(0); Material id = Material.getMaterial(value); if (id == null) { throw new Exception("Invalid material in line: " + line); } types.put(symbol, id); frameTypes.add(id); } else { config.put(key, value); } } /** * Reads an integer configuration key * @param config

The configuration to read

* @param fileName

The filename of the config file

* @param key

The config key to read

* @return

The read value, or -1 if it cannot be read

*/ private static int readConfig(Map config, String fileName, String key) { if (config.containsKey(key)) { try { return Integer.parseInt(config.get(key)); } catch (NumberFormatException ex) { Stargate.log.log(Level.WARNING, String.format("%s reading %s: %s is not numeric", ex.getClass().getName(), fileName, key)); } } return -1; } /** * Gets the material defined in the config * * @param config

The config to read

* @param fileName

The config file the config belongs to

* @param key

The config key to read

* @param defaultMaterial

The default material to use, in case the config is invalid

* @return

The material to use

*/ private static Material readConfig(Map config, String fileName, String key, Material defaultMaterial) { if (config.containsKey(key)) { Material material = Material.getMaterial(config.get(key)); if (material != null) { return material; } else { Stargate.log.log(Level.WARNING, String.format("Error reading %s: %s is not a material", fileName, key)); } } return defaultMaterial; } /** * Loads all gates inside the given folder * * @param gateFolder

The folder containing the gates

*/ public static void loadGates(String gateFolder) { File directory = new File(gateFolder); File[] files; if (directory.exists()) { files = directory.listFiles((file) -> file.isFile() && file.getName().endsWith(".gate")); } else { files = new File[0]; } if (files == null || files.length == 0) { //The gates folder was not found. Assume this is the first run if (directory.mkdir()) { populateDefaults(gateFolder); } } else { for (File file : files) { Gate gate = loadGate(file); if (gate != null) { registerGate(gate); } } } } /** * Writes the default gate specifications to the given folder * * @param gateFolder

The folder containing gate config files

*/ private static void populateDefaults(String gateFolder) { loadGateFromJar("nethergate.gate", gateFolder); loadGateFromJar("watergate.gate", gateFolder); } /** * Loads the given gate file from within the Jar's resources directory * * @param gateFile

The name of the gate file

* @param gateFolder

The folder containing gates

*/ private static void loadGateFromJar(String gateFile, String gateFolder) { InputStream gateFileStream = Gate.class.getResourceAsStream("/gates/" + gateFile); if (gateFileStream != null) { Scanner scanner = new Scanner(gateFileStream); Gate gate = loadGate(gateFile, gateFolder, scanner); if (gate != null) { registerGate(gate); } } } /** * Gets the gates with the given control block * *

The control block is the block type where the sign should be placed. It is used to decide whether a user * is creating a new portal.

* * @param block

The control block to check

* @return

A list of gates using the given control block

*/ public static Gate[] getGatesByControlBlock(Block block) { return getGatesByControlBlock(block.getType()); } /** * Gets the gates with the given control block * * @param type

The type of the control block to check

* @return

A list of gates using the given material for control block

*/ public static Gate[] getGatesByControlBlock(Material type) { Gate[] result = new Gate[0]; List lookup = controlBlocks.get(type); if (lookup != null) { result = lookup.toArray(result); } return result; } /** * Gets a portal by its name (filename before .gate) * * @param name

The name of the gate to get

* @return

The gate with the given name

*/ public static Gate getGateByName(String name) { return gates.get(name); } /** * Gets the number of loaded gate configurations * * @return

The number of loaded gate configurations

*/ public static int getGateCount() { return gates.size(); } /** * Clears all loaded gates */ public static void clearGates() { gates.clear(); controlBlocks.clear(); } }