Some checks failed
EpicKnarvik97/Stargate/pipeline/head There was a failure building this commit
403 lines
17 KiB
Java
403 lines
17 KiB
Java
package net.knarcraft.stargate.utility;
|
|
|
|
import net.knarcraft.stargate.Stargate;
|
|
import net.knarcraft.stargate.container.BlockChangeRequest;
|
|
import net.knarcraft.stargate.container.ControlBlockUpdateRequest;
|
|
import net.knarcraft.stargate.portal.Portal;
|
|
import net.knarcraft.stargate.portal.PortalRegistry;
|
|
import net.knarcraft.stargate.portal.property.PortalLocation;
|
|
import net.knarcraft.stargate.portal.property.PortalOptions;
|
|
import net.knarcraft.stargate.portal.property.PortalOwner;
|
|
import net.knarcraft.stargate.portal.property.PortalStrings;
|
|
import net.knarcraft.stargate.portal.property.gate.Gate;
|
|
import net.knarcraft.stargate.portal.property.gate.GateHandler;
|
|
import net.knarcraft.stargate.transformation.SimpleVectorOperation;
|
|
import org.bukkit.Bukkit;
|
|
import org.bukkit.Location;
|
|
import org.bukkit.Material;
|
|
import org.bukkit.World;
|
|
import org.bukkit.block.Block;
|
|
import org.bukkit.block.BlockFace;
|
|
import org.bukkit.block.data.BlockData;
|
|
import org.bukkit.block.data.Directional;
|
|
import org.bukkit.block.data.Waterlogged;
|
|
import org.bukkit.util.BlockVector;
|
|
import org.bukkit.util.Vector;
|
|
import org.jetbrains.annotations.NotNull;
|
|
|
|
import java.io.BufferedWriter;
|
|
import java.io.File;
|
|
import java.io.FileWriter;
|
|
import java.io.IOException;
|
|
import java.util.List;
|
|
import java.util.Scanner;
|
|
|
|
import static net.knarcraft.stargate.portal.PortalSignDrawer.markPortalWithInvalidGate;
|
|
|
|
/**
|
|
* Helper class for saving and loading portal save files
|
|
*/
|
|
public final class PortalFileHelper {
|
|
|
|
private PortalFileHelper() {
|
|
|
|
}
|
|
|
|
/**
|
|
* Saves all portals for the given world
|
|
*
|
|
* @param world <p>The world to save portals for</p>
|
|
*/
|
|
public static void saveAllPortals(@NotNull World world) {
|
|
Stargate.getStargateConfig().addManagedWorld(world.getName());
|
|
String saveFileLocation = Stargate.getPortalFolder() + "/" + world.getName() + ".db";
|
|
|
|
try {
|
|
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(saveFileLocation, false));
|
|
|
|
for (Portal portal : PortalRegistry.getAllPortals()) {
|
|
//Skip portals in other worlds
|
|
String worldName = portal.getLocation().getWorld().getName();
|
|
if (!worldName.equalsIgnoreCase(world.getName())) {
|
|
continue;
|
|
}
|
|
//Save the portal
|
|
savePortal(bufferedWriter, portal);
|
|
}
|
|
|
|
bufferedWriter.close();
|
|
} catch (Exception exception) {
|
|
Stargate.logSevere(String.format("Exception while writing stargates to %s: %s", saveFileLocation, exception));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Saves one portal
|
|
*
|
|
* @param bufferedWriter <p>The buffered writer to write to</p>
|
|
* @param portal <p>The portal to save</p>
|
|
* @throws IOException <p>If unable to write to the buffered writer</p>
|
|
*/
|
|
private static void savePortal(@NotNull BufferedWriter bufferedWriter, @NotNull Portal portal) throws IOException {
|
|
StringBuilder builder = new StringBuilder();
|
|
Block button = portal.getStructure().getButton();
|
|
|
|
//WARNING: Because of the primitive save format, any change in order will break everything!
|
|
builder.append(portal.getName()).append(':');
|
|
builder.append(portal.getLocation().getSignBlock()).append(':');
|
|
builder.append((button != null) ? button.toString() : "").append(':');
|
|
|
|
//Add removes config values to keep indices consistent
|
|
builder.append(0).append(':');
|
|
builder.append(0).append(':');
|
|
|
|
builder.append(DirectionHelper.getYawFromBlockFace(portal.getLocation().getFacing())).append(':');
|
|
builder.append(portal.getLocation().getTopLeft()).append(':');
|
|
builder.append(portal.getGate().getFilename()).append(':');
|
|
|
|
//Only save the destination name if the gate is fixed as it doesn't matter otherwise
|
|
builder.append(portal.getOptions().isFixed() ? portal.getDestinationName() : "").append(':');
|
|
|
|
builder.append(portal.getNetwork()).append(':');
|
|
|
|
//Name is saved as a fallback if the UUID is unavailable
|
|
builder.append(portal.getOwner().getIdentifier());
|
|
|
|
//Save all the portal options
|
|
savePortalOptions(portal, builder);
|
|
|
|
bufferedWriter.append(builder.toString());
|
|
bufferedWriter.newLine();
|
|
}
|
|
|
|
/**
|
|
* Saves all portal options for the given portal
|
|
*
|
|
* @param portal <p>The portal to save</p>
|
|
* @param builder <p>The string builder to append to</p>
|
|
*/
|
|
private static void savePortalOptions(@NotNull Portal portal, @NotNull StringBuilder builder) {
|
|
PortalOptions options = portal.getOptions();
|
|
builder.append(':');
|
|
builder.append(options.isHidden()).append(':');
|
|
builder.append(options.isAlwaysOn()).append(':');
|
|
builder.append(options.isPrivate()).append(':');
|
|
builder.append(portal.getLocation().getWorld().getName()).append(':');
|
|
builder.append(options.isFree()).append(':');
|
|
builder.append(options.isBackwards()).append(':');
|
|
builder.append(options.isShown()).append(':');
|
|
builder.append(options.isNoNetwork()).append(':');
|
|
builder.append(options.isRandom()).append(':');
|
|
builder.append(options.isBungee()).append(':');
|
|
builder.append(options.isQuiet()).append(':');
|
|
builder.append(options.hasNoSign());
|
|
}
|
|
|
|
/**
|
|
* Loads all portals for the given world
|
|
*
|
|
* @param world <p>The world to load portals for</p>
|
|
* @return <p>True if portals could be loaded</p>
|
|
*/
|
|
public static boolean loadAllPortals(@NotNull World world) {
|
|
String location = Stargate.getPortalFolder();
|
|
|
|
File database = new File(location, world.getName() + ".db");
|
|
|
|
if (database.exists()) {
|
|
return loadPortals(world, database);
|
|
} else {
|
|
Stargate.logInfo(String.format("{%s} No stargates for world ", world.getName()));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Loads all the given portals
|
|
*
|
|
* @param world <p>The world to load portals for</p>
|
|
* @param database <p>The database file containing the portals</p>
|
|
* @return <p>True if the portals were loaded successfully</p>
|
|
*/
|
|
private static boolean loadPortals(@NotNull World world, @NotNull File database) {
|
|
int lineIndex = 0;
|
|
try {
|
|
Scanner scanner = new Scanner(database);
|
|
boolean needsToSaveDatabase = false;
|
|
while (scanner.hasNextLine()) {
|
|
//Read the line and do whatever needs to be done
|
|
needsToSaveDatabase = readPortalLine(scanner, ++lineIndex, world) || needsToSaveDatabase;
|
|
}
|
|
scanner.close();
|
|
|
|
//Do necessary tasks after all portals have loaded
|
|
Stargate.debug("PortalFileHelper::loadPortals", String.format("Finished loading portals for %s. " +
|
|
"Starting post loading tasks", world));
|
|
doPostLoadTasks(world, needsToSaveDatabase);
|
|
return true;
|
|
} catch (Exception exception) {
|
|
Stargate.logSevere(String.format("Exception while reading stargates from %s: %d! Message: %s",
|
|
database.getName(), lineIndex, exception.getMessage()));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Reads one file line containing information about one portal
|
|
*
|
|
* @param scanner <p>The scanner to read</p>
|
|
* @param lineIndex <p>The index of the read line</p>
|
|
* @param world <p>The world for which portals are currently being read</p>
|
|
* @return <p>True if the read portal has changed and the world's database needs to be saved</p>
|
|
*/
|
|
private static boolean readPortalLine(@NotNull Scanner scanner, int lineIndex, @NotNull World world) {
|
|
String line = scanner.nextLine().trim();
|
|
|
|
//Ignore empty and comment lines
|
|
if (line.startsWith("#") || line.isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
//Check if the min. required portal data is present
|
|
String[] portalData = line.split(":");
|
|
if (portalData.length < 8) {
|
|
Stargate.logInfo(String.format("Invalid line - %s", lineIndex));
|
|
return false;
|
|
}
|
|
|
|
//Load the portal defined in the current line
|
|
return loadPortal(portalData, world, lineIndex);
|
|
}
|
|
|
|
/**
|
|
* Performs tasks which must be run after portals have loaded
|
|
*
|
|
* <p>This will open always on portals, print info about loaded stargates and re-draw portal signs for loaded
|
|
* portals.</p>
|
|
*
|
|
* @param world <p>The world portals have been loaded for</p>
|
|
* @param needsToSaveDatabase <p>Whether the portal database's file needs to be updated</p>
|
|
*/
|
|
private static void doPostLoadTasks(@NotNull World world, boolean needsToSaveDatabase) {
|
|
//Open any always-on portals. Do this here as it should be more efficient than in the loop.
|
|
PortalUtil.verifyAllPortals();
|
|
int portalCount = PortalRegistry.getAllPortals().size();
|
|
int openCount = PortalUtil.openAlwaysOpenPortals();
|
|
|
|
//Print info about loaded stargates so that admins can see if all stargates loaded
|
|
Stargate.logInfo(String.format("{%s} Loaded %d stargates with %d set as always-on", world.getName(),
|
|
portalCount, openCount));
|
|
|
|
|
|
//Re-draw the signs in case a bug in the config prevented the portal from loading and has been fixed since
|
|
Stargate.debug("PortalFileHelper::doPostLoadTasks::update",
|
|
String.format("Queueing portal sign/button updates for %s", world));
|
|
for (Portal portal : PortalRegistry.getAllPortals()) {
|
|
if (portal.isRegistered() && portal.getLocation().getWorld().equals(world) &&
|
|
world.getWorldBorder().isInside(portal.getLocation().getSignBlock().getLocation())) {
|
|
Stargate.addControlBlockUpdateRequest(new ControlBlockUpdateRequest(portal));
|
|
Stargate.debug("UpdateSignsButtons", String.format("Queued sign and button updates for portal %s",
|
|
portal.getName()));
|
|
}
|
|
}
|
|
//Save the portals to disk to update with any changes
|
|
Stargate.debug("PortalFileHelper::doPostLoadTasks", String.format("Saving database for world %s", world));
|
|
if (needsToSaveDatabase) {
|
|
saveAllPortals(world);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Loads one portal 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>
|
|
* @return <p>True if the portal's data has changed and its database needs to be updated</p>
|
|
*/
|
|
private static boolean loadPortal(@NotNull String[] portalData, @NotNull World world, int lineIndex) {
|
|
//Load min. required portal data
|
|
String name = portalData[0];
|
|
Block button = (!portalData[2].isEmpty()) ? getBlock(world, portalData[2]) : null;
|
|
|
|
//Load the portal's location
|
|
|
|
|
|
PortalLocation portalLocation = new PortalLocation(getBlock(world, portalData[6]),
|
|
DirectionHelper.getBlockFaceFromYaw(Float.parseFloat(portalData[5])), getBlock(world, portalData[1]), button);
|
|
|
|
//Check if the portal's gate type exists and is loaded
|
|
Gate gate = GateHandler.getGateByName(portalData[7]);
|
|
if (gate == null) {
|
|
//Mark the sign as invalid to reduce some player confusion
|
|
markPortalWithInvalidGate(portalLocation, portalData[7], lineIndex);
|
|
return false;
|
|
}
|
|
|
|
//Load extra portal data
|
|
String destination = (portalData.length > 8) ? portalData[8] : "";
|
|
String network = (portalData.length > 9 && !portalData[9].isEmpty()) ? portalData[9] :
|
|
Stargate.getDefaultNetwork();
|
|
String ownerString = (portalData.length > 10) ? portalData[10] : "";
|
|
|
|
//Get the owner from the owner string
|
|
PortalOwner owner = new PortalOwner(ownerString);
|
|
|
|
//Create the new portal
|
|
PortalStrings portalStrings = new PortalStrings(name, network, destination);
|
|
Portal portal = new Portal(portalLocation, button, portalStrings, gate, owner,
|
|
PortalUtil.getPortalOptions(portalData));
|
|
|
|
//Register the portal, and close it in case it wasn't properly closed when the server stopped
|
|
boolean buttonLocationChanged = updateButtonVector(portal);
|
|
PortalUtil.registerPortal(portal);
|
|
portal.getPortalOpener().closePortal(true);
|
|
return buttonLocationChanged;
|
|
}
|
|
|
|
/**
|
|
* Decides the material to use for removing a portal's button/sign
|
|
*
|
|
* @param block <p>The location of the button/sign to replace</p>
|
|
* @param portal <p>The portal the button/sign belongs to</p>
|
|
* @return <p>The material to use for removing the button/sign</p>
|
|
*/
|
|
@NotNull
|
|
public static Material decideRemovalMaterial(@NotNull Block block, @NotNull Portal portal) {
|
|
//Get the blocks to each side of the location
|
|
SimpleVectorOperation vectorOperation = portal.getLocation().getVectorOperation();
|
|
Location leftLocation = block.getLocation().clone().add(vectorOperation.performToRealSpaceOperation(new Vector(-1, 0, 0)));
|
|
Location rightLocation = block.getLocation().clone().add(vectorOperation.performToRealSpaceOperation(new Vector(1, 0, 0)));
|
|
|
|
//If the block is water or is waterlogged, assume the portal is underwater
|
|
if (isUnderwater(leftLocation) || isUnderwater(rightLocation)) {
|
|
return Material.WATER;
|
|
} else {
|
|
return Material.AIR;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks whether the given location is underwater
|
|
*
|
|
* <p>If the location has a water block, or a block which is waterlogged, it will be considered underwater.</p>
|
|
*
|
|
* @param location <p>The location to check</p>
|
|
* @return <p>True if the location is underwater</p>
|
|
*/
|
|
private static boolean isUnderwater(@NotNull Location location) {
|
|
BlockData blockData = location.getBlock().getBlockData();
|
|
return blockData.getMaterial() == Material.WATER ||
|
|
(blockData instanceof Waterlogged waterlogged && waterlogged.isWaterlogged());
|
|
}
|
|
|
|
/**
|
|
* Updates the button vector for the given portal
|
|
*
|
|
* <p>As the button vector isn't saved, it is null when the portal is loaded. This method allows it to be
|
|
* explicitly set when necessary.</p>
|
|
*
|
|
* @param portal <p>The portal to update the button vector for</p>
|
|
* @return <p>True if the calculated button location is not the same as the one in the portal file</p>
|
|
*/
|
|
private static boolean updateButtonVector(@NotNull Portal portal) {
|
|
for (BlockVector control : portal.getGate().getLayout().getControls()) {
|
|
Block buttonLocation = portal.getLocation().getRelative(control.clone().add(new Vector(0, 0, 1)).toBlockVector());
|
|
if (!buttonLocation.equals(portal.getLocation().getSignBlock())) {
|
|
portal.getLocation().setButtonBlock(buttonLocation);
|
|
|
|
Block oldButtonLocation = portal.getStructure().getButton();
|
|
if (oldButtonLocation != null && !oldButtonLocation.equals(buttonLocation)) {
|
|
Stargate.addControlBlockUpdateRequest(new BlockChangeRequest(oldButtonLocation, Material.AIR, null));
|
|
portal.getStructure().setButton(buttonLocation);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Generates a button for a portal
|
|
*
|
|
* @param portal <p>The portal to generate button for</p>
|
|
* @param buttonFacing <p>The direction the button should be facing</p>
|
|
*/
|
|
public static void generatePortalButton(@NotNull Portal portal, @NotNull BlockFace buttonFacing) {
|
|
//Go one block outwards to find the button's location rather than the control block's location
|
|
Block button = portal.getLocation().getButtonBlock();
|
|
|
|
// If the button location is null here, it is assumed that the button generation wasn't necessary
|
|
if (button == null) {
|
|
return;
|
|
}
|
|
|
|
if (!MaterialHelper.isButtonCompatible(button.getType())) {
|
|
@NotNull List<Material> possibleMaterials = MaterialHelper.specifiersToMaterials(
|
|
portal.getGate().getPortalButtonMaterials()).stream().toList();
|
|
Material buttonType = ListHelper.getRandom(possibleMaterials);
|
|
|
|
Directional buttonData = (Directional) Bukkit.createBlockData(buttonType);
|
|
buttonData.setFacing(buttonFacing);
|
|
button.setBlockData(buttonData);
|
|
}
|
|
portal.getStructure().setButton(button);
|
|
}
|
|
|
|
/**
|
|
* Gets the block specified in the input
|
|
*
|
|
* @param world <p>The world the block belongs to</p>
|
|
* @param string <p>Comma-separated coordinate string</p>
|
|
* @return <p>The specified block</p>
|
|
* @throws NumberFormatException <p>If non-numeric values are encountered</p>
|
|
*/
|
|
@NotNull
|
|
private static Block getBlock(@NotNull World world, @NotNull String string) throws NumberFormatException {
|
|
String[] parts = string.split(",");
|
|
return new Location(world, Integer.parseInt(parts[0]), Integer.parseInt(parts[1]),
|
|
Integer.parseInt(parts[2])).getBlock();
|
|
}
|
|
|
|
}
|