Adds some sub-packages to the portal package to improve logical structure
Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit

This commit is contained in:
2021-11-04 00:07:03 +01:00
parent e5c1ad1f3a
commit e0ac9b41e7
28 changed files with 55 additions and 28 deletions

View File

@@ -0,0 +1,145 @@
package net.knarcraft.stargate.portal.property;
import net.knarcraft.stargate.container.BlockLocation;
import net.knarcraft.stargate.container.RelativeBlockVector;
import org.bukkit.Axis;
import org.bukkit.World;
import org.bukkit.block.BlockFace;
/**
* Keeps track of location related data for a portal
*/
@SuppressWarnings("UnusedReturnValue")
public class PortalLocation {
private BlockLocation topLeft;
private float yaw;
private BlockLocation signLocation;
private RelativeBlockVector buttonVector;
private BlockFace buttonFacing;
/**
* Gets the top-left block of the portal
*
* @return <p>The top-left block of the portal</p>
*/
public BlockLocation getTopLeft() {
return topLeft;
}
/**
* Gets the yaw for looking outwards from the portal
*
* @return <p>The portal's yaw</p>
*/
public float getYaw() {
return yaw;
}
/**
* Gets the location of the portal's sign
*
* @return <p>The location of the portal's sign</p>
*/
public BlockLocation getSignLocation() {
return signLocation;
}
/**
* The relative block vector pointing to the portal's button
*
* @return <p>The relative location of the portal's button</p>
*/
public RelativeBlockVector getButtonVector() {
return buttonVector;
}
/**
* Gets the block face determining the button's direction
*
* @return <p>The button's block face</p>
*/
public BlockFace getButtonFacing() {
return buttonFacing;
}
/**
* Gets the rotation axis, which is the axis along which the gate is placed
* <p>The portal's rotation axis is the cross axis of the button's axis</p>
*
* @return <p>The portal's rotation axis</p>
*/
public Axis getRotationAxis() {
return getYaw() == 0.0F || getYaw() == 180.0F ? Axis.X : Axis.Z;
}
/**
* Gets the world this portal resides in
*
* @return <p>The world this portal resides in</p>
*/
public World getWorld() {
return topLeft.getWorld();
}
/**
* Sets the portal's top-left location
*
* <p>Assuming the portal is a square, the top-left block is the top-left block when looking at the portal at the
* side with the portal's sign.</p>
*
* @param topLeft <p>The new top-left block of the portal's square structure</p>
* @return <p>The portal location Object</p>
*/
public PortalLocation setTopLeft(BlockLocation topLeft) {
this.topLeft = topLeft;
return this;
}
/**
* Sets the portal's yaw
*
* <p>The portal's yaw is the yaw a player would get when looking directly out from the portal</p>
*
* @param yaw <p>The portal's new yaw</p>
* @return <p>The portal location Object</p>
*/
public PortalLocation setYaw(float yaw) {
this.yaw = yaw;
return this;
}
/**
* Sets the location of the portal's sign
*
* @param signLocation <p>The new sign location</p>
* @return <p>The portal location Object</p>
*/
public PortalLocation setSignLocation(BlockLocation signLocation) {
this.signLocation = signLocation;
return this;
}
/**
* Sets the relative location of the portal's button
*
* @param buttonVector <p>The new relative button location</p>
* @return <p>The portal location Object</p>
*/
public PortalLocation setButtonVector(RelativeBlockVector buttonVector) {
this.buttonVector = buttonVector;
return this;
}
/**
* Sets the block face for the direction the portal button is facing
*
* @param buttonFacing <p>The new block face of the portal's button</p>
* @return <p>The portal location Object</p>
*/
public PortalLocation setButtonFacing(BlockFace buttonFacing) {
this.buttonFacing = buttonFacing;
return this;
}
}

View File

@@ -0,0 +1,96 @@
package net.knarcraft.stargate.portal.property;
/**
* Each enum value represents one option a portal can have/use
*/
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;
}
}

View File

@@ -0,0 +1,168 @@
package net.knarcraft.stargate.portal.property;
import net.knarcraft.stargate.Stargate;
import java.util.Map;
/**
* Keeps track of all options for one portal
*/
public class PortalOptions {
private final Map<PortalOption, Boolean> options;
private boolean isFixed;
/**
* Instantiates a new portal options object
*
* @param options <p>All options to keep track of</p>
* @param hasDestination <p>Whether the portal has a fixed destination</p>
*/
public PortalOptions(Map<PortalOption, Boolean> options, boolean hasDestination) {
this.options = options;
isFixed = hasDestination || this.isRandom() || this.isBungee();
if (this.isAlwaysOn() && !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");
}
}
/**
* Gets whether this portal is fixed
*
* <p>A fixed portal is a portal for which the player cannot choose destination. A portal with a set destination, a
* random portal and bungee portals are fixed. While the player has no choice regarding destinations, a fixed gate
* may still need to be activated if not set to always on.</p>
*
* @return <p>Whether this portal is fixed</p>
*/
public boolean isFixed() {
return this.isFixed;
}
/**
* Sets whether this portal is fixed
*
* @param fixed <p>Whether this gate should be fixed</p>
*/
public void setFixed(boolean fixed) {
this.isFixed = fixed;
}
/**
* Gets whether this portal is always on
*
* <p>An always on portal is always open for everyone, and always uses the open-block. It never needs to be
* activated or opened manually.</p>
*
* @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
*
* <p>A hidden portal will be hidden on a network for everyone but admins and the portal owner. In other words,
* when selecting a destination using a portal's sign, hidden gates will only be available in the list for the
* owner and players with the appropriate permission.</p>
*
* @return <p>Whether this portal is hidden</p>
*/
public boolean isHidden() {
return this.options.get(PortalOption.HIDDEN);
}
/**
* Gets whether this portal is private
*
* <p>A private portal can only be opened by the owner and players with the appropriate permission. A private gate
* is not hidden unless the hidden option is also enabled.</p>
*
* @return <p>Whether this portal is private</p>
*/
public boolean isPrivate() {
return this.options.get(PortalOption.PRIVATE);
}
/**
* Gets whether this portal is free
*
* <p>A free portal is exempt from any fees which would normally occur from using the portal. It does nothing if
* economy is disabled.</p>
*
* @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. It's important to note that the exit is
* mirrored, not rotated, when exiting backwards.</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
*
* <p>Normally, always-on portals are not selectable on a network, but enabling this option allows the portal to be
* shown.</p>
*
* @return <p>Whether portal gate is shown</p>
*/
public boolean isShown() {
return this.options.get(PortalOption.SHOW);
}
/**
* Gets whether this portal shows no network
*
* <p>Enabling the no network option allows the portal's network to be hidden for whatever reason. If allowing
* normal players to create portals, this can be used to prevent random users from connecting gates to
* "protected networks".</p>
*
* @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
*
* <p>A random portal is always on and will teleport to a random destination within the same network.</p>
*
* @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
*
* <p>A bungee portal is able to teleport to a portal on another server. It works differently from other portals as
* it does not have a network, but instead the network line specifies the same of the server it connects to.</p>
*
* @return <p>Whether this portal is a bungee portal</p>
*/
public boolean isBungee() {
return this.options.get(PortalOption.BUNGEE);
}
}

View File

@@ -0,0 +1,118 @@
package net.knarcraft.stargate.portal.property;
import net.knarcraft.stargate.Stargate;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import java.util.UUID;
/**
* The portal owner represents the owner of a portal
*/
public class PortalOwner {
private UUID ownerUUID;
private String ownerName;
/**
* Instantiates a new portal owner
*
* @param ownerIdentifier <p>A UUID, or a username for legacy support</p>
*/
public PortalOwner(String ownerIdentifier) {
parseIdentifier(ownerIdentifier);
}
/**
* Instantiates a new portal owner
*
* @param player <p>The player which is the owner of the portal</p>
*/
public PortalOwner(Player player) {
this.ownerUUID = player.getUniqueId();
this.ownerName = player.getName();
}
/**
* Gets the UUID of this owner
*
* @return <p>The UUID of this owner, or null if a UUID is not available</p>
*/
public UUID getUUID() {
return ownerUUID;
}
/**
* Sets the unique id for a portal owner without one
*
* <p>This method is only meant to be used to set the unique id for an owner without one. If the owner already has
* an unique id, an exception will be thrown.</p>
*
* @param uniqueId <p>The new unique id for the portal owner</p>
*/
public void setUUID(UUID uniqueId) {
if (ownerUUID == null) {
ownerUUID = uniqueId;
} else {
throw new IllegalArgumentException("An existing UUID cannot be overwritten.");
}
}
/**
* Gets the name of this owner
*
* @return <p>The name of this owner</p>
*/
public String getName() {
return ownerName;
}
/**
* Gets the one identifier used for saving the owner
*
* <p>If the UUID is available, a string representation of the UUID will be returned. If not, the owner's name will
* be returned.</p>
*
* @return <p>The owner's identifier</p>
*/
public String getIdentifier() {
if (ownerUUID != null) {
return ownerUUID.toString();
} else {
return ownerName;
}
}
/**
* Parses the identifier of a portal's owner
*
* <p>The identifier should be a valid UUID, but can be a username of max 16 characters for legacy support. Strings
* longer than 16 characters not parse-able as a UUID will silently fail by setting the owner name to the
* identifier.</p>
*
* @param ownerIdentifier <p>The identifier for a portal's owner</p>
*/
private void parseIdentifier(String ownerIdentifier) {
UUID ownerUUID = null;
String ownerName;
if (ownerIdentifier.length() > 16) {
//If more than 16 characters, the string cannot be a username, so it's probably a UUID
try {
ownerUUID = UUID.fromString(ownerIdentifier);
OfflinePlayer offlineOwner = Bukkit.getServer().getOfflinePlayer(ownerUUID);
ownerName = offlineOwner.getName();
} catch (IllegalArgumentException ex) {
//Invalid as UUID and username, so just keep it as owner name and hope the server owner fixes it
ownerName = ownerIdentifier;
Stargate.debug("loadAllPortals", "Invalid stargate owner string: " + ownerIdentifier);
}
} else {
//Old username from the pre-UUID times. Just keep it as the owner name
ownerName = ownerIdentifier;
}
this.ownerName = ownerName;
this.ownerUUID = ownerUUID;
}
}

View File

@@ -0,0 +1,150 @@
package net.knarcraft.stargate.portal.property;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.BlockLocation;
import net.knarcraft.stargate.container.RelativeBlockVector;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.property.gate.Gate;
/**
* The portal structure is responsible for the physical properties of a portal
*
* <p>The portal structure knows which gate type is used, where the real locations of buttons, frames and entrances are
* and whether the portal is verified.</p>
*/
public class PortalStructure {
private final Portal portal;
private final Gate gate;
private BlockLocation button;
private BlockLocation[] frame;
private BlockLocation[] entrances;
private boolean verified;
/**
* Instantiates a new portal structure
*
* @param portal <p>The portal whose structure to store</p>
* @param gate <p>The gate type used by this portal structure</p>
* @param button <p>The real location of the portal's button</p>
*/
public PortalStructure(Portal portal, Gate gate, BlockLocation button) {
this.portal = portal;
this.gate = gate;
this.verified = false;
this.button = button;
}
/**
* Gets the gate used by this portal structure
*
* @return <p>The gate used by this portal structure</p>
*/
public Gate getGate() {
return gate;
}
/**
* Gets the location of this portal's button
*
* @return <p>The location of this portal's button</p>
*/
public BlockLocation getButton() {
return button;
}
/**
* Sets the location of this portal's button
*
* @param button <p>The location of this portal's button</p>
*/
public void setButton(BlockLocation button) {
this.button = button;
}
/**
* 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() {
boolean verified = true;
if (!Stargate.getGateConfig().verifyPortals()) {
return true;
}
for (RelativeBlockVector control : gate.getLayout().getControls()) {
verified = verified && portal.getBlockAt(control).getBlock().getType().equals(gate.getControlBlock());
}
this.verified = verified;
return verified;
}
/**
* Gets the result of the last portal verification
*
* @return <p>True if this portal was verified</p>
*/
public boolean wasVerified() {
if (!Stargate.getGateConfig().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.getGateConfig().verifyPortals()) {
return gate.matches(portal.getTopLeft(), portal.getYaw());
} else {
return true;
}
}
/**
* Gets a list of block locations from a list of relative block vectors
*
* <p>The block locations will be calculated by using this portal's top-left block as the origin for the relative
* vectors..</p>
*
* @param vectors <p>The relative block vectors to convert</p>
* @return <p>A list of block locations</p>
*/
private BlockLocation[] relativeBlockVectorsToBlockLocations(RelativeBlockVector[] vectors) {
BlockLocation[] locations = new BlockLocation[vectors.length];
for (int i = 0; i < vectors.length; i++) {
locations[i] = portal.getBlockAt(vectors[i]);
}
return locations;
}
/**
* Gets the locations of this portal's entrances
*
* @return <p>The locations of this portal's entrances</p>
*/
public BlockLocation[] getEntrances() {
if (entrances == null) {
//Get the locations of the entrances once, and only if necessary as it's an expensive operation
entrances = relativeBlockVectorsToBlockLocations(gate.getLayout().getEntrances());
}
return entrances;
}
/**
* Gets the locations of this portal's frame
*
* @return <p>The locations of this portal's frame</p>
*/
public BlockLocation[] getFrame() {
if (frame == null) {
//Get the locations of the frame blocks once, and only if necessary as it's an expensive operation
frame = relativeBlockVectorsToBlockLocations(gate.getLayout().getBorder());
}
return frame;
}
}

View File

@@ -0,0 +1,347 @@
package net.knarcraft.stargate.portal.property.gate;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.BlockLocation;
import net.knarcraft.stargate.container.RelativeBlockVector;
import org.bukkit.Material;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* 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 Map<Character, Material> characterMaterialMap;
//Gate materials
private final Material portalOpenBlock;
private final Material portalClosedBlock;
private final Material portalButton;
//Economy information
private final int useCost;
private final int createCost;
private final int destroyCost;
private final boolean toOwner;
/**
* Instantiates a new gate
*
* @param filename <p>The name of the gate file, including extension</p>
* @param layout <p>The gate layout defined in the gate file</p>
* @param characterMaterialMap <p>The material types the different layout characters represent</p>
* @param portalOpenBlock <p>The material to set the opening to when the portal is open</p>
* @param portalClosedBlock <p>The material to set the opening 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, Map<Character, Material> characterMaterialMap, Material portalOpenBlock,
Material portalClosedBlock, Material portalButton, int useCost, int createCost, int destroyCost,
boolean toOwner) {
this.filename = filename;
this.layout = layout;
this.characterMaterialMap = characterMaterialMap;
this.portalOpenBlock = portalOpenBlock;
this.portalClosedBlock = portalClosedBlock;
this.portalButton = portalButton;
this.useCost = useCost;
this.createCost = createCost;
this.destroyCost = destroyCost;
this.toOwner = toOwner;
}
/**
* Gets this gate's layout
*
* @return <p>This gate's layout</p>
*/
public GateLayout getLayout() {
return layout;
}
/**
* Gets the material type used for this gate's control blocks
*
* @return <p>The material type used for control blocks</p>
*/
public Material getControlBlock() {
return characterMaterialMap.get(GateHandler.getControlBlockCharacter());
}
/**
* Gets the filename of this gate's file
*
* @return <p>The filename of this gate's file</p>
*/
public String getFilename() {
return filename;
}
/**
* Gets the block type to use for the opening when a portal using this gate is open
*
* @return <p>The block type to use for the opening when open</p>
*/
public Material getPortalOpenBlock() {
return portalOpenBlock;
}
/**
* Gets the block type to use for the opening when a portal using this gate is closed
*
* @return <p>The block type to use for the opening when closed</p>
*/
public Material getPortalClosedBlock() {
return portalClosedBlock;
}
/**
* Gets the material to use for a portal's button if using this gate type
*
* @return <p>The material to use for a portal's button if using this gate type</p>
*/
public Material getPortalButton() {
return portalButton;
}
/**
* Gets the cost of using a portal with this gate
*
* @return <p>The cost of using a portal with this gate</p>
*/
public int getUseCost() {
return useCost < 0 ? Stargate.getEconomyConfig().getDefaultUseCost() : useCost;
}
/**
* Gets the cost of creating a portal with this gate
*
* @return <p>The cost of creating a portal with this gate</p>
*/
public Integer getCreateCost() {
return createCost < 0 ? Stargate.getEconomyConfig().getDefaultCreateCost() : createCost;
}
/**
* Gets the cost of destroying a portal with this gate
*
* @return <p>The cost of destroying a portal with this gate</p>
*/
public Integer getDestroyCost() {
return destroyCost < 0 ? Stargate.getEconomyConfig().getDefaultDestroyCost() : destroyCost;
}
/**
* Gets whether portal payments go to this portal's owner
*
* @return <p>Whether portal payments go to the owner</p>
*/
public Boolean getToOwner() {
return toOwner;
}
/**
* Checks whether a portal's gate matches this gate type
*
* @param topLeft <p>The top-left block of the portal's gate</p>
* @param yaw <p>The yaw when looking directly outwards</p>
* @return <p>True if this gate matches the portal</p>
*/
public boolean matches(BlockLocation topLeft, double yaw) {
return matches(topLeft, yaw, false);
}
/**
* Checks whether a portal's gate matches this gate type
*
* <p>If enabling onCreate, opening blocks with materials AIR and WATER will be allowed even if the gate closed
* material is a different one. If checking and onCreate is not enabled, any inconsistency with opening blocks
* containing AIR or WATER will cause the gate to not match.</p>
*
* @param topLeft <p>The top-left block of the portal's gate</p>
* @param yaw <p>The yaw when looking directly outwards</p>
* @param onCreate <p>Whether this is used in the context of creating a new gate</p>
* @return <p>True if this gate matches the portal</p>
*/
public boolean matches(BlockLocation topLeft, double yaw, boolean onCreate) {
return verifyGateEntrancesMatch(topLeft, yaw, onCreate) && verifyGateBorderMatches(topLeft, yaw);
}
/**
* Verifies that all border blocks of a portal matches this gate type
*
* @param topLeft <p>The top-left block of the portal</p>
* @param yaw <p>The yaw when looking directly outwards from the portal</p>
* @return <p>True if all border blocks of the gate match the layout</p>
*/
private boolean verifyGateBorderMatches(BlockLocation topLeft, double yaw) {
Map<Character, Material> characterMaterialMap = new HashMap<>(this.characterMaterialMap);
for (RelativeBlockVector borderVector : layout.getBorder()) {
int rowIndex = borderVector.getRight();
int lineIndex = borderVector.getDown();
Character key = layout.getLayout()[lineIndex][rowIndex];
Material materialInLayout = characterMaterialMap.get(key);
Material materialAtLocation = topLeft.getRelativeLocation(borderVector, yaw).getType();
if (materialInLayout == null) {
/* This generally should not happen with proper checking, but just in case a material character is not
* recognized, but still allowed in previous checks, verify the gate as long as all such instances of
* the character correspond to the same material in the physical gate. All subsequent gates will also
* need to match the first verified gate. */
characterMaterialMap.put(key, materialAtLocation);
Stargate.debug("Gate::Matches", String.format("Missing layout material in %s. Using %s from the" +
" physical portal.", getFilename(), materialAtLocation));
} else if (materialAtLocation != materialInLayout) {
Stargate.debug("Gate::Matches", String.format("Block Type Mismatch: %s != %s",
materialAtLocation, materialInLayout));
return false;
}
}
return true;
}
/**
* Verifies that all entrances of a portal gate matches this gate type
*
* @param topLeft <p>The top-left block of this portal</p>
* @param yaw <p>The yaw when looking directly outwards</p>
* @param onCreate <p>Whether this is used in the context of creating a new gate</p>
* @return <p>Whether this is used in the context of creating a new gate</p>
*/
private boolean verifyGateEntrancesMatch(BlockLocation topLeft, double yaw, boolean onCreate) {
Stargate.debug("verifyGateEntrancesMatch", String.valueOf(topLeft));
for (RelativeBlockVector entranceVector : layout.getEntrances()) {
Stargate.debug("verifyGateEntrancesMatch", String.valueOf(entranceVector));
Material type = topLeft.getRelativeLocation(entranceVector, yaw).getType();
//Ignore entrance if it's air or water, and we're creating a new gate
if (onCreate && (type == Material.AIR || type == Material.WATER)) {
continue;
}
if (type != portalClosedBlock && type != portalOpenBlock) {
Stargate.debug("Gate::Matches", "Entrance/Exit Material Mismatch: " + type);
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));
//Save main material names
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 material types to use for frame blocks
saveFrameBlockTypes(bufferedWriter);
bufferedWriter.newLine();
//Save the gate layout
layout.saveLayout(bufferedWriter);
bufferedWriter.close();
} catch (IOException ex) {
Stargate.logSevere(String.format("Could not save Gate %s - %s", 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 {
//Write use cost if not disabled
if (useCost != -1) {
writeConfig(bufferedWriter, "usecost", useCost);
}
//Write create cost if not disabled
if (createCost != -1) {
writeConfig(bufferedWriter, "createcost", createCost);
}
//Write destroy cost if not disabled
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 : characterMaterialMap.entrySet()) {
Character type = entry.getKey();
Material value = entry.getValue();
//Skip characters not part of the frame
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 a formatted string to a buffered writer
*
* @param bufferedWriter <p>The buffered writer to write the formatted string to</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 key, Object value) throws IOException {
//Figure out the correct formatting to use
String format = "%s=";
if (value instanceof Boolean) {
format += "%b";
} else if (value instanceof Integer) {
format += "%d";
} else if (value instanceof String) {
format += "%s";
} else {
throw new IllegalArgumentException("Unrecognized config value type");
}
bufferedWriter.append(String.format(format, key, value));
bufferedWriter.newLine();
}
}

View File

@@ -0,0 +1,327 @@
package net.knarcraft.stargate.portal.property.gate;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.utility.GateReader;
import net.knarcraft.stargate.utility.MaterialHelper;
import org.bukkit.Material;
import org.bukkit.block.Block;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import static net.knarcraft.stargate.utility.GateReader.generateLayoutMatrix;
import static net.knarcraft.stargate.utility.GateReader.readGateConfig;
import static net.knarcraft.stargate.utility.GateReader.readGateFile;
/**
* The gate handler keeps track of all gates
*/
public class GateHandler {
private static final Character ANYTHING = ' ';
private static final Character ENTRANCE = '.';
private static final Character EXIT = '*';
private static final Character CONTROL_BLOCK = '-';
private static final Material defaultPortalBlockOpen = Material.NETHER_PORTAL;
private static final Material defaultPortalBlockClosed = Material.AIR;
private static final Material defaultButton = Material.STONE_BUTTON;
private static final HashMap<String, Gate> gates = new HashMap<>();
private static final HashMap<Material, List<Gate>> controlBlocks = new HashMap<>();
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;
}
/**
* Gets the character used for defining the exit
*
* @return <p>The character used for defining the exit</p>
*/
public static Character getExitCharacter() {
return EXIT;
}
/**
* Gets the character used for defining control blocks
*
* @return <p>The character used for defining control blocks</p>
*/
public static Character getControlBlockCharacter() {
return CONTROL_BLOCK;
}
/**
* Register a gate into the list of available gates
*
* @param gate <p>The gate to register</p>
*/
private static void registerGate(Gate gate) {
gates.put(gate.getFilename(), gate);
Material blockID = gate.getControlBlock();
if (!controlBlocks.containsKey(blockID)) {
controlBlocks.put(blockID, new ArrayList<>());
}
controlBlocks.get(blockID).add(gate);
}
/**
* Loads a gate from a file
*
* @param file <p>The file containing the gate data</p>
* @return <p>The loaded gate, or null if unable to load the gate</p>
*/
private static Gate loadGate(File file) {
try (Scanner scanner = new Scanner(file)) {
return loadGate(file.getName(), file.getParent(), scanner);
} catch (Exception exception) {
Stargate.logSevere(String.format("Could not load Gate %s - %s", file.getName(), exception.getMessage()));
return null;
}
}
/**
* Loads a gate from a file
*
* @param fileName <p>The name of the file containing the gate data</p>
* @param parentFolder <p>The parent folder of the gate data file</p>
* @param scanner <p>The scanner to use for reading the gate data</p>
* @return <p>The loaded gate or null if unable to load the gate</p>
*/
private static Gate loadGate(String fileName, String parentFolder, Scanner scanner) {
List<List<Character>> design = new ArrayList<>();
Map<Character, Material> characterMaterialMap = new HashMap<>();
Map<String, String> config = new HashMap<>();
Set<Material> frameTypes = new HashSet<>();
//Initialize character to material map
characterMaterialMap.put(ENTRANCE, Material.AIR);
characterMaterialMap.put(EXIT, Material.AIR);
characterMaterialMap.put(ANYTHING, Material.AIR);
//Read the file into appropriate lists and maps
int columns = readGateFile(scanner, characterMaterialMap, fileName, design, frameTypes, config);
if (columns < 0) {
return null;
}
Character[][] layout = generateLayoutMatrix(design, columns);
//Create and validate the new gate
Gate gate = createGate(config, fileName, layout, characterMaterialMap);
if (gate == null) {
return null;
}
//Update gate file in case the format has changed between versions
gate.save(parentFolder + "/");
return gate;
}
/**
* Creates a new gate
*
* @param config <p>The config map to get configuration values from</p>
* @param fileName <p>The name of the saved gate config file</p>
* @param layout <p>The layout matrix of the new gate</p>
* @param characterMaterialMap <p>A map between layout characters and the material to use</p>
* @return <p>A new gate, or null if the config is invalid</p>
*/
private static Gate createGate(Map<String, String> config, String fileName, Character[][] layout,
Map<Character, Material> characterMaterialMap) {
//Read relevant material types
Material portalOpenBlock = readGateConfig(config, fileName, "portal-open", defaultPortalBlockOpen);
Material portalClosedBlock = readGateConfig(config, fileName, "portal-closed", defaultPortalBlockClosed);
Material portalButton = readGateConfig(config, fileName, "button", defaultButton);
//Read economy values
int useCost = GateReader.readGateConfig(config, fileName, "usecost");
int createCost = GateReader.readGateConfig(config, fileName, "createcost");
int destroyCost = GateReader.readGateConfig(config, fileName, "destroycost");
boolean toOwner = (config.containsKey("toowner") ? Boolean.parseBoolean(config.get("toowner")) :
Stargate.getEconomyConfig().sendPaymentToOwner());
//Create the new gate
Gate gate = new Gate(fileName, new GateLayout(layout), characterMaterialMap, portalOpenBlock, portalClosedBlock,
portalButton, useCost, createCost, destroyCost, toOwner);
if (!validateGate(gate, fileName)) {
return null;
}
return gate;
}
/**
* Validates that a gate is valid
*
* @param gate <p>The gate to validate</p>
* @param fileName <p>The filename of the loaded gate file</p>
* @return <p>True if the gate is valid. False otherwise</p>
*/
private static boolean validateGate(Gate gate, String fileName) {
if (gate.getLayout().getControls().length != 2) {
Stargate.logSevere(String.format("Could not load Gate %s - Gates must have exactly 2 control points.",
fileName));
return false;
}
if (!MaterialHelper.isButtonCompatible(gate.getPortalButton())) {
Stargate.logSevere(String.format("Could not load Gate %s - Gate button must be a type of button.", fileName));
return false;
}
return true;
}
/**
* 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()) {
//Get all files with a .gate extension
files = directory.listFiles((file) -> file.isFile() && file.getName().endsWith(".gate"));
} else {
//Set files to empty list to signal that default gates need to be copied
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()) {
writeDefaultGatesToFolder(gateFolder);
}
} else {
//Load and register the corresponding gate for each file
for (File file : files) {
Gate gate = loadGate(file);
if (gate != null) {
registerGate(gate);
}
}
}
}
/**
* Writes the default gates to the given folder
*
* @param gateFolder <p>The folder containing gate config files</p>
*/
public static void writeDefaultGatesToFolder(String gateFolder) {
loadGateFromJar("nethergate.gate", gateFolder);
loadGateFromJar("watergate.gate", gateFolder);
loadGateFromJar("endgate.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) {
//Get an input stream for the internal file
InputStream gateFileStream = Gate.class.getResourceAsStream("/gates/" + gateFile);
if (gateFileStream != null) {
Scanner scanner = new Scanner(gateFileStream);
//Load and register the gate
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
*
* <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 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 given its filename
*
* @param fileName <p>The filename of the gate to get</p>
* @return <p>The gate with the given filename</p>
*/
public static Gate getGateByName(String fileName) {
return gates.get(fileName);
}
/**
* Gets the number of loaded gate configurations
*
* @return <p>The number of loaded gate configurations</p>
*/
public static int getGateCount() {
return gates.size();
}
/**
* Clears all loaded gates and control blocks
*/
public static void clearGates() {
gates.clear();
controlBlocks.clear();
}
}

View File

@@ -0,0 +1,209 @@
package net.knarcraft.stargate.portal.property.gate;
import net.knarcraft.stargate.container.RelativeBlockVector;
import java.io.BufferedWriter;
import java.io.IOException;
import java.util.ArrayList;
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. All relative vectors has an origin in the top-left block when looking at the gate's front
* (the side with the sign). The origin of the relative vectors can also be seen as 0,0 in the character matrix.</p>
*/
public class GateLayout {
private final Character[][] layout;
private final List<RelativeBlockVector> exits = new ArrayList<>();
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 matrix 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 all entrances for this gate
*
* <p>Entrances contain both the portal entrance blocks and the portal exit blocks.</p>
*
* @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 all possible exit locations defined in the layout
*
* <p>This returns all blocks usable as exits. This basically means it returns the lowest block in each opening of
* the gate layout.</p>
*
* @return <p>All possible exits</p>
*/
public List<RelativeBlockVector> 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. The control block without a
* sign will be used for the button if necessary. There will always be exactly two control blocks.</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 saveLayout(BufferedWriter bufferedWriter) throws IOException {
for (Character[] line : this.layout) {
for (Character character : line) {
bufferedWriter.append(character);
}
bufferedWriter.newLine();
}
}
/**
* Reads the layout and stores key information
*
* <p>This methods reads the layout and stores exits, entrances, border blocks and control blocks.</p>
*/
private void readLayout() {
List<RelativeBlockVector> entranceList = new ArrayList<>();
List<RelativeBlockVector> borderList = new ArrayList<>();
List<RelativeBlockVector> controlList = new ArrayList<>();
readLayout(controlList, entranceList, borderList);
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>
*/
private void readLayout(List<RelativeBlockVector> controlList, List<RelativeBlockVector> entranceList,
List<RelativeBlockVector> borderList) {
//Store the lowest opening for each column
int[] exitDepths = new int[layout[0].length];
//A row is the same as one line in the gate file
int lineCount = layout.length;
for (int rowIndex = 0; rowIndex < lineCount; rowIndex++) {
Character[] row = layout[rowIndex];
int rowSize = row.length;
for (int columnIndex = 0; columnIndex < rowSize; columnIndex++) {
Character key = row[columnIndex];
parseLayoutCharacter(key, columnIndex, rowIndex, exitDepths, controlList, entranceList, borderList);
}
}
//Generate all possible exits
for (int x = 0; x < exitDepths.length; x++) {
//Ignore invalid exits
if (exitDepths[x] > 0) {
this.exits.add(new RelativeBlockVector(x, exitDepths[x], 0));
}
}
}
/**
* Parses one character of the layout
*
* @param key <p>The read character</p>
* @param columnIndex <p>The column containing the read character</p>
* @param rowIndex <p>The row containing 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 columnIndex, int rowIndex, int[] exitDepths,
List<RelativeBlockVector> controlList, List<RelativeBlockVector> entranceList,
List<RelativeBlockVector> borderList) {
//Add control blocks to the control block list
if (key.equals(GateHandler.getControlBlockCharacter())) {
controlList.add(new RelativeBlockVector(columnIndex, rowIndex, 0));
}
if (isOpening(key)) {
//Register entrance
entranceList.add(new RelativeBlockVector(columnIndex, rowIndex, 0));
//Overwrite the lowest exit location for this column/x-coordinate
exitDepths[columnIndex] = rowIndex;
//Register exit if found
if (key.equals(GateHandler.getExitCharacter())) {
this.exitBlock = new RelativeBlockVector(columnIndex, rowIndex, 0);
}
} else if (!key.equals(GateHandler.getAnythingCharacter())) {
//Register border block
borderList.add(new RelativeBlockVector(columnIndex, rowIndex, 0));
}
}
/**
* Checks whether the given character represents a gate opening
*
* @param character <p>The character to check</p>
* @return <p>True if the character represents an opening</p>
*/
private boolean isOpening(Character character) {
return character.equals(GateHandler.getEntranceCharacter()) || character.equals(GateHandler.getExitCharacter());
}
}