Splits Gate into Gate, GateLayout and GateHandler, and creates a new portal package with portal related classes

This commit is contained in:
2021-02-22 17:01:47 +01:00
parent c422cb9ea9
commit fb70b8bc75
30 changed files with 749 additions and 324 deletions

View File

@ -0,0 +1,300 @@
package net.knarcraft.stargate.portal;
import net.knarcraft.stargate.BlockLocation;
import net.knarcraft.stargate.EconomyHandler;
import net.knarcraft.stargate.Stargate;
import org.bukkit.Material;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
/**
* A gate describes the physical structure of a stargate
*
* <p>While the portal class represents a portal in space, the Gate class represents the physical gate/portal entrance.</p>
*/
public class Gate {
private final String filename;
private final GateLayout layout;
private final HashMap<Character, Material> types;
//Gate materials
private Material portalOpenBlock;
private Material portalClosedBlock;
private Material portalButton;
// Economy information
private int useCost;
private int createCost;
private int destroyCost;
private boolean toOwner;
/**
* Instantiates a new gate
*
* @param filename <p>The name of the gate which equal the name of the file</p>
* @param layout <p>The character layout defined in the gate file</p>
* @param types <p>The block types the different layout characters represent</p>
* @param portalOpenBlock <p>The material to set the non-frame to when the portal is open</p>
* @param portalClosedBlock <p>The material to set the non-frame to when the portal is closed</p>
* @param portalButton <p>The material to use for the portal button</p>
* @param useCost <p>The cost of using a portal with this gate layout (-1 to disable)</p>
* @param createCost <p>The cost of creating a portal with this gate layout (-1 to disable)</p>
* @param destroyCost <p>The cost of destroying a portal with this gate layout (-1 to disable)</p>
* @param toOwner <p>Whether any payment should go to the owner of the gate, as opposed to just disappearing</p>
*/
public Gate(String filename, GateLayout layout, HashMap<Character, Material> types, Material portalOpenBlock,
Material portalClosedBlock, Material portalButton, int useCost, int createCost, int destroyCost,
boolean toOwner) {
this.filename = filename;
this.layout = layout;
this.types = types;
this.portalOpenBlock = portalOpenBlock;
this.portalClosedBlock = portalClosedBlock;
this.portalButton = portalButton;
this.useCost = useCost;
this.createCost = createCost;
this.destroyCost = destroyCost;
this.toOwner = toOwner;
}
/**
* Gets the layout of this gate
*
* @return <p>The layout of this gate</p>
*/
public GateLayout getLayout() {
return layout;
}
/**
* Gets the material types each layout character represents
*
* @return <p>The material types each layout character represents</p>
*/
public HashMap<Character, Material> getTypes() {
return types;
}
public Material getControlBlock() {
return types.get('-');
}
public String getFilename() {
return filename;
}
public Material getPortalOpenBlock() {
return portalOpenBlock;
}
public void setPortalOpenBlock(Material type) {
portalOpenBlock = type;
}
public Material getPortalClosedBlock() {
return portalClosedBlock;
}
public void setPortalClosedBlock(Material type) {
portalClosedBlock = type;
}
public Material getPortalButton() {
return portalButton;
}
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<Character, Material> portalTypes = new HashMap<>(types);
Character[][] layout = this.layout.getLayout();
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(GateHandler.getEntranceCharacter()) || key.equals(GateHandler.getExitCharacter())) {
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 != portalClosedBlock && type != portalOpenBlock) {
Stargate.debug("Gate::Matches", "Entrance/Exit Material Mismatch: " + type);
return false;
}
} else if (!key.equals(GateHandler.getAnythingCharacter())) {
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;
}
/**
* Saves this gate to a file
*
* <p>This method will save the gate to its filename in the given folder.</p>
*
* @param gateFolder <p>The folder to save the gate file in</p>
*/
public void save(String gateFolder) {
try {
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(gateFolder + filename));
writeConfig(bufferedWriter, "portal-open", portalOpenBlock.name());
writeConfig(bufferedWriter, "portal-closed", portalClosedBlock.name());
writeConfig(bufferedWriter, "button", portalButton.name());
//Save the values necessary for economy
saveEconomyValues(bufferedWriter);
//Store type material type to use for frame blocks
saveFrameBlockTypes(bufferedWriter);
bufferedWriter.newLine();
//Save the layout
layout.save(bufferedWriter);
bufferedWriter.close();
} catch (IOException ex) {
Stargate.log.log(Level.SEVERE, "Could not save Gate " + filename + " - " + ex.getMessage());
}
}
/**
* Saves current economy related values using a buffered writer
*
* @param bufferedWriter <p>The buffered writer to write to</p>
* * @throws IOException <p>If unable to write to the buffered writer</p>
*/
private void saveEconomyValues(BufferedWriter bufferedWriter) throws IOException {
if (useCost != -1) {
writeConfig(bufferedWriter, "usecost", useCost);
}
if (createCost != -1) {
writeConfig(bufferedWriter, "createcost", createCost);
}
if (destroyCost != -1) {
writeConfig(bufferedWriter, "destroycost", destroyCost);
}
writeConfig(bufferedWriter, "toowner", toOwner);
}
/**
* Saves the types of blocks used for the gate frame/border using a buffered writer
*
* @param bufferedWriter <p>The buffered writer to write to</p>
* @throws IOException <p>If unable to write to the buffered writer</p>
*/
private void saveFrameBlockTypes(BufferedWriter bufferedWriter) throws IOException {
for (Map.Entry<Character, Material> entry : types.entrySet()) {
Character type = entry.getKey();
Material value = entry.getValue();
// Skip control values
if (type.equals(GateHandler.getAnythingCharacter()) ||
type.equals(GateHandler.getEntranceCharacter()) ||
type.equals(GateHandler.getExitCharacter())) {
continue;
}
bufferedWriter.append(type);
bufferedWriter.append('=');
if (value != null) {
bufferedWriter.append(value.toString());
}
bufferedWriter.newLine();
}
}
/**
* Writes an integer to a config
*
* @param bufferedWriter <p>The buffered writer to write the config to</p>
* @param key <p>The config key to save</p>
* @param value <p>The value of the config key</p>
* @throws IOException <p>If unable to write to the buffered writer</p>
*/
private void writeConfig(BufferedWriter bufferedWriter, String key, int value) throws IOException {
writeConfig(bufferedWriter, "%s=%d", key, value);
}
/**
* Writes a boolean to a config
*
* @param bufferedWriter <p>The buffered writer to write the config to</p>
* @param key <p>The config key to save</p>
* @param value <p>The value of the config key</p>
* @throws IOException <p>If unable to write to the buffered writer</p>
*/
private void writeConfig(BufferedWriter bufferedWriter, String key, boolean value) throws IOException {
writeConfig(bufferedWriter, "%s=%b", key, value);
}
/**
* Writes a string to a config
*
* @param bufferedWriter <p>The buffered writer to write the config to</p>
* @param key <p>The config key to save</p>
* @param value <p>The value of the config key</p>
* @throws IOException <p>If unable to write to the buffered writer</p>
*/
private void writeConfig(BufferedWriter bufferedWriter, String key, String value) throws IOException {
writeConfig(bufferedWriter, "%s=%s", key, value);
}
/**
* Writes a formatted string to a buffered writer
*
* @param bufferedWriter <p>The buffered writer to write the formatted string to</p>
* @param format <p>The format to use</p>
* @param key <p>The config key to save</p>
* @param value <p>The config value to save</p>
* @throws IOException <p>If unable to write to the buffered writer</p>
*/
private void writeConfig(BufferedWriter bufferedWriter, String format, String key, Object value) throws IOException {
bufferedWriter.append(String.format(format, key, value));
bufferedWriter.newLine();
}
}

View File

@ -0,0 +1,344 @@
package net.knarcraft.stargate.portal;
import net.knarcraft.stargate.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.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Scanner;
import java.util.logging.Level;
public class GateHandler {
private static final Character ANYTHING = ' ';
private static final Character ENTRANCE = '.';
private static final Character EXIT = '*';
private static Material defaultPortalBlockOpen = Material.NETHER_PORTAL;
private static Material defaultPortalBlockClosed = Material.AIR;
private static Material defaultButton = Material.STONE_BUTTON;
private static final HashMap<String, Gate> gates = new HashMap<>();
private static final HashMap<Material, List<Gate>> controlBlocks = new HashMap<>();
private static final HashSet<Material> frameBlocks = new HashSet<>();
private GateHandler() {
}
/**
* Gets the character used for blocks that are not part of the gate
*
* @return <p>The character used for blocks that are not part of the gate</p>
*/
public static Character getAnythingCharacter() {
return ANYTHING;
}
/**
* Gets the character used for defining the entrance
*
* @return <p>The character used for defining the entrance</p>
*/
public static Character getEntranceCharacter() {
return ENTRANCE;
}
public static Character getExitCharacter() {
return EXIT;
}
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) {
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;
}
}
public static Gate loadGate(String fileName, String parentFolder, Scanner scanner) {
boolean designing = false;
List<List<Character>> design = new ArrayList<>();
HashMap<Character, Material> types = new HashMap<>();
HashMap<String, String> config = new HashMap<>();
HashSet<Material> 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 {
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
if (designing) {
List<Character> 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 null;
}
row.add(symbol);
}
design.add(row);
} else {
if (!line.isEmpty() && !line.startsWith("#")) {
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);
}
} 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 null;
} finally {
if (scanner != null) {
scanner.close();
}
}
Character[][] layout = new Character[design.size()][cols];
//y = relative line number of layout file
for (int y = 0; y < design.size(); y++) {
List<Character> 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;
}
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", -1);
int createCost = readConfig(config, fileName, "createcost", -1);
int destroyCost = readConfig(config, fileName, "destroycost", -1);
boolean toOwner = (config.containsKey("toowner") ? Boolean.valueOf(config.get("toowner")) : EconomyHandler.toOwner);
Gate gate = new Gate(fileName, new GateLayout(layout), types, portalOpenBlock, portalClosedBlock, portalButton, useCost,
createCost, destroyCost, toOwner);
if (gate.getLayout().getControls().length != 2) {
Stargate.log.log(Level.SEVERE, "Could not load Gate " + fileName + " - Gates must have exactly 2 control points.");
return null;
}
if (!MaterialHelper.isButtonCompatible(gate.getPortalButton())) {
Stargate.log.log(Level.SEVERE, "Could not load Gate " + fileName + " - Gate button must be a type of button.");
return null;
}
// Merge frame types, add open mat to list
frameBlocks.addAll(frameTypes);
gate.save(parentFolder + "/"); // Updates format for version changes
return gate;
}
private static int readConfig(HashMap<String, String> config, String fileName, String key, int defaultInteger) {
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 defaultInteger;
}
/**
* Gets the material defined in the config
*
* @param config <p>The config to read</p>
* @param fileName <p>The config file the config belongs to</p>
* @param key <p>The config key to read</p>
* @param defaultMaterial <p>The default material to use, in case the config is invalid</p>
* @return <p>The material to use</p>
*/
private static Material readConfig(HashMap<String, String> 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 <p>The folder containing the gates</p>
*/
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 <p>The folder containing gate config files</p>
*/
public 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 <p>The name of the gate file</p>
* @param gateFolder <p>The folder containing gates</p>
*/
private static void loadGateFromJar(String gateFile, String gateFolder) {
Scanner scanner = new Scanner(Gate.class.getResourceAsStream("/gates/" + gateFile));
Gate gate = loadGate(gateFile, gateFolder, scanner);
if (gate != null) {
registerGate(gate);
}
}
/**
* Gets the gates with the given control block
*
* <p>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.</p>
*
* @param block <p>The control block to check</p>
* @return <p>A list of gates using the given control block</p>
*/
public static Gate[] getGatesByControlBlock(Block block) {
return getGatesByControlBlock(block.getType());
}
/**
* Gets the gates with the given control block
*
* @param type <p>The type of the control block to check</p>
* @return <p>A list of gates using the given material for control block</p>
*/
public static Gate[] getGatesByControlBlock(Material type) {
Gate[] result = new Gate[0];
List<Gate> lookup = controlBlocks.get(type);
if (lookup != null) {
result = lookup.toArray(result);
}
return result;
}
/**
* Gets a portal by its name (filename before .gate)
*
* @param name <p>The name of the gate to get</p>
* @return <p>The gate with the given name</p>
*/
public static Gate getGateByName(String name) {
return gates.get(name);
}
/**
* Gets the number of loaded gate configurations
*
* @return <p>The number of loaded gate configurations</p>
*/
public static int getGateCount() {
return gates.size();
}
/**
* Checks whether the given material is used for the frame of any portals
*
* @param type <p>The material type to check</p>
* @return <p>True if the material is used for the frame of at least one portal</p>
*/
public static boolean isGateBlock(Material type) {
return frameBlocks.contains(type);
}
/**
* Clears all loaded gates
*/
public static void clearGates() {
gates.clear();
controlBlocks.clear();
frameBlocks.clear();
}
}

View File

@ -0,0 +1,202 @@
package net.knarcraft.stargate.portal;
import net.knarcraft.stargate.RelativeBlockVector;
import java.io.BufferedWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* The gate layout describes where every part of the gate should be
*
* <p>The gate layout parses a layout described by a Character matrix and stores the different parts of the gate as
* relative block vectors.</p>
*/
public class GateLayout {
private Character [][] layout;
private final HashMap<RelativeBlockVector, Integer> exits = new HashMap<>();
private RelativeBlockVector[] entrances = new RelativeBlockVector[0];
private RelativeBlockVector[] border = new RelativeBlockVector[0];
private RelativeBlockVector[] controls = new RelativeBlockVector[0];
private RelativeBlockVector exitBlock = null;
/**
* Instantiates a new gate layout
*
* @param layout <p>A character array describing the layout</p>
*/
public GateLayout(Character[][] layout) {
this.layout = layout;
readLayout();
}
/**
* Gets the character array describing this layout
*
* @return <p>The character array describing this layout</p>
*/
public Character[][] getLayout() {
return this.layout;
}
/**
* Gets the locations of entrances for this gate
*
* @return <p>The locations of entrances for this gate</p>
*/
public RelativeBlockVector[] getEntrances() {
return entrances;
}
/**
* Gets the locations of border blocks for the gate described by this layout
*
* <p>A border block is basically any block of the frame. In terms of the nether gate, the border blocks are every
* block of the gate that's not air when the gate is closed. The sign and button are not border blocks.</p>
*
* @return <p>The locations of border blocks for this gate</p>
*/
public RelativeBlockVector[] getBorder() {
return border;
}
/**
* Gets the exit block defined in the layout
*
* @return <p>The exit block defined in the layout</p>
*/
public RelativeBlockVector getExit() {
return exitBlock;
}
/**
* Gets other possible exits of the gate
*
* @return <p>Other possible gate exits</p>
*/
public HashMap<RelativeBlockVector, Integer> getExits() {
return exits;
}
/**
* Gets the locations of the control blocks for this gate
*
* <p>The control blocks are the blocks where a sign can be placed to create a portal.</p>
*
* @return <p>The locations of the control blocks for this gate</p>
*/
public RelativeBlockVector[] getControls() {
return controls;
}
/**
* Saves the gate layout using a buffered writer
*
* @param bufferedWriter <p>The buffered writer to write to</p>
* @throws IOException <p>If unable to write to the buffered writer</p>
*/
public void save(BufferedWriter bufferedWriter) throws IOException {
for (Character[] line : this.layout) {
for (Character symbol : line) {
bufferedWriter.append(symbol);
}
bufferedWriter.newLine();
}
}
/**
* Reads the gate layout to relative block vectors
*/
private void readLayout() {
List<RelativeBlockVector> entranceList = new ArrayList<>();
List<RelativeBlockVector> borderList = new ArrayList<>();
List<RelativeBlockVector> controlList = new ArrayList<>();
RelativeBlockVector[] relativeExits = new RelativeBlockVector[layout[0].length];
RelativeBlockVector lastExit = null;
int[] exitDepths = readLayout(controlList, entranceList, borderList);
//Generate other possible exits
for (int x = 0; x < exitDepths.length; x++) {
relativeExits[x] = new RelativeBlockVector(x, exitDepths[x], 0);
}
//Add non-null exits to the exits list
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);
}
/**
* Reads the given layout matrix, filling in the given lists of relative block vectors
*
* @param controlList <p>The list of control blocks to save to</p>
* @param entranceList <p>The list of entrances to save to</p>
* @param borderList <p>The list of border blocks to save to</p>
* @return <p>A list of depths of possible extra exits</p>
*/
private int[] readLayout(List<RelativeBlockVector> controlList, List<RelativeBlockVector> entranceList,
List<RelativeBlockVector> borderList) {
//Store the depth/line of each
int[] exitDepths = new int[layout[0].length];
int lineCount = layout.length;
for (int lineIndex = 0; lineIndex < lineCount; lineIndex++) {
int rowSize = layout[lineIndex].length;
for (int rowIndex = 0; rowIndex < rowSize; rowIndex++) {
Character key = layout[lineIndex][rowIndex];
parseLayoutCharacter(key, rowIndex, lineIndex, exitDepths, controlList, entranceList, borderList);
}
}
return exitDepths;
}
/**
* Parses one character of the layout
*
* @param key <p>The character read</p>
* @param rowIndex <p>The row of the read character</p>
* @param lineIndex <p>The line of the read character</p>
* @param exitDepths <p>The list of exit depths to save to</p>
* @param controlList <p>The list of control blocks to save to</p>
* @param entranceList <p>The list of entrances to save to</p>
* @param borderList <p>The list of border blocks to save to</p>
*/
private void parseLayoutCharacter(Character key, int rowIndex, int lineIndex, int[] exitDepths,
List<RelativeBlockVector> controlList, List<RelativeBlockVector> entranceList,
List<RelativeBlockVector> borderList) {
//Add control blocks
if (key.equals('-')) {
controlList.add(new RelativeBlockVector(rowIndex, lineIndex, 0));
}
if (key.equals(GateHandler.getEntranceCharacter()) || key.equals(GateHandler.getExitCharacter())) {
//Register entrances
entranceList.add(new RelativeBlockVector(rowIndex, lineIndex, 0));
//Find the lowest exit block at a given x position
exitDepths[rowIndex] = lineIndex;
//Register exit
if (key.equals(GateHandler.getExitCharacter())) {
this.exitBlock = new RelativeBlockVector(rowIndex, lineIndex, 0);
}
} else if (!key.equals(GateHandler.getAnythingCharacter())) {
//Add border
borderList.add(new RelativeBlockVector(rowIndex, lineIndex, 0));
}
}
}

View File

@ -0,0 +1,928 @@
package net.knarcraft.stargate.portal;
import net.knarcraft.stargate.BlockLocation;
import net.knarcraft.stargate.BloxPopulator;
import net.knarcraft.stargate.EconomyHandler;
import net.knarcraft.stargate.RelativeBlockVector;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.event.StargateActivateEvent;
import net.knarcraft.stargate.event.StargateCloseEvent;
import net.knarcraft.stargate.event.StargateDeactivateEvent;
import net.knarcraft.stargate.event.StargateOpenEvent;
import net.knarcraft.stargate.event.StargatePortalEvent;
import org.bukkit.Axis;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.BlockState;
import org.bukkit.block.Sign;
import org.bukkit.block.data.Bisected;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Powerable;
import org.bukkit.entity.Boat;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Vehicle;
import org.bukkit.entity.minecart.RideableMinecart;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.util.Vector;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.logging.Level;
public class Portal {
// Gate location block info
private final BlockLocation topLeft;
private final int modX;
private final int modZ;
private final float rotX;
private final Axis rot;
// Block references
private final BlockLocation id;
private final Gate gate;
private final World world;
private BlockLocation button;
private BlockLocation[] frame;
private BlockLocation[] entrances;
// Gate information
private String name;
private String destination;
private String lastDestination = "";
private String network;
private String ownerName;
private UUID ownerUUID;
private boolean verified;
private boolean fixed;
private Map<PortalOption, Boolean> options;
// In-use information
private Player player;
private Player activePlayer;
private List<String> destinations = new ArrayList<>();
private boolean isOpen = false;
private long openTime;
/**
* Instantiates a new portal
*
* @param topLeft <p>The top-left block of the portal. This is used to decide the positions of the rest of the portal</p>
* @param modX <p></p>
* @param modZ <p></p>
* @param rotX <p></p>
* @param id <p>The location of the portal's id block, which is the sign which activated the portal</p>
* @param button <p>The location of the portal's open button</p>
* @param destination <p>The destination defined on the sign's destination line</p>
* @param name <p>The name of the portal defined on the sign's first line</p>
* @param verified <p>Whether the portal's gate has been verified to match its template</p>
* @param network <p>The network the portal belongs to, defined on the sign's network line</p>
* @param gate <p>The gate template this portal uses</p>
* @param ownerUUID <p>The UUID of the gate's owner</p>
* @param ownerName <p>The name of the gate's owner</p>
* @param options <p>A map containing all possible portal options</p>
*/
Portal(BlockLocation topLeft, int modX, int modZ, float rotX, BlockLocation id, BlockLocation button,
String destination, String name, boolean verified, String network, Gate gate, UUID ownerUUID,
String ownerName, Map<PortalOption, Boolean> options) {
this.topLeft = topLeft;
this.modX = modX;
this.modZ = modZ;
this.rotX = rotX;
this.rot = rotX == 0.0F || rotX == 180.0F ? Axis.X : Axis.Z;
this.id = id;
this.destination = destination;
this.button = button;
this.verified = verified;
this.network = network;
this.name = name;
this.gate = gate;
this.ownerUUID = ownerUUID;
this.ownerName = ownerName;
this.options = options;
this.world = topLeft.getWorld();
this.fixed = destination.length() > 0 || this.isRandom() || this.isBungee();
if (this.isAlwaysOn() && !this.isFixed()) {
this.options.put(PortalOption.ALWAYS_ON, false);
Stargate.debug("Portal", "Can not create a non-fixed always-on gate. Setting AlwaysOn = false");
}
if (this.isRandom() && !this.isAlwaysOn()) {
this.options.put(PortalOption.ALWAYS_ON, true);
Stargate.debug("Portal", "Gate marked as random, set to always-on");
}
if (verified) {
this.drawSign();
}
}
/**
* Removes the special characters |, : and # from a portal name
*
* @param input <p>The name to filter</p>
* @return <p>The filtered name</p>
*/
public static String filterName(String input) {
if (input == null) {
return "";
}
return input.replaceAll("[|:#]", "").trim();
}
/**
* Gets whether this portal is currently open
*
* @return <p>Whether this portal is open</p>
*/
public boolean isOpen() {
return isOpen || isAlwaysOn();
}
/**
* Gets whether this portal is always on
*
* @return <p>Whether this portal is always on</p>
*/
public boolean isAlwaysOn() {
return this.options.get(PortalOption.ALWAYS_ON);
}
/**
* Gets whether this portal is hidden
*
* @return <p>Whether this portal is hidden</p>
*/
public boolean isHidden() {
return this.options.get(PortalOption.HIDDEN);
}
/**
* Gets whether this portal is private
*
* @return <p>Whether this portal is private</p>
*/
public boolean isPrivate() {
return this.options.get(PortalOption.PRIVATE);
}
/**
* Gets whether this portal is free
*
* @return <p>Whether this portal is free</p>
*/
public boolean isFree() {
return this.options.get(PortalOption.FREE);
}
/**
* Gets whether this portal is backwards
*
* <p>A backwards portal is one where players exit through the back.</p>
*
* @return <p>Whether this portal is backwards</p>
*/
public boolean isBackwards() {
return this.options.get(PortalOption.BACKWARDS);
}
/**
* Gets whether this portal is shown on the network even if it's always on
*
* @return <p>Whether portal gate is shown</p>
*/
public boolean isShown() {
return this.options.get(PortalOption.SHOW);
}
/**
* Gets whether this portal shows no network
*
* @return <p>Whether this portal shows no network/p>
*/
public boolean isNoNetwork() {
return this.options.get(PortalOption.NO_NETWORK);
}
/**
* Gets whether this portal goes to a random location on the network
*
* @return <p>Whether this portal goes to a random location</p>
*/
public boolean isRandom() {
return this.options.get(PortalOption.RANDOM);
}
/**
* Gets whether this portal is a bungee portal
*
* @return <p>Whether this portal is a bungee portal</p>
*/
public boolean isBungee() {
return this.options.get(PortalOption.BUNGEE);
}
/**
* Gets the rotation of the portal in degrees
*
* @return <p>The rotation of the portal</p>
*/
public float getRotation() {
return rotX;
}
public Axis getAxis() {
return rot;
}
public Player getActivePlayer() {
return activePlayer;
}
public String getNetwork() {
return network;
}
public void setNetwork(String network) {
this.network = network;
}
public long getOpenTime() {
return openTime;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = filterName(name);
drawSign();
}
public Portal getDestination(Player player) {
if (isRandom()) {
destinations = PortalHandler.getDestinations(this, player, getNetwork());
if (destinations.size() == 0) {
return null;
}
String dest = destinations.get((new Random()).nextInt(destinations.size()));
destinations.clear();
return PortalHandler.getByName(dest, getNetwork());
}
return PortalHandler.getByName(destination, getNetwork());
}
public Portal getDestination() {
return getDestination(null);
}
public void setDestination(Portal destination) {
setDestination(destination.getName());
}
public void setDestination(String destination) {
this.destination = destination;
}
public String getDestinationName() {
return destination;
}
public Gate getGate() {
return gate;
}
public String getOwnerName() {
return ownerName;
}
public UUID getOwnerUUID() {
return ownerUUID;
}
public void setOwner(UUID owner) {
this.ownerUUID = owner;
}
public boolean isOwner(Player player) {
if (this.ownerUUID != null) {
return player.getUniqueId().compareTo(this.ownerUUID) == 0;
} else {
return player.getName().equalsIgnoreCase(this.ownerName);
}
}
public BlockLocation[] getEntrances() {
if (entrances == null) {
RelativeBlockVector[] space = gate.getLayout().getEntrances();
entrances = new BlockLocation[space.length];
int i = 0;
for (RelativeBlockVector vector : space) {
entrances[i++] = getBlockAt(vector);
}
}
return entrances;
}
public BlockLocation[] getFrame() {
if (frame == null) {
RelativeBlockVector[] border = gate.getLayout().getBorder();
frame = new BlockLocation[border.length];
int i = 0;
for (RelativeBlockVector vector : border) {
frame[i++] = getBlockAt(vector);
}
}
return frame;
}
public BlockLocation getSign() {
return id;
}
public World getWorld() {
return world;
}
public BlockLocation getButton() {
return button;
}
public void setButton(BlockLocation button) {
this.button = button;
}
public boolean open(boolean force) {
return open(null, force);
}
public boolean open(Player openFor, boolean force) {
// Call the StargateOpenEvent
StargateOpenEvent event = new StargateOpenEvent(openFor, this, force);
Stargate.server.getPluginManager().callEvent(event);
if (event.isCancelled()) return false;
force = event.getForce();
if (isOpen() && !force) return false;
Material openType = gate.getPortalOpenBlock();
Axis ax = openType == Material.NETHER_PORTAL ? rot : null;
for (BlockLocation inside : getEntrances()) {
Stargate.blockPopulatorQueue.add(new BloxPopulator(inside, openType, ax));
}
isOpen = true;
openTime = System.currentTimeMillis() / 1000;
Stargate.openList.add(this);
Stargate.activeList.remove(this);
// Open remote gate
if (!isAlwaysOn()) {
player = openFor;
Portal end = getDestination();
// Only open dest if it's not-fixed or points at this gate
if (!isRandom() && end != null && (!end.isFixed() || end.getDestinationName().equalsIgnoreCase(getName())) && !end.isOpen()) {
end.open(openFor, false);
end.setDestination(this);
if (end.isVerified()) end.drawSign();
}
}
return true;
}
public void close(boolean force) {
if (!isOpen) return;
// Call the StargateCloseEvent
StargateCloseEvent event = new StargateCloseEvent(this, force);
Stargate.server.getPluginManager().callEvent(event);
if (event.isCancelled()) return;
force = event.getForce();
if (isAlwaysOn() && !force) return; // Only close always-open if forced
// Close this gate, then the dest gate.
Material closedType = gate.getPortalClosedBlock();
for (BlockLocation inside : getEntrances()) {
Stargate.blockPopulatorQueue.add(new BloxPopulator(inside, closedType));
}
player = null;
isOpen = false;
Stargate.openList.remove(this);
Stargate.activeList.remove(this);
if (!isAlwaysOn()) {
Portal end = getDestination();
if (end != null && end.isOpen()) {
end.deactivate(); // Clear it's destination first.
end.close(false);
}
}
deactivate();
}
public boolean isOpenFor(Player player) {
if (!isOpen) {
return false;
}
if ((isAlwaysOn()) || (this.player == null)) {
return true;
}
return (player != null) && (player.getName().equalsIgnoreCase(this.player.getName()));
}
/**
* Gets whether this portal points to a fixed exit portal
*
* @return <p>True if this portal points to a fixed exit portal</p>
*/
public boolean isFixed() {
return fixed;
}
public void setFixed(boolean fixed) {
this.fixed = fixed;
}
public boolean isPowered() {
RelativeBlockVector[] controls = gate.getLayout().getControls();
for (RelativeBlockVector vector : controls) {
BlockData data = getBlockAt(vector).getBlock().getBlockData();
if (data instanceof Powerable && ((Powerable) data).isPowered()) {
return true;
}
}
return false;
}
/**
* Teleports a player to this portal
*
* @param player <p>The player to teleport</p>
* @param origin <p>The portal the player teleports from</p>
* @param event <p>The player move event triggering the event</p>
*/
public void teleport(Player player, Portal origin, PlayerMoveEvent event) {
Location traveller = player.getLocation();
Location exit = getExit(player, traveller);
//Rotate the player to face out from the portal
int adjust = 180;
if (isBackwards() != origin.isBackwards()) {
adjust = 0;
}
exit.setYaw(traveller.getYaw() - origin.getRotation() + this.getRotation() + adjust);
// Call the StargatePortalEvent to allow plugins to change destination
if (!origin.equals(this)) {
StargatePortalEvent stargatePortalEvent = new StargatePortalEvent(player, origin, this, exit);
Stargate.server.getPluginManager().callEvent(stargatePortalEvent);
// Teleport is cancelled
if (stargatePortalEvent.isCancelled()) {
origin.teleport(player, origin, event);
return;
}
// Update exit if needed
exit = stargatePortalEvent.getExit();
}
// If no event is passed in, assume it's a teleport, and act as such
if (event == null) {
exit.setYaw(this.getRotation());
player.teleport(exit);
} else {
// The new method to teleport in a move event is set the "to" field.
event.setTo(exit);
}
}
/**
* Teleports a vehicle to this portal
*
* @param vehicle <p>The vehicle to teleport</p>
*/
public void teleport(final Vehicle vehicle) {
Location traveller = new Location(this.world, vehicle.getLocation().getX(), vehicle.getLocation().getY(),
vehicle.getLocation().getZ());
Stargate.log.info(Stargate.getString("prefix") + "Location of vehicle is " + traveller);
Location exit = getExit(vehicle, traveller);
double velocity = vehicle.getVelocity().length();
// Stop and teleport
vehicle.setVelocity(new Vector());
// Get new velocity
final Vector newVelocity = new Vector(modX, 0.0F, modZ);
newVelocity.multiply(velocity);
List<Entity> passengers = vehicle.getPassengers();
World vehicleWorld = exit.getWorld();
if (vehicleWorld == null) {
Stargate.log.warning(Stargate.getString("prefix") + "Unable to get the world to teleport the vehicle to");
return;
}
if (!passengers.isEmpty()) {
if (vehicle instanceof RideableMinecart || vehicle instanceof Boat) {
putPlayerInNewVehicle(vehicle, passengers, vehicleWorld, exit, newVelocity);
return;
}
vehicle.eject();
handleVehiclePassengers(vehicle, passengers, vehicle, exit);
vehicle.teleport(exit);
Stargate.server.getScheduler().scheduleSyncDelayedTask(Stargate.stargate, () -> vehicle.setVelocity(newVelocity), 3);
} else {
Stargate.log.info(Stargate.getString("prefix") + "Teleported vehicle to " + exit);
vehicle.teleport(exit);
Stargate.server.getScheduler().scheduleSyncDelayedTask(Stargate.stargate, () -> {
vehicle.setVelocity(newVelocity);
}, 1);
}
}
private void putPlayerInNewVehicle(Vehicle vehicle, List<Entity> passengers, World vehicleWorld, Location exit, Vector newVelocity) {
Vehicle newVehicle = vehicleWorld.spawn(exit, vehicle.getClass());
vehicle.eject();
vehicle.remove();
handleVehiclePassengers(vehicle, passengers, newVehicle, exit);
Stargate.server.getScheduler().scheduleSyncDelayedTask(Stargate.stargate, () -> newVehicle.setVelocity(newVelocity), 1);
}
private void handleVehiclePassengers(Vehicle sourceVehicle, List<Entity> passengers, Vehicle targetVehicle, Location exit) {
for (Entity passenger : passengers) {
passenger.eject();
Stargate.log.info("Teleporting passenger" + passenger + " to " + exit);
if (!passenger.teleport(exit)) {
Stargate.log.info("Failed to teleport passenger");
}
Stargate.server.getScheduler().scheduleSyncDelayedTask(Stargate.stargate, () -> targetVehicle.addPassenger(passenger), 1);
}
}
/**
* Gets the exit location for a given entity and current location
*
* @param entity <p>The entity to teleport (used to determine distance from portal to avoid suffocation)</p>
* @param traveller <p>The location of the entity travelling</p>
* @return <p>The location the entity should be teleported to.</p>
*/
public Location getExit(Entity entity, Location traveller) {
Location exitLocation = null;
// Check if the gate has an exit block
if (gate.getLayout().getExit() != null) {
BlockLocation exit = getBlockAt(gate.getLayout().getExit());
int back = (isBackwards()) ? -1 : 1;
double entitySize = Math.ceil((float) Math.max(entity.getBoundingBox().getWidthX(), entity.getBoundingBox().getWidthZ()));
exitLocation = exit.modRelativeLoc(0D, 0D, entitySize, traveller.getYaw(), traveller.getPitch(), modX * back, 1, modZ * back);
} else {
Stargate.log.log(Level.WARNING, Stargate.getString("prefix") + "Missing destination point in .gate file " + gate.getFilename());
}
if (exitLocation != null) {
//Prevent traveller from spawning inside a slab
BlockData blockData = getWorld().getBlockAt(exitLocation).getBlockData();
if (blockData instanceof Bisected && ((Bisected) blockData).getHalf() == Bisected.Half.BOTTOM) {
exitLocation.add(0, 0.5, 0);
}
exitLocation.setPitch(traveller.getPitch());
return exitLocation;
} else {
Stargate.log.log(Level.WARNING, Stargate.getString("prefix") + "Unable to generate exit location");
}
return traveller;
}
/**
* Checks whether the chunk the portal is located at is loaded
*
* @return <p>True if the chunk containing the portal is loaded</p>
*/
public boolean isChunkLoaded() {
//TODO: Improve this in the case where the portal sits between two chunks
return getWorld().isChunkLoaded(topLeft.getBlock().getChunk());
}
/**
* Gets the identity (sign) location of the portal
*
* @return <p>The identity location of the portal</p>
*/
public BlockLocation getId() {
return this.id;
}
public int getModX() {
return this.modX;
}
public int getModZ() {
return this.modZ;
}
public float getRotX() {
return this.rotX;
}
/**
* Gets the location of the top-left block of the portal
*
* @return <p>The location of the top-left portal block</p>
*/
public BlockLocation getTopLeft() {
return this.topLeft;
}
/**
* Verifies that all control blocks in this portal follows its gate template
*
* @return <p>True if all control blocks were verified</p>
*/
public boolean isVerified() {
verified = true;
if (!Stargate.verifyPortals) {
return true;
}
for (RelativeBlockVector control : gate.getLayout().getControls()) {
verified = verified && getBlockAt(control).getBlock().getType().equals(gate.getControlBlock());
}
return verified;
}
/**
* Gets the result of the last portal verification
*
* @return <p>True if this portal was verified</p>
*/
public boolean wasVerified() {
if (!Stargate.verifyPortals) {
return true;
}
return verified;
}
/**
* Checks if all blocks in a gate matches the gate template
*
* @return <p>True if all blocks match the gate template</p>
*/
public boolean checkIntegrity() {
if (!Stargate.verifyPortals) {
return true;
}
return gate.matches(topLeft, modX, modZ);
}
/**
* Activates this portal for the given player
*
* @param player <p>The player to activate the portal for</p>
* @return <p>True if the portal was activated</p>
*/
public boolean activate(Player player) {
destinations.clear();
destination = "";
Stargate.activeList.add(this);
activePlayer = player;
String network = getNetwork();
destinations = PortalHandler.getDestinations(this, player, network);
if (Stargate.sortLists) {
Collections.sort(destinations);
}
if (Stargate.destMemory && !lastDestination.isEmpty() && destinations.contains(lastDestination)) {
destination = lastDestination;
}
StargateActivateEvent event = new StargateActivateEvent(this, player, destinations, destination);
Stargate.server.getPluginManager().callEvent(event);
if (event.isCancelled()) {
Stargate.activeList.remove(this);
return false;
}
destination = event.getDestination();
destinations = event.getDestinations();
drawSign();
return true;
}
/**
* Deactivates this portal
*/
public void deactivate() {
StargateDeactivateEvent event = new StargateDeactivateEvent(this);
Stargate.server.getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
}
Stargate.activeList.remove(this);
if (isFixed()) {
return;
}
destinations.clear();
destination = "";
activePlayer = null;
drawSign();
}
public boolean isActive() {
return isFixed() || (destinations.size() > 0);
}
public void cycleDestination(Player player) {
cycleDestination(player, 1);
}
public void cycleDestination(Player player, int dir) {
boolean activate = false;
if (!isActive() || getActivePlayer() != player) {
// If the event is cancelled, return
if (!activate(player)) {
return;
}
Stargate.debug("cycleDestination", "Network Size: " + PortalHandler.getNetwork(network).size());
Stargate.debug("cycleDestination", "Player has access to: " + destinations.size());
activate = true;
}
if (destinations.size() == 0) {
Stargate.sendMessage(player, Stargate.getString("destEmpty"));
return;
}
if (!Stargate.destMemory || !activate || lastDestination.isEmpty()) {
int index = destinations.indexOf(destination);
index += dir;
if (index >= destinations.size())
index = 0;
else if (index < 0)
index = destinations.size() - 1;
destination = destinations.get(index);
lastDestination = destination;
}
openTime = System.currentTimeMillis() / 1000;
drawSign();
}
public final void drawSign() {
BlockState state = id.getBlock().getState();
if (!(state instanceof Sign)) {
Stargate.log.warning(Stargate.getString("prefix") + "Sign block is not a Sign object");
Stargate.debug("Portal::drawSign", "Block: " + id.getBlock().getType() + " @ " + id.getBlock().getLocation());
return;
}
Sign sign = (Sign) state;
Stargate.setLine(sign, 0, "-" + name + "-");
int max = destinations.size() - 1;
int done = 0;
if (!isActive()) {
Stargate.setLine(sign, ++done, Stargate.getString("signRightClick"));
Stargate.setLine(sign, ++done, Stargate.getString("signToUse"));
if (!isNoNetwork()) {
Stargate.setLine(sign, ++done, "(" + network + ")");
}
} else {
// Awesome new logic for Bungee gates
if (isBungee()) {
Stargate.setLine(sign, ++done, Stargate.getString("bungeeSign"));
Stargate.setLine(sign, ++done, ">" + destination + "<");
Stargate.setLine(sign, ++done, "[" + network + "]");
} else if (isFixed()) {
if (isRandom()) {
Stargate.setLine(sign, ++done, "> " + Stargate.getString("signRandom") + " <");
} else {
Stargate.setLine(sign, ++done, ">" + destination + "<");
}
if (isNoNetwork()) {
Stargate.setLine(sign, ++done, "");
} else {
Stargate.setLine(sign, ++done, "(" + network + ")");
}
Portal dest = PortalHandler.getByName(destination, network);
if (dest == null && !isRandom()) {
Stargate.setLine(sign, ++done, Stargate.getString("signDisconnected"));
} else {
Stargate.setLine(sign, ++done, "");
}
} else {
int index = destinations.indexOf(destination);
if ((index == max) && (max > 1) && (++done <= 3)) {
if (EconomyHandler.useEconomy() && EconomyHandler.freeGatesGreen) {
Portal dest = PortalHandler.getByName(destinations.get(index - 2), network);
boolean green = Stargate.isFree(activePlayer, this, dest);
Stargate.setLine(sign, done, (green ? ChatColor.DARK_GREEN : "") + destinations.get(index - 2));
} else {
Stargate.setLine(sign, done, destinations.get(index - 2));
}
}
if ((index > 0) && (++done <= 3)) {
if (EconomyHandler.useEconomy() && EconomyHandler.freeGatesGreen) {
Portal dest = PortalHandler.getByName(destinations.get(index - 1), network);
boolean green = Stargate.isFree(activePlayer, this, dest);
Stargate.setLine(sign, done, (green ? ChatColor.DARK_GREEN : "") + destinations.get(index - 1));
} else {
Stargate.setLine(sign, done, destinations.get(index - 1));
}
}
if (++done <= 3) {
if (EconomyHandler.useEconomy() && EconomyHandler.freeGatesGreen) {
Portal dest = PortalHandler.getByName(destination, network);
boolean green = Stargate.isFree(activePlayer, this, dest);
Stargate.setLine(sign, done, (green ? ChatColor.DARK_GREEN : "") + ">" + destination + "<");
} else {
Stargate.setLine(sign, done, " >" + destination + "< ");
}
}
if ((max >= index + 1) && (++done <= 3)) {
if (EconomyHandler.useEconomy() && EconomyHandler.freeGatesGreen) {
Portal dest = PortalHandler.getByName(destinations.get(index + 1), network);
boolean green = Stargate.isFree(activePlayer, this, dest);
Stargate.setLine(sign, done, (green ? ChatColor.DARK_GREEN : "") + destinations.get(index + 1));
} else {
Stargate.setLine(sign, done, destinations.get(index + 1));
}
}
if ((max >= index + 2) && (++done <= 3)) {
if (EconomyHandler.useEconomy() && EconomyHandler.freeGatesGreen) {
Portal dest = PortalHandler.getByName(destinations.get(index + 2), network);
boolean green = Stargate.isFree(activePlayer, this, dest);
Stargate.setLine(sign, done, (green ? ChatColor.DARK_GREEN : "") + destinations.get(index + 2));
} else {
Stargate.setLine(sign, done, destinations.get(index + 2));
}
}
}
}
for (done++; done <= 3; done++) {
sign.setLine(done, "");
}
sign.update();
}
/**
* Gets the block at a relative block vector location
*
* @param vector <p>The relative block vector</p>
* @return <p>The block at the given relative position</p>
*/
BlockLocation getBlockAt(RelativeBlockVector vector) {
return topLeft.modRelative(vector.getRight(), vector.getDepth(), vector.getDistance(), modX, 1, modZ);
}
@Override
public String toString() {
return String.format("Portal [id=%s, network=%s name=%s, type=%s]", id, network, name, gate.getFilename());
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((network == null) ? 0 : network.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Portal other = (Portal) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equalsIgnoreCase(other.name))
return false;
if (network == null) {
return other.network == null;
} else {
return network.equalsIgnoreCase(other.network);
}
}
}

View File

@ -0,0 +1,951 @@
package net.knarcraft.stargate.portal;
import net.knarcraft.stargate.BlockLocation;
import net.knarcraft.stargate.RelativeBlockVector;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.TwoTuple;
import net.knarcraft.stargate.event.StargateCreateEvent;
import net.knarcraft.stargate.utility.EconomyHelper;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.Sign;
import org.bukkit.block.data.Directional;
import org.bukkit.block.data.type.WallSign;
import org.bukkit.entity.Player;
import org.bukkit.event.block.SignChangeEvent;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.UUID;
import java.util.logging.Level;
public class PortalHandler {
// Static variables used to store portal lists
private static final Map<BlockLocation, Portal> lookupBlocks = new HashMap<>();
private static final Map<BlockLocation, Portal> lookupEntrances = new HashMap<>();
private static final Map<BlockLocation, Portal> lookupControls = new HashMap<>();
private static final List<Portal> allPortals = new ArrayList<>();
private static final HashMap<String, List<String>> allPortalsNet = new HashMap<>();
private static final HashMap<String, HashMap<String, Portal>> lookupNamesNet = new HashMap<>();
// A list of Bungee gates
private static final Map<String, Portal> bungeePortals = new HashMap<>();
private PortalHandler() {
}
public static List<String> getNetwork(String network) {
return allPortalsNet.get(network.toLowerCase());
}
/**
* Gets all destinations in the network viewable by the given player
*
* @param entrancePortal <p>The portal the user is entering from</p>
* @param player <p>The player who wants to see destinations</p>
* @param network <p>The network to get destinations from</p>
* @return <p>All destinations the player can go to</p>
*/
public static List<String> getDestinations(Portal entrancePortal, Player player, String network) {
List<String> destinations = new ArrayList<>();
for (String destination : allPortalsNet.get(network.toLowerCase())) {
Portal portal = getByName(destination, network);
if (portal == null) {
continue;
}
// Check if destination is a random gate
if (portal.isRandom()) {
continue;
}
// Check if destination is always open (Don't show if so)
if (portal.isAlwaysOn() && !portal.isShown()) {
continue;
}
// Check if destination is this portal
if (destination.equalsIgnoreCase(entrancePortal.getName())) {
continue;
}
// Check if destination is a fixed gate not pointing to this gate
if (portal.isFixed() && !portal.getDestinationName().equalsIgnoreCase(entrancePortal.getName())) {
continue;
}
// Allow random use by non-players (Minecarts)
if (player == null) {
destinations.add(portal.getName());
continue;
}
// Check if this player can access the dest world
if (!Stargate.canAccessWorld(player, portal.getWorld().getName())) {
Stargate.log.info("cannot access world");
continue;
}
// Visible to this player.
if (Stargate.canSee(player, portal)) {
destinations.add(portal.getName());
}
}
return destinations;
}
/**
* Un-registers the given portal
*
* @param portal <p>The portal to un-register</p>
* @param removeAll <p>Whether to remove the portal from the list of all portals</p>
*/
public static void unregisterPortal(Portal portal, boolean removeAll) {
Stargate.debug("Unregister", "Unregistering gate " + portal.getName());
portal.close(true);
for (BlockLocation block : portal.getFrame()) {
lookupBlocks.remove(block);
}
// Include the sign and button
lookupBlocks.remove(portal.getId());
lookupControls.remove(portal.getId());
if (portal.getButton() != null) {
lookupBlocks.remove(portal.getButton());
lookupControls.remove(portal.getButton());
}
for (BlockLocation entrance : portal.getEntrances()) {
lookupEntrances.remove(entrance);
}
if (removeAll) {
allPortals.remove(portal);
}
if (portal.isBungee()) {
bungeePortals.remove(portal.getName().toLowerCase());
} else {
lookupNamesNet.get(portal.getNetwork().toLowerCase()).remove(portal.getName().toLowerCase());
allPortalsNet.get(portal.getNetwork().toLowerCase()).remove(portal.getName().toLowerCase());
for (String originName : allPortalsNet.get(portal.getNetwork().toLowerCase())) {
Portal origin = getByName(originName, portal.getNetwork());
if (origin == null) continue;
if (!origin.getDestinationName().equalsIgnoreCase(portal.getName())) continue;
if (!origin.isVerified()) continue;
if (origin.isFixed()) origin.drawSign();
if (origin.isAlwaysOn()) origin.close(true);
}
}
if (portal.getId().getBlock().getBlockData() instanceof WallSign) {
Sign sign = (Sign) portal.getId().getBlock().getState();
sign.setLine(0, portal.getName());
sign.setLine(1, "");
sign.setLine(2, "");
sign.setLine(3, "");
sign.update();
}
saveAllGates(portal.getWorld());
}
/**
* Registers a portal
*
* @param portal <p>The portal to register</p>
*/
private static void registerPortal(Portal portal) {
portal.setFixed(portal.getDestinationName().length() > 0 || portal.isRandom() || portal.isBungee());
// Bungee gates are stored in their own list
if (portal.isBungee()) {
bungeePortals.put(portal.getName().toLowerCase(), portal);
} else {
// Check if network exists in our network list
if (!lookupNamesNet.containsKey(portal.getNetwork().toLowerCase())) {
Stargate.debug("register", "Network " + portal.getNetwork() + " not in lookupNamesNet, adding");
lookupNamesNet.put(portal.getNetwork().toLowerCase(), new HashMap<>());
}
lookupNamesNet.get(portal.getNetwork().toLowerCase()).put(portal.getName().toLowerCase(), portal);
// Check if this network exists
if (!allPortalsNet.containsKey(portal.getNetwork().toLowerCase())) {
Stargate.debug("register", "Network " + portal.getNetwork() + " not in allPortalsNet, adding");
allPortalsNet.put(portal.getNetwork().toLowerCase(), new ArrayList<>());
}
allPortalsNet.get(portal.getNetwork().toLowerCase()).add(portal.getName().toLowerCase());
}
for (BlockLocation block : portal.getFrame()) {
lookupBlocks.put(block, portal);
}
// Include the sign and button
lookupBlocks.put(portal.getId(), portal);
lookupControls.put(portal.getId(), portal);
if (portal.getButton() != null) {
lookupBlocks.put(portal.getButton(), portal);
lookupControls.put(portal.getButton(), portal);
}
for (BlockLocation entrance : portal.getEntrances()) {
lookupEntrances.put(entrance, portal);
}
allPortals.add(portal);
}
/**
* Creates a new portal
*
* @param event <p>The sign change event which initialized the creation</p>
* @param player <p>The player who's creating the portal</p>
* @return <p>The created portal</p>
*/
public static Portal createPortal(SignChangeEvent event, Player player) {
BlockLocation id = new BlockLocation(event.getBlock());
Block idParent = id.getParent();
if (idParent == null) {
return null;
}
if (GateHandler.getGatesByControlBlock(idParent).length == 0) {
return null;
}
if (getByBlock(idParent) != null) {
Stargate.debug("createPortal", "idParent belongs to existing gate");
return null;
}
BlockLocation parent = new BlockLocation(player.getWorld(), idParent.getX(), idParent.getY(), idParent.getZ());
BlockLocation topLeft = null;
String name = filterName(event.getLine(0));
String destinationName = filterName(event.getLine(1));
String network = filterName(event.getLine(2));
String options = filterName(event.getLine(3)).toLowerCase();
Map<PortalOption, Boolean> portalOptions = getPortalOptions(player, destinationName, options);
// Moved the layout check so as to avoid invalid messages when not making a gate
int modX = 0;
int modZ = 0;
float rotX = 0f;
BlockFace buttonFacing = BlockFace.DOWN;
if (idParent.getX() > id.getBlock().getX()) {
modZ -= 1;
rotX = 90f;
buttonFacing = BlockFace.WEST;
} else if (idParent.getX() < id.getBlock().getX()) {
modZ += 1;
rotX = 270f;
buttonFacing = BlockFace.EAST;
} else if (idParent.getZ() > id.getBlock().getZ()) {
modX += 1;
rotX = 180f;
buttonFacing = BlockFace.NORTH;
} else if (idParent.getZ() < id.getBlock().getZ()) {
modX -= 1;
rotX = 0f;
buttonFacing = BlockFace.SOUTH;
}
Gate[] possibleGates = GateHandler.getGatesByControlBlock(idParent);
Gate gate = null;
RelativeBlockVector buttonVector = null;
for (Gate possibility : possibleGates) {
if (gate != null || buttonVector != null) {
break;
}
RelativeBlockVector[] vectors = possibility.getLayout().getControls();
RelativeBlockVector otherControl = null;
for (RelativeBlockVector vector : vectors) {
BlockLocation tl = parent.modRelative(-vector.getRight(), -vector.getDepth(), -vector.getDistance(), modX, 1, modZ);
if (gate == null) {
if (possibility.matches(tl, modX, modZ, true)) {
gate = possibility;
topLeft = tl;
if (otherControl != null) {
buttonVector = otherControl;
}
}
} else if (otherControl != null) {
buttonVector = vector;
}
otherControl = vector;
}
}
if ((gate == null) || (buttonVector == null)) {
Stargate.debug("createPortal", "Could not find matching gate layout");
return null;
}
// If the player is trying to create a Bungee gate without permissions, drop out here
// Do this after the gate layout check, in the least
if (options.indexOf(PortalOption.BUNGEE.getCharacterRepresentation()) != -1) {
if (!Stargate.hasPerm(player, "stargate.admin.bungee")) {
Stargate.sendMessage(player, Stargate.getString("bungeeDeny"));
return null;
}
}
if (portalOptions.get(PortalOption.BUNGEE)) {
if (!Stargate.enableBungee) {
Stargate.sendMessage(player, Stargate.getString("bungeeDisabled"));
return null;
} else if (destinationName.isEmpty() || network.isEmpty()) {
Stargate.sendMessage(player, Stargate.getString("bungeeEmpty"));
return null;
}
}
// Debug
StringBuilder builder = new StringBuilder();
for (PortalOption option : portalOptions.keySet()) {
builder.append(option.getCharacterRepresentation()).append(" = ").append(portalOptions.get(option)).append(" ");
}
Stargate.debug("createPortal", builder.toString());
if (!portalOptions.get(PortalOption.BUNGEE) && (network.length() < 1 || network.length() > 11)) {
network = Stargate.getDefaultNetwork();
}
boolean deny = false;
String denyMsg = "";
// Check if the player can create gates on this network
if (!portalOptions.get(PortalOption.BUNGEE) && !Stargate.canCreate(player, network)) {
Stargate.debug("createPortal", "Player doesn't have create permissions on network. Trying personal");
if (Stargate.canCreatePersonal(player)) {
network = player.getName();
if (network.length() > 11) network = network.substring(0, 11);
Stargate.debug("createPortal", "Creating personal portal");
Stargate.sendMessage(player, Stargate.getString("createPersonal"));
} else {
Stargate.debug("createPortal", "Player does not have access to network");
deny = true;
denyMsg = Stargate.getString("createNetDeny");
//return null;
}
}
// Check if the player can create this gate layout
String gateName = gate.getFilename();
gateName = gateName.substring(0, gateName.indexOf('.'));
if (!deny && !Stargate.canCreateGate(player, gateName)) {
Stargate.debug("createPortal", "Player does not have access to gate layout");
deny = true;
denyMsg = Stargate.getString("createGateDeny");
}
// Check if the user can create gates to this world.
if (!portalOptions.get(PortalOption.BUNGEE) && !deny && destinationName.length() > 0) {
Portal p = getByName(destinationName, network);
if (p != null) {
String world = p.getWorld().getName();
if (!Stargate.canAccessWorld(player, world)) {
Stargate.debug("canCreate", "Player does not have access to destination world");
deny = true;
denyMsg = Stargate.getString("createWorldDeny");
}
}
}
// Bleh, gotta check to make sure none of this gate belongs to another gate. Boo slow.
for (RelativeBlockVector v : gate.getLayout().getBorder()) {
BlockLocation b = topLeft.modRelative(v.getRight(), v.getDepth(), v.getDistance(), modX, 1, modZ);
if (getByBlock(b.getBlock()) != null) {
Stargate.debug("createPortal", "Gate conflicts with existing gate");
Stargate.sendMessage(player, Stargate.getString("createConflict"));
return null;
}
}
BlockLocation button = null;
Portal portal;
portal = new Portal(topLeft, modX, modZ, rotX, id, button, destinationName, name, false, network,
gate, player.getUniqueId(), player.getName(), portalOptions);
int cost = Stargate.getCreateCost(player, gate);
// Call StargateCreateEvent
StargateCreateEvent cEvent = new StargateCreateEvent(player, portal, event.getLines(), deny, denyMsg, cost);
Stargate.server.getPluginManager().callEvent(cEvent);
if (cEvent.isCancelled()) {
return null;
}
if (cEvent.getDeny()) {
Stargate.sendMessage(player, cEvent.getDenyReason());
return null;
}
cost = cEvent.getCost();
// Name & Network can be changed in the event, so do these checks here.
if (portal.getName().length() < 1 || portal.getName().length() > 11) {
Stargate.debug("createPortal", "Name length error");
Stargate.sendMessage(player, Stargate.getString("createNameLength"));
return null;
}
// Don't do network checks for bungee gates
if (portal.isBungee()) {
if (bungeePortals.get(portal.getName().toLowerCase()) != null) {
Stargate.debug("createPortal::Bungee", "Gate Exists");
Stargate.sendMessage(player, Stargate.getString("createExists"));
return null;
}
} else {
if (getByName(portal.getName(), portal.getNetwork()) != null) {
Stargate.debug("createPortal", "Name Error");
Stargate.sendMessage(player, Stargate.getString("createExists"));
return null;
}
// Check if there are too many gates in this network
List<String> netList = allPortalsNet.get(portal.getNetwork().toLowerCase());
if (Stargate.maxGates > 0 && netList != null && netList.size() >= Stargate.maxGates) {
Stargate.sendMessage(player, Stargate.getString("createFull"));
return null;
}
}
if (cost > 0) {
if (!Stargate.chargePlayer(player, cost)) {
EconomyHelper.sendInsufficientFundsMessage(name, player, cost);
Stargate.debug("createPortal", "Insufficient Funds");
return null;
}
EconomyHelper.sendDeductMessage(name, player, cost);
}
// No button on an always-open gate.
if (!portalOptions.get(PortalOption.ALWAYS_ON)) {
button = topLeft.modRelative(buttonVector.getRight(), buttonVector.getDepth(), buttonVector.getDistance() + 1, modX, 1, modZ);
Directional buttonData = (Directional) Bukkit.createBlockData(gate.getPortalButton());
buttonData.setFacing(buttonFacing);
button.getBlock().setBlockData(buttonData);
portal.setButton(button);
}
registerPortal(portal);
portal.drawSign();
// Open always on gate
if (portal.isRandom() || portal.isBungee()) {
portal.open(true);
} else if (portal.isAlwaysOn()) {
Portal dest = getByName(destinationName, portal.getNetwork());
if (dest != null) {
portal.open(true);
dest.drawSign();
}
// Set the inside of the gate to its closed material
} else {
for (BlockLocation inside : portal.getEntrances()) {
inside.setType(portal.getGate().getPortalClosedBlock());
}
}
// Don't do network stuff for bungee gates
if (!portal.isBungee()) {
// Open any always on gate pointing at this gate
for (String originName : allPortalsNet.get(portal.getNetwork().toLowerCase())) {
Portal origin = getByName(originName, portal.getNetwork());
if (origin == null) continue;
if (!origin.getDestinationName().equalsIgnoreCase(portal.getName())) continue;
if (!origin.isVerified()) continue;
if (origin.isFixed()) origin.drawSign();
if (origin.isAlwaysOn()) origin.open(true);
}
}
saveAllGates(portal.getWorld());
return portal;
}
/**
* Gets all portal options to be applied to a new gate
*
* @param player <p>The player creating the portal</p>
* @param destinationName <p>The destination of the portal</p>
* @param options <p>The string on the option line of the sign</p>
* @return <p>A map containing all portal options and their values</p>
*/
private static Map<PortalOption, Boolean> getPortalOptions(Player player, String destinationName, String options) {
Map<PortalOption, Boolean> portalOptions = new HashMap<>();
for (PortalOption option : PortalOption.values()) {
portalOptions.put(option, options.indexOf(option.getCharacterRepresentation()) != -1 &&
Stargate.canOption(player, option));
}
// Can not create a non-fixed always-on gate.
if (portalOptions.get(PortalOption.ALWAYS_ON) && destinationName.length() == 0) {
portalOptions.put(PortalOption.ALWAYS_ON, false);
}
// Show isn't useful if always on is false
if (portalOptions.get(PortalOption.SHOW) && !portalOptions.get(PortalOption.ALWAYS_ON)) {
portalOptions.put(PortalOption.SHOW, false);
}
// Random gates are always on and can't be shown
if (portalOptions.get(PortalOption.RANDOM)) {
portalOptions.put(PortalOption.ALWAYS_ON, true);
portalOptions.put(PortalOption.SHOW, false);
}
// Bungee gates are always on and don't support Random
if (portalOptions.get(PortalOption.BUNGEE)) {
portalOptions.put(PortalOption.ALWAYS_ON, true);
portalOptions.put(PortalOption.RANDOM, false);
}
return portalOptions;
}
/**
* Gets a portal given its name
*
* @param name <p>The name of the portal</p>
* @param network <p>The network the portal is connected to</p>
* @return <p>The portal with the given name or null</p>
*/
public static Portal getByName(String name, String network) {
if (!lookupNamesNet.containsKey(network.toLowerCase())) {
return null;
}
return lookupNamesNet.get(network.toLowerCase()).get(name.toLowerCase());
}
/**
* Gets a portal given its entrance
*
* @param location <p>The location of the portal's entrance</p>
* @return <p>The portal at the given location</p>
*/
public static Portal getByEntrance(Location location) {
return lookupEntrances.get(new BlockLocation(location.getWorld(), location.getBlockX(), location.getBlockY(),
location.getBlockZ()));
}
/**
* Gets a portal given its entrance
*
* @param block <p>The block at the portal's entrance</p>
* @return <p>The portal at the given block's location</p>
*/
public static Portal getByEntrance(Block block) {
return lookupEntrances.get(new BlockLocation(block));
}
/**
* Gets a portal given a location adjacent to its entrance
*
* @param loc <p>A location adjacent to the portal's entrance</p>
* @return <p>The portal adjacent to the given location</p>
*/
public static Portal getByAdjacentEntrance(Location loc) {
int centerX = loc.getBlockX();
int centerY = loc.getBlockY();
int centerZ = loc.getBlockZ();
World world = loc.getWorld();
Portal portal = lookupEntrances.get(new BlockLocation(world, centerX, centerY, centerZ));
if (portal != null) {
return portal;
}
portal = lookupEntrances.get(new BlockLocation(world, centerX + 1, centerY, centerZ));
if (portal != null) {
return portal;
}
portal = lookupEntrances.get(new BlockLocation(world, centerX - 1, centerY, centerZ));
if (portal != null) {
return portal;
}
portal = lookupEntrances.get(new BlockLocation(world, centerX, centerY, centerZ + 1));
if (portal != null) {
return portal;
}
portal = lookupEntrances.get(new BlockLocation(world, centerX, centerY, centerZ - 1));
if (portal != null) {
return portal;
}
return null;
}
/**
* Gets a portal given its control block (the block type used for the sign and button)
*
* @param block <p>The portal's control block</p>
* @return <p>The gate with the given control block</p>
*/
public static Portal getByControl(Block block) {
return lookupControls.get(new BlockLocation(block));
}
/**
* Gets a portal given a block
*
* @param block <p>One of the loaded lookup blocks</p>
* @return <p>The portal corresponding to the block</p>
*/
public static Portal getByBlock(Block block) {
return lookupBlocks.get(new BlockLocation(block));
}
/**
* Gets a bungee gate given its name
*
* @param name <p>The name of the bungee gate to get</p>
* @return <p>A bungee gate</p>
*/
public static Portal getBungeeGate(String name) {
return bungeePortals.get(name.toLowerCase());
}
/**
* Saves all gates for the given world
*
* @param world <p>The world to save gates for</p>
*/
public static void saveAllGates(World world) {
Stargate.managedWorlds.add(world.getName());
String loc = Stargate.getSaveLocation() + "/" + world.getName() + ".db";
try {
BufferedWriter bw = new BufferedWriter(new FileWriter(loc, false));
for (Portal portal : allPortals) {
String wName = portal.getWorld().getName();
if (!wName.equalsIgnoreCase(world.getName())) continue;
StringBuilder builder = new StringBuilder();
BlockLocation button = portal.getButton();
builder.append(portal.getName());
builder.append(':');
builder.append(portal.getId().toString());
builder.append(':');
builder.append((button != null) ? button.toString() : "");
builder.append(':');
builder.append(portal.getModX());
builder.append(':');
builder.append(portal.getModZ());
builder.append(':');
builder.append(portal.getRotX());
builder.append(':');
builder.append(portal.getTopLeft().toString());
builder.append(':');
builder.append(portal.getGate().getFilename());
builder.append(':');
builder.append(portal.isFixed() ? portal.getDestinationName() : "");
builder.append(':');
builder.append(portal.getNetwork());
builder.append(':');
UUID owner = portal.getOwnerUUID();
if (owner != null) {
builder.append(portal.getOwnerUUID().toString());
} else {
builder.append(portal.getOwnerName());
}
builder.append(':');
builder.append(portal.isHidden());
builder.append(':');
builder.append(portal.isAlwaysOn());
builder.append(':');
builder.append(portal.isPrivate());
builder.append(':');
builder.append(portal.getWorld().getName());
builder.append(':');
builder.append(portal.isFree());
builder.append(':');
builder.append(portal.isBackwards());
builder.append(':');
builder.append(portal.isShown());
builder.append(':');
builder.append(portal.isNoNetwork());
builder.append(':');
builder.append(portal.isRandom());
builder.append(':');
builder.append(portal.isBungee());
bw.append(builder.toString());
bw.newLine();
}
bw.close();
} catch (Exception e) {
Stargate.log.log(Level.SEVERE, "Exception while writing stargates to " + loc + ": " + e);
}
}
/**
* Clears all loaded gates and gate data from all worlds
*/
public static void clearGates() {
lookupBlocks.clear();
lookupNamesNet.clear();
lookupEntrances.clear();
lookupControls.clear();
allPortals.clear();
allPortalsNet.clear();
}
/**
* Clears all gates loaded in a given world
*
* @param world <p>The world containing the portals to clear</p>
*/
public static void clearGates(World world) {
//This is necessary
List<Portal> portalsToRemove = new ArrayList<>();
allPortals.forEach((portal) -> {
if (portal.getWorld().equals(world)) {
portalsToRemove.add(portal);
}
});
clearGates(portalsToRemove);
}
/**
* Clears a given list of portals from all relevant variables
*
* @param portalsToRemove <p>A list of portals to remove</p>
*/
private static void clearGates(List<Portal> portalsToRemove) {
List<String> portalNames = new ArrayList<>();
portalsToRemove.forEach((portal) -> portalNames.add(portal.getName()));
lookupBlocks.keySet().removeIf((key) -> portalsToRemove.contains(lookupBlocks.get(key)));
lookupNamesNet.keySet().forEach((network) -> lookupNamesNet.get(network).keySet().removeIf((key) ->
portalsToRemove.contains(lookupNamesNet.get(network).get(key))));
//Remove any networks with no portals
lookupNamesNet.keySet().removeIf((key) -> lookupNamesNet.get(key).isEmpty());
lookupEntrances.keySet().removeIf((key) -> portalsToRemove.contains(lookupEntrances.get(key)));
lookupControls.keySet().removeIf((key) -> portalsToRemove.contains(lookupControls.get(key)));
allPortals.removeIf(portalsToRemove::contains);
allPortalsNet.keySet().forEach((network) -> allPortalsNet.get(network).removeIf(portalNames::contains));
//Remove any networks with no portals
allPortalsNet.keySet().removeIf((network) -> allPortalsNet.get(network).isEmpty());
}
/**
* Loads all gates for the given world
*
* @param world <p>The world to load gates for</p>
* @return <p>True if gates could be loaded</p>
*/
public static boolean loadAllGates(World world) {
String location = Stargate.getSaveLocation();
File database = new File(location, world.getName() + ".db");
if (database.exists()) {
return loadGates(world, database);
} else {
Stargate.log.info(Stargate.getString("prefix") + "{" + world.getName() + "} No stargates for world ");
}
return false;
}
/**
* Loads all the given gates
*
* @param world <p>The world to load gates for</p>
* @param database <p>The database file containing the gates</p>
* @return <p>True if the gates were loaded successfully</p>
*/
private static boolean loadGates(World world, File database) {
int lineIndex = 0;
try {
Scanner scanner = new Scanner(database);
while (scanner.hasNextLine()) {
lineIndex++;
String line = scanner.nextLine().trim();
//Ignore empty and comment lines
if (line.startsWith("#") || line.isEmpty()) {
continue;
}
//Check if the min. required portal data is present
String[] portalData = line.split(":");
if (portalData.length < 8) {
Stargate.log.info(Stargate.getString("prefix") + "Invalid line - " + lineIndex);
continue;
}
loadGate(portalData, world, lineIndex);
}
scanner.close();
// Open any always-on gates. Do this here as it should be more efficient than in the loop.
TwoTuple<Integer, Integer> portalCounts = openAlwaysOpenGates();
Stargate.log.info(String.format("%s{%s} Loaded %d stargates with %d set as always-on",
Stargate.getString("prefix"), world.getName(), portalCounts.getSecondValue(),
portalCounts.getFirstValue()));
return true;
} catch (Exception e) {
Stargate.log.log(Level.SEVERE, "Exception while reading stargates from " + database.getName() + ": " + lineIndex);
e.printStackTrace();
}
return false;
}
/**
* Loads one gate from a data array
*
* @param portalData <p>The array describing the portal</p>
* @param world <p>The world to create the portal in</p>
* @param lineIndex <p>The line index to report in case the user needs to fix an error</p>
*/
private static void loadGate(String[] portalData, World world, int lineIndex) {
//Load min. required portal data
String name = portalData[0];
BlockLocation sign = new BlockLocation(world, portalData[1]);
BlockLocation button = (portalData[2].length() > 0) ? new BlockLocation(world, portalData[2]) : null;
int modX = Integer.parseInt(portalData[3]);
int modZ = Integer.parseInt(portalData[4]);
float rotX = Float.parseFloat(portalData[5]);
BlockLocation topLeft = new BlockLocation(world, portalData[6]);
Gate gate = GateHandler.getGateByName(portalData[7]);
if (gate == null) {
Stargate.log.info(Stargate.getString("prefix") + "Gate layout on line " + lineIndex +
" does not exist [" + portalData[7] + "]");
return;
}
//Load extra portal data
String destination = (portalData.length > 8) ? portalData[8] : "";
String network = (portalData.length > 9) ? portalData[9] : Stargate.getDefaultNetwork();
if (network.isEmpty()) {
network = Stargate.getDefaultNetwork();
}
String ownerString = (portalData.length > 10) ? portalData[10] : "";
// Attempt to get owner as UUID
UUID ownerUUID = null;
String ownerName;
if (ownerString.length() > 16) {
try {
ownerUUID = UUID.fromString(ownerString);
OfflinePlayer offlineOwner = Bukkit.getServer().getOfflinePlayer(ownerUUID);
ownerName = offlineOwner.getName();
} catch (IllegalArgumentException ex) {
// neither name nor UUID, so keep it as-is
ownerName = ownerString;
Stargate.debug("loadAllGates", "Invalid stargate owner string: " + ownerString);
}
} else {
ownerName = ownerString;
}
//Creates the new portal
Portal portal = new Portal(topLeft, modX, modZ, rotX, sign, button, destination, name, false,
network, gate, ownerUUID, ownerName, getPortalOptions(portalData));
registerPortal(portal);
portal.close(true);
}
/**
* Gets all portal options stored in the portal data
*
* @param portalData <p>The string list containing all information about a portal</p>
* @return <p>A map between portal options and booleans</p>
*/
private static Map<PortalOption, Boolean> getPortalOptions(String[] portalData) {
Map<PortalOption, Boolean> portalOptions = new HashMap<>();
for (PortalOption option : PortalOption.values()) {
int saveIndex = option.getSaveIndex();
portalOptions.put(option, portalData.length > saveIndex && Boolean.parseBoolean(portalData[saveIndex]));
}
return portalOptions;
}
/**
* Opens all always open gates
*
* @return <p>A TwoTuple where the first value is the number of always open gates and the second value is the total number of gates</p>
*/
private static TwoTuple<Integer, Integer> openAlwaysOpenGates() {
int portalCount = 0;
int openCount = 0;
for (Iterator<Portal> iterator = allPortals.iterator(); iterator.hasNext(); ) {
Portal portal = iterator.next();
if (portal == null) {
continue;
}
// Verify portal integrity/register portal
if (!portal.wasVerified() && (!portal.isVerified() || !portal.checkIntegrity())) {
destroyInvalidStarGate(portal);
iterator.remove();
continue;
}
portalCount++;
//Open the gate if it's set as always open or if it's a bungee gate
if (portal.isFixed() && (Stargate.enableBungee && portal.isBungee() || portal.getDestination() != null &&
portal.isAlwaysOn())) {
portal.open(true);
openCount++;
}
}
return new TwoTuple<>(openCount, portalCount);
}
/**
* Destroys a star gate which has failed its integrity test
*
* @param portal <p>The portal of the star gate</p>
*/
private static void destroyInvalidStarGate(Portal portal) {
// DEBUG
for (RelativeBlockVector control : portal.getGate().getLayout().getControls()) {
if (!portal.getBlockAt(control).getBlock().getType().equals(portal.getGate().getControlBlock())) {
Stargate.debug("loadAllGates", "Control Block Type == " + portal.getBlockAt(control).getBlock().getType().name());
}
}
PortalHandler.unregisterPortal(portal, false);
Stargate.log.info(Stargate.getString("prefix") + "Destroying stargate at " + portal.toString());
}
/**
* Closes all star gate portals
*/
public static void closeAllGates() {
Stargate.log.info("Closing all stargates.");
for (Portal portal : allPortals) {
if (portal != null) {
portal.close(true);
}
}
}
/**
* Removes the special characters |, : and # from a portal name
*
* @param input <p>The name to filter</p>
* @return <p>The filtered name</p>
*/
public static String filterName(String input) {
if (input == null) {
return "";
}
return input.replaceAll("[|:#]", "").trim();
}
}

View File

@ -0,0 +1,93 @@
package net.knarcraft.stargate.portal;
public enum PortalOption {
/**
* This option allows a portal to be hidden from others
*/
HIDDEN('h', "stargate.option.hidden", 11),
/**
* This option allows a portal that's always on and does not need to be activated or opened each time
*/
ALWAYS_ON('a', "stargate.option.alwayson", 12),
/**
* This option allows a portal that's private to the stargate's owner
*/
PRIVATE('p', "stargate.option.private", 13),
/**
* This option allows a portal that's free even if stargates usually are not
*/
FREE('f', "stargate.option.free", 15),
/**
* This option allows a portal where players exit through the back of the portal
*/
BACKWARDS('b', "stargate.option.backwards", 16),
/**
* This option shows the gate in the network list even if it's always on
*/
SHOW('s', "stargate.option.show", 17),
/**
* This option hides the network name on the sign
*/
NO_NETWORK('n', "stargate.option.nonetwork", 18),
/**
* This option allows a portal where players teleport to a random exit portal in the network
*/
RANDOM('r', "stargate.option.random", 19),
/**
* This option allows a portal to teleport to another server connected through BungeeCord
*/
BUNGEE('u', "stargate.admin.bungee", 20);
private final char characterRepresentation;
private final String permissionString;
private final int saveIndex;
/**
* Instantiates a new portal options
*
* @param characterRepresentation <p>The character representation used on the sign to allow this option</p>
* @param permissionString <p>The permission necessary to use this option</p>
*/
PortalOption(final char characterRepresentation, String permissionString, int saveIndex) {
this.characterRepresentation = characterRepresentation;
this.permissionString = permissionString;
this.saveIndex = saveIndex;
}
/**
* Gets the character representation used to enable this setting on the sign
*
* @return <p>The character representation of this option</p>
*/
public char getCharacterRepresentation() {
return this.characterRepresentation;
}
/**
* Gets the permission necessary to use this option
*
* @return <p>The permission necessary for this option</p>
*/
public String getPermissionString() {
return this.permissionString;
}
/**
* Gets the index of the save file this option is stored at
*
* @return <p>This option's save index</p>
*/
public int getSaveIndex() {
return this.saveIndex;
}
}