package net.knarcraft.stargate; import org.bukkit.Material; import org.bukkit.block.Block; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Scanner; import java.util.logging.Level; /** * stargate - A portal plugin for Bukkit * Copyright (C) 2011 Shaun (sturmeh) * Copyright (C) 2011 Dinnerbone * Copyright (C) 2011, 2012 Steven "Drakia" Scott * Copyright (C) 2021 Kristian Knarvik *

* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. *

* This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. *

* You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ public class Gate { private static final Character ANYTHING = ' '; private static final Character ENTRANCE = '.'; private static final Character EXIT = '*'; private static final HashMap gates = new HashMap<>(); private static final HashMap> controlBlocks = new HashMap<>(); private static final HashSet frameBlocks = new HashSet<>(); private final String filename; private final Character[][] layout; private final HashMap types; private RelativeBlockVector[] entrances = new RelativeBlockVector[0]; private RelativeBlockVector[] border = new RelativeBlockVector[0]; private RelativeBlockVector[] controls = new RelativeBlockVector[0]; private RelativeBlockVector exitBlock = null; private final HashMap exits = new HashMap<>(); private Material portalBlockOpen = Material.NETHER_PORTAL; private Material portalBlockClosed = Material.AIR; private Material button = Material.STONE_BUTTON; // Economy information private int useCost = -1; private int createCost = -1; private int destroyCost = -1; private boolean toOwner = false; public Gate(String filename, Character[][] layout, HashMap types) { this.filename = filename; this.layout = layout; this.types = types; populateCoordinates(); } private void populateCoordinates() { ArrayList entranceList = new ArrayList<>(); ArrayList borderList = new ArrayList<>(); ArrayList controlList = new ArrayList<>(); RelativeBlockVector[] relativeExits = new RelativeBlockVector[layout[0].length]; int[] exitDepths = new int[layout[0].length]; RelativeBlockVector lastExit = null; for (int y = 0; y < layout.length; y++) { for (int x = 0; x < layout[y].length; x++) { Character key = layout[y][x]; if (key.equals('-')) { controlList.add(new RelativeBlockVector(x, y, 0)); } if (key.equals(ENTRANCE) || key.equals(EXIT)) { entranceList.add(new RelativeBlockVector(x, y, 0)); exitDepths[x] = y; if (key.equals(EXIT)) { this.exitBlock = new RelativeBlockVector(x, y, 0); } } else if (!key.equals(ANYTHING)) { borderList.add(new RelativeBlockVector(x, y, 0)); } } } for (int x = 0; x < exitDepths.length; x++) { relativeExits[x] = new RelativeBlockVector(x, exitDepths[x], 0); } for (int x = relativeExits.length - 1; x >= 0; x--) { if (relativeExits[x] != null) { lastExit = relativeExits[x]; } else { relativeExits[x] = lastExit; } if (exitDepths[x] > 0) this.exits.put(relativeExits[x], x); } this.entrances = entranceList.toArray(this.entrances); this.border = borderList.toArray(this.border); this.controls = controlList.toArray(this.controls); } public void save(String gateFolder) { try { BufferedWriter bw = new BufferedWriter(new FileWriter(gateFolder + filename)); writeConfig(bw, "portal-open", portalBlockOpen.name()); writeConfig(bw, "portal-closed", portalBlockClosed.name()); writeConfig(bw, "button", button.name()); if (useCost != -1) writeConfig(bw, "usecost", useCost); if (createCost != -1) writeConfig(bw, "createcost", createCost); if (destroyCost != -1) writeConfig(bw, "destroycost", destroyCost); writeConfig(bw, "toowner", toOwner); for (Map.Entry entry : types.entrySet()) { Character type = entry.getKey(); Material value = entry.getValue(); // Skip control values if (type.equals(ANYTHING) || type.equals(ENTRANCE) || type.equals(EXIT)) { continue; } bw.append(type); bw.append('='); if (value != null) { bw.append(value.toString()); } bw.newLine(); } bw.newLine(); for (Character[] aLayout : layout) { for (Character symbol : aLayout) { bw.append(symbol); } bw.newLine(); } bw.close(); } catch (IOException ex) { Stargate.log.log(Level.SEVERE, "Could not save Gate " + filename + " - " + ex.getMessage()); } } private void writeConfig(BufferedWriter bw, String key, int value) throws IOException { bw.append(String.format("%s=%d", key, value)); bw.newLine(); } private void writeConfig(BufferedWriter bw, String key, boolean value) throws IOException { bw.append(String.format("%s=%b", key, value)); bw.newLine(); } private void writeConfig(BufferedWriter bw, String key, String value) throws IOException { bw.append(String.format("%s=%s", key, value)); bw.newLine(); } public Character[][] getLayout() { return layout; } public HashMap getTypes() { return types; } public RelativeBlockVector[] getEntrances() { return entrances; } public RelativeBlockVector[] getBorder() { return border; } public RelativeBlockVector[] getControls() { return controls; } public HashMap getExits() { return exits; } public RelativeBlockVector getExit() { return exitBlock; } public Material getControlBlock() { return types.get('-'); } public String getFilename() { return filename; } public Material getPortalBlockOpen() { return portalBlockOpen; } public void setPortalBlockOpen(Material type) { portalBlockOpen = type; } public Material getPortalBlockClosed() { return portalBlockClosed; } public void setPortalBlockClosed(Material type) { portalBlockClosed = type; } public Material getButton() { return button; } public int getUseCost() { if (useCost < 0) return EconomyHandler.useCost; return useCost; } public Integer getCreateCost() { if (createCost < 0) return EconomyHandler.createCost; return createCost; } public Integer getDestroyCost() { if (destroyCost < 0) return EconomyHandler.destroyCost; return destroyCost; } public Boolean getToOwner() { return toOwner; } public boolean matches(BlockLocation topleft, int modX, int modZ) { return matches(topleft, modX, modZ, false); } public boolean matches(BlockLocation topleft, int modX, int modZ, boolean onCreate) { HashMap portalTypes = new HashMap<>(types); for (int y = 0; y < layout.length; y++) { for (int x = 0; x < layout[y].length; x++) { Character key = layout[y][x]; if (key.equals(ENTRANCE) || key.equals(EXIT)) { if (Stargate.ignoreEntrance) continue; Material type = topleft.modRelative(x, y, 0, modX, 1, modZ).getType(); // Ignore entrance if it's air and we're creating a new gate if (onCreate && type == Material.AIR) continue; if (type != portalBlockClosed && type != portalBlockOpen) { Stargate.debug("Gate::Matches", "Entrance/Exit Material Mismatch: " + type); return false; } } else if (!key.equals(ANYTHING)) { Material id = portalTypes.get(key); if (id == null) { portalTypes.put(key, topleft.modRelative(x, y, 0, modX, 1, modZ).getType()); } else if (topleft.modRelative(x, y, 0, modX, 1, modZ).getType() != id) { Stargate.debug("Gate::Matches", "Block Type Mismatch: " + topleft.modRelative(x, y, 0, modX, 1, modZ).getType() + " != " + id); return false; } } } } return true; } public 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); } public static Gate loadGate(File file) { Scanner scanner = null; boolean designing = false; ArrayList> design = new ArrayList<>(); HashMap types = new HashMap<>(); HashMap config = new HashMap<>(); HashSet frameTypes = new HashSet<>(); int cols = 0; // Init types map types.put(ENTRANCE, Material.AIR); types.put(EXIT, Material.AIR); types.put(ANYTHING, Material.AIR); try { scanner = new Scanner(file); while (scanner.hasNextLine()) { String line = scanner.nextLine(); if (designing) { ArrayList 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 " + file.getName() + " - Unknown symbol '" + symbol + "' in diagram"); return null; } row.add(symbol); } design.add(row); } else { if ((line.isEmpty()) || (!line.contains("="))) { designing = true; } else { 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); } } } } } catch (Exception ex) { Stargate.log.log(Level.SEVERE, "Could not load Gate " + file.getName() + " - " + ex.getMessage()); return null; } finally { if (scanner != null) scanner.close(); } Character[][] layout = new Character[design.size()][cols]; for (int y = 0; y < design.size(); y++) { ArrayList row = design.get(y); Character[] result = new Character[cols]; for (int x = 0; x < cols; x++) { if (x < row.size()) { result[x] = row.get(x); } else { result[x] = ' '; } } layout[y] = result; } Gate gate = new Gate(file.getName(), layout, types); gate.portalBlockOpen = readConfig(config, gate, file, "portal-open", gate.portalBlockOpen); gate.portalBlockClosed = readConfig(config, gate, file, "portal-closed", gate.portalBlockClosed); gate.button = readConfig(config, gate, file, "button", gate.button); gate.useCost = readConfig(config, gate, file, "usecost", -1); gate.destroyCost = readConfig(config, gate, file, "destroycost", -1); gate.createCost = readConfig(config, gate, file, "createcost", -1); gate.toOwner = (config.containsKey("toowner") ? Boolean.valueOf(config.get("toowner")) : EconomyHandler.toOwner); if (gate.getControls().length != 2) { Stargate.log.log(Level.SEVERE, "Could not load Gate " + file.getName() + " - Gates must have exactly 2 control points."); return null; } if (!MaterialHelper.isButtonCompatible(gate.button)) { Stargate.log.log(Level.SEVERE, "Could not load Gate " + file.getName() + " - Gate button must be a type of button."); return null; } // Merge frame types, add open mat to list frameBlocks.addAll(frameTypes); gate.save(file.getParent() + "/"); // Updates format for version changes return gate; } private static int readConfig(HashMap config, Gate gate, File file, String key, int def) { 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(), file, key)); } } return def; } private static Material readConfig(HashMap config, Gate gate, File file, String key, Material def) { if (config.containsKey(key)) { Material mat = Material.getMaterial(config.get(key)); if (mat != null) { return mat; } Stargate.log.log(Level.WARNING, String.format("Error reading %s: %s is not a material", file, key)); } return def; } /** * 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 specification to the given folder * @param gateFolder

The folder containing gate config files

*/ public static void populateDefaults(String gateFolder) { Character[][] layout = new Character[][]{ {' ', 'X', 'X', ' '}, {'X', '.', '.', 'X'}, {'-', '.', '.', '-'}, {'X', '*', '.', 'X'}, {' ', 'X', 'X', ' '}, }; HashMap types = new HashMap<>(); types.put(ENTRANCE, Material.AIR); types.put(EXIT, Material.AIR); types.put(ANYTHING, Material.AIR); types.put('X', Material.OBSIDIAN); types.put('-', Material.OBSIDIAN); Gate gate = new Gate("nethergate.gate", layout, types); gate.save(gateFolder); registerGate(gate); } public static Gate[] getGatesByControlBlock(Block block) { return getGatesByControlBlock(block.getType()); } public static Gate[] getGatesByControlBlock(Material type) { Gate[] result = new Gate[0]; ArrayList 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(); } /** * Checks whether the given material is used for the frame of any portals * @param type

The material type to check

* @return

True if the material is used for the frame of at least one portal

*/ public static boolean isGateBlock(Material type) { return frameBlocks.contains(type); } /** * Clears all loaded gates */ public static void clearGates() { gates.clear(); controlBlocks.clear(); frameBlocks.clear(); } }