Merge remote-tracking branch 'knarvik/master' into temp-legacy

This commit is contained in:
Pheotis
2023-03-24 15:37:03 -04:00
129 changed files with 15149 additions and 5196 deletions

19
HEADER Normal file
View File

@@ -0,0 +1,19 @@
Stargate - A portal plugin for Bukkit
Copyright (C) 2011 Shaun (sturmeh)
Copyright (C) 2011 Dinnerbone
Copyright (C) 2011-2013 Steven "Drakia" Scott <Contact@TheDgtl.net>
Copyright (C) 2015-2020 Michael Smith (PseudoKnight)
Copyright (C) 2021-2022 Kristian Knarvik (EpicKnarvik97)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

33
Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,33 @@
pipeline {
agent any
tools {
jdk 'JDK17'
}
stages {
stage('Build') {
steps {
echo 'Building...'
sh 'mvn clean & mvn validate & mvn compile'
}
}
stage('Test') {
steps {
echo 'Testing...'
sh 'mvn test'
}
}
stage('Verify') {
steps {
echo 'Verifying...'
sh 'mvn verify -Dmaven.test.skip=true'
}
}
stage('Deploy') {
steps {
echo 'Deploying...'
sh 'mvn deploy -Dmaven.install.skip=true -Dmaven.test.skip=true'
archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true
}
}
}
}

1544
README.md

File diff suppressed because it is too large Load Diff

140
pom.xml
View File

@@ -1,12 +1,28 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.TheDgtl</groupId>
<groupId>net.knarcraft</groupId>
<artifactId>Stargate</artifactId>
<version>0.8.0.3</version>
<version>0.9.4.3-SNAPSHOT</version>
<licenses>
<license>
<name>GNU Lesser General Public License</name>
<url>https://www.gnu.org/licenses/lgpl-3.0.en.html</url>
</license>
</licenses>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>16</java.version>
</properties>
<repositories>
<repository>
<id>knarcraft-repo</id>
<url>https://git.knarcraft.net/api/packages/EpicKnarvik97/maven</url>
</repository>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
@@ -15,46 +31,124 @@
<id>vault-repo</id>
<url>https://nexus.hc.to/content/repositories/pub_releases</url>
</repository>
<repository>
<id>dynmap</id>
<url>https://repo.mikeprimm.com/</url>
</repository>
<repository>
<id>papermc</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
</repositories>
<distributionManagement>
<repository>
<id>knarcraft-repo</id>
<url>https://git.knarcraft.net/api/packages/EpicKnarvik97/maven</url>
</repository>
<snapshotRepository>
<id>knarcraft-repo</id>
<url>https://git.knarcraft.net/api/packages/EpicKnarvik97/maven</url>
</snapshotRepository>
</distributionManagement>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.16.2-R0.1-SNAPSHOT</version>
<version>1.19.3-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.milkbowl.vault</groupId>
<artifactId>VaultAPI</artifactId>
<version>1.7</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.seeseemelk</groupId>
<artifactId>MockBukkit-v1.18</artifactId>
<version>2.85.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>23.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>us.dynmap</groupId>
<artifactId>dynmap-api</artifactId>
<version>3.1-beta-2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.knarcraft</groupId>
<artifactId>knarlib</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src</sourceDirectory>
<resources>
<resource>
<targetPath>net/TheDgtl/Stargate/resources</targetPath>
<directory>src/net/TheDgtl/Stargate/resources</directory>
<includes>
<include>*.txt</include>
</includes>
</resource>
<resource>
<directory>src</directory>
<includes>
<include>*.yml</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<filters>
<filter>
<artifact>net.knarcraft:knarlib</artifact>
<includes>
<include>net/knarcraft/knarlib/**</include>
</includes>
</filter>
<filter>
<excludes>
<exclude>*.MF</exclude>
<exclude>*.yml</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
</project>

View File

@@ -1,54 +0,0 @@
# Stargate Configuration File
# Main Stargate config
#
# portal-folder - The folder for storing portals
# gate-folder - The folder for storing gate layouts
# default-gate-network - The default gate network
# destroyexplosion - Whether or not to destroy gates with explosions (Creeper, TNT, etc)
# maxgates - The maximum number of gates allowed on a network - 0 for unlimited
# lang - The language file to load for messages
# destMemory - Whether to remember the cursor location between uses
# ignoreEntrance - Ignore the entrance blocks of a gate when checking. Used to work around snowmen
# handleVehicles - Whether to allow vehicles through gates
# sortLists - Whether to sort network lists alphabetically
# protectEntrance - Whether to protect gate entrance material (More resource intensive. Only enable if using destroyable open/closed material)
# signColor - The color used for drawing signs (Default: BLACK).
# verifyPortals - Whether or not all the non-sign blocks are checked to match the gate layout when a stargate is loaded.
############################
# Stargate economy options #
############################
# useeconomy - Whether to use an economy plugin
# createcost - The cost to create a gate
# destroycost - The cost to destroy a gate
# usecost - The cost to use a gate
# toowner - Whether the charge for using a gate goes to the gates owner
# chargefreedestination - Whether a gate whose destination is a free gate is still charged
# freegatesgreen - Whether a free gate in the destination list is drawn green
#################
# Debug options #
#################
# debug - Debug -- Only enable if you have issues, massive console output
# permdebug - This will output any and all Permissions checks to console, used for permissions debugging (Requires debug: true)
portal-folder: plugins/Stargate/portals/
gate-folder: plugins/Stargate/gates/
default-gate-network: central
destroyexplosion: false
maxgates: 0
lang: en
destMemory: false
ignoreEntrance: false
handleVehicles: true
sortLists: false
protectEntrance: false
signColor: BLACK
useeconomy: false
createcost: 0
destroycost: 0
usecost: 0
toowner: false
chargefreedestination: true
freegatesgreen: false
debug: false
permdebug: false
enableBungee: false
verifyPortals: false

View File

@@ -0,0 +1,437 @@
package net.knarcraft.stargate;
import net.knarcraft.knarlib.util.UpdateChecker;
import net.knarcraft.stargate.command.CommandStarGate;
import net.knarcraft.stargate.command.StarGateTabCompleter;
import net.knarcraft.stargate.config.EconomyConfig;
import net.knarcraft.stargate.config.MessageSender;
import net.knarcraft.stargate.config.StargateConfig;
import net.knarcraft.stargate.config.StargateGateConfig;
import net.knarcraft.stargate.container.BlockChangeRequest;
import net.knarcraft.stargate.container.ChunkUnloadRequest;
import net.knarcraft.stargate.listener.BlockEventListener;
import net.knarcraft.stargate.listener.EntityEventListener;
import net.knarcraft.stargate.listener.EntitySpawnListener;
import net.knarcraft.stargate.listener.PlayerEventListener;
import net.knarcraft.stargate.listener.PluginEventListener;
import net.knarcraft.stargate.listener.PortalEventListener;
import net.knarcraft.stargate.listener.TeleportEventListener;
import net.knarcraft.stargate.listener.VehicleEventListener;
import net.knarcraft.stargate.listener.WorldEventListener;
import net.knarcraft.stargate.portal.PortalHandler;
import net.knarcraft.stargate.portal.PortalRegistry;
import net.knarcraft.stargate.thread.BlockChangeThread;
import net.knarcraft.stargate.thread.ChunkUnloadThread;
import net.knarcraft.stargate.thread.StarGateThread;
import org.bukkit.Server;
import org.bukkit.command.PluginCommand;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.java.JavaPluginLoader;
import org.bukkit.scheduler.BukkitScheduler;
import java.io.File;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.logging.Level;
import java.util.logging.Logger;
/*
Stargate - A portal plugin for Bukkit
Copyright (C) 2011 Shaun (sturmeh)
Copyright (C) 2011 Dinnerbone
Copyright (C) 2011-2013 Steven "Drakia" Scott <Contact@TheDgtl.net>
Copyright (C) 2015-2020 Michael Smith (PseudoKnight)
Copyright (C) 2021-2022 Kristian Knarvik (EpicKnarvik97)
The following license notice applies to all source and resource files in the Stargate project:
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* The main class of the Stargate plugin
*/
@SuppressWarnings("unused")
public class Stargate extends JavaPlugin {
private static final Queue<BlockChangeRequest> blockChangeRequestQueue = new LinkedList<>();
private static final Queue<ChunkUnloadRequest> chunkUnloadQueue = new PriorityQueue<>();
private static Logger logger;
private static Stargate stargate;
private static String pluginVersion;
private static PluginManager pluginManager;
private static StargateConfig stargateConfig;
private static String updateAvailable = null;
/**
* Empty constructor necessary for Spigot
*/
public Stargate() {
super();
}
/**
* Special constructor used for MockBukkit
*
* @param loader <p>The plugin loader to be used.</p>
* @param descriptionFile <p>The description file to be used.</p>
* @param dataFolder <p>The data folder to be used.</p>
* @param file <p>The file to be used</p>
*/
protected Stargate(JavaPluginLoader loader, PluginDescriptionFile descriptionFile, File dataFolder, File file) {
super(loader, descriptionFile, dataFolder, file);
}
/**
* Stores information about an available update
*
* <p>If a non-null version is given, joining admins will be alerted about the new update.</p>
*
* @param version <p>The version of the new update available</p>
*/
public static void setUpdateAvailable(String version) {
updateAvailable = version;
}
/**
* Gets information about an available update
*
* @return <p>The version number if an update is available. Null otherwise</p>
*/
public static String getUpdateAvailable() {
return updateAvailable;
}
/**
* Gets an instance of this plugin
*
* @return <p>An instance of this plugin, or null if not instantiated</p>
*/
public static Stargate getInstance() {
return stargate;
}
/**
* Adds a block change request to the request queue
*
* @param request <p>The request to add</p>
*/
public static void addBlockChangeRequest(BlockChangeRequest request) {
if (request != null) {
blockChangeRequestQueue.add(request);
}
}
/**
* Gets the queue containing block change requests
*
* @return <p>A block change request queue</p>
*/
public static Queue<BlockChangeRequest> getBlockChangeRequestQueue() {
return blockChangeRequestQueue;
}
/**
* Gets the sender for sending messages to players
*
* @return <p>The sender for sending messages to players</p>
*/
public static MessageSender getMessageSender() {
return stargateConfig.getMessageSender();
}
/**
* Gets the object containing gate configuration values
*
* @return <p>The object containing gate configuration values</p>
*/
public static StargateGateConfig getGateConfig() {
return stargateConfig.getStargateGateConfig();
}
/**
* Gets the version of this plugin
*
* @return <p>This plugin's version</p>
*/
public static String getPluginVersion() {
return pluginVersion;
}
/**
* Gets the logger used for logging to the console
*
* @return <p>The logger</p>
*/
public static Logger getConsoleLogger() {
return logger;
}
/**
* Gets the max length of portal names and networks
*
* @return <p>The max portal name/network length</p>
*/
@SuppressWarnings("SameReturnValue")
public static int getMaxNameNetworkLength() {
return 13;
}
/**
* Sends a debug message
*
* @param route <p>The class name/route where something happened</p>
* @param message <p>A message describing what happened</p>
*/
public static void debug(String route, String message) {
if (stargateConfig == null || stargateConfig.isDebuggingEnabled()) {
logger.info("[Stargate::" + route + "] " + message);
} else {
logger.log(Level.FINEST, "[Stargate::" + route + "] " + message);
}
}
/**
* Logs an info message to the console
*
* @param message <p>The message to log</p>
*/
public static void logInfo(String message) {
logger.info(getBackupString("prefix") + message);
}
/**
* Logs a severe error message to the console
*
* @param message <p>The message to log</p>
*/
public static void logSevere(String message) {
log(Level.SEVERE, message);
}
/**
* Logs a warning message to the console
*
* @param message <p>The message to log</p>
*/
public static void logWarning(String message) {
log(Level.WARNING, message);
}
/**
* Logs a message to the console
*
* @param severity <p>The severity of the event triggering the message</p>
* @param message <p>The message to log</p>
*/
private static void log(Level severity, String message) {
logger.log(severity, getBackupString("prefix") + message);
}
/**
* Gets the folder for saving created portals
*
* <p>The returned String path is the full path to the folder</p>
*
* @return <p>The folder for storing the portal database</p>
*/
public static String getPortalFolder() {
return stargateConfig.getPortalFolder();
}
/**
* Gets the folder storing gate files
*
* <p>The returned String path is the full path to the folder</p>
*
* @return <p>The folder storing gate files</p>
*/
public static String getGateFolder() {
return stargateConfig.getGateFolder();
}
/**
* Gets the default network for gates where a network is not specified
*
* @return <p>The default network</p>
*/
public static String getDefaultNetwork() {
return stargateConfig.getStargateGateConfig().getDefaultPortalNetwork();
}
/**
* Gets a translated string given its string key
*
* <p>The name/key is the string before the equals sign in the language files</p>
*
* @param name <p>The name/key of the string to get</p>
* @return <p>The full translated string</p>
*/
public static String getString(String name) {
return stargateConfig.getLanguageLoader().getString(name);
}
/**
* Gets a backup string given its string key
*
* <p>The name/key is the string before the equals sign in the language files</p>
*
* @param name <p>The name/key of the string to get</p>
* @return <p>The full string in the backup language (English)</p>
*/
public static String getBackupString(String name) {
return stargateConfig.getLanguageLoader().getBackupString(name);
}
/**
* Replaces a variable in a string
*
* @param input <p>The input containing the variables</p>
* @param search <p>The variable to replace</p>
* @param value <p>The replacement value</p>
* @return <p>The input string with the search replaced with value</p>
*/
public static String replaceVars(String input, String search, String value) {
return input.replace(search, value);
}
/**
* Gets this plugin's plugin manager
*
* @return <p>A plugin manager</p>
*/
public static PluginManager getPluginManager() {
return pluginManager;
}
/**
* Gets the object containing economy config values
*
* @return <p>The object containing economy config values</p>
*/
public static EconomyConfig getEconomyConfig() {
return stargateConfig.getEconomyConfig();
}
@Override
public void onDisable() {
PortalHandler.closeAllPortals();
PortalRegistry.clearPortals();
stargateConfig.clearManagedWorlds();
getServer().getScheduler().cancelTasks(this);
}
@Override
public void onEnable() {
PluginDescriptionFile pluginDescriptionFile = this.getDescription();
pluginManager = getServer().getPluginManager();
FileConfiguration newConfig = this.getConfig();
this.saveDefaultConfig();
newConfig.options().copyDefaults(true);
logger = Logger.getLogger("Minecraft");
Server server = getServer();
stargate = this;
stargateConfig = new StargateConfig(logger);
stargateConfig.finishSetup();
pluginVersion = pluginDescriptionFile.getVersion();
logger.info(pluginDescriptionFile.getName() + " v." + pluginDescriptionFile.getVersion() + " is enabled.");
//Register events before loading gates to stop weird things from happening.
registerEventListeners();
//Run necessary threads
runThreads();
this.registerCommands();
//Check for any available updates
UpdateChecker.checkForUpdate(this, "https://api.spigotmc.org/legacy/update.php?resource=97784",
Stargate::getPluginVersion, Stargate::setUpdateAvailable);
}
/**
* Starts threads using the bukkit scheduler
*/
private void runThreads() {
BukkitScheduler scheduler = getServer().getScheduler();
scheduler.runTaskTimer(this, new StarGateThread(), 0L, 100L);
scheduler.runTaskTimer(this, new BlockChangeThread(), 0L, 1L);
scheduler.runTaskTimer(this, new ChunkUnloadThread(), 0L, 100L);
}
/**
* Registers all event listeners
*/
private void registerEventListeners() {
pluginManager.registerEvents(new PlayerEventListener(), this);
pluginManager.registerEvents(new BlockEventListener(), this);
pluginManager.registerEvents(new VehicleEventListener(), this);
pluginManager.registerEvents(new EntityEventListener(), this);
pluginManager.registerEvents(new PortalEventListener(), this);
pluginManager.registerEvents(new WorldEventListener(), this);
pluginManager.registerEvents(new PluginEventListener(this), this);
pluginManager.registerEvents(new TeleportEventListener(), this);
pluginManager.registerEvents(new EntitySpawnListener(), this);
}
/**
* Registers a command for this plugin
*/
private void registerCommands() {
PluginCommand stargateCommand = this.getCommand("stargate");
if (stargateCommand != null) {
stargateCommand.setExecutor(new CommandStarGate());
stargateCommand.setTabCompleter(new StarGateTabCompleter());
}
}
/**
* Gets the chunk unload queue containing chunks to unload
*
* @return <p>The chunk unload queue</p>
*/
public static Queue<ChunkUnloadRequest> getChunkUnloadQueue() {
return chunkUnloadQueue;
}
/**
* Adds a new chunk unload request to the chunk unload queue
*
* @param request <p>The new chunk unload request to add</p>
*/
public static void addChunkUnloadRequest(ChunkUnloadRequest request) {
chunkUnloadQueue.removeIf((item) -> item.getChunkToUnload().equals(request.getChunkToUnload()));
chunkUnloadQueue.add(request);
}
/**
* Gets the stargate configuration
*
* @return <p>The stargate configuration</p>
*/
public static StargateConfig getStargateConfig() {
return stargateConfig;
}
}

View File

@@ -0,0 +1,32 @@
package net.knarcraft.stargate.command;
import net.knarcraft.stargate.Stargate;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
/**
* This command represents the plugin's about command
*/
public class CommandAbout implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] strings) {
ChatColor textColor = ChatColor.GOLD;
ChatColor highlightColor = ChatColor.GREEN;
commandSender.sendMessage(textColor + "Stargate Plugin originally created by " + highlightColor +
"Drakia" + textColor + ", and revived by " + highlightColor + "EpicKnarvik97");
commandSender.sendMessage(textColor + "Go to " + highlightColor +
"https://git.knarcraft.net/EpicKnarvik97/Stargate " + textColor + "for the official repository");
String author = Stargate.getStargateConfig().getLanguageLoader().getString("author");
if (!author.isEmpty()) {
commandSender.sendMessage(textColor + "Language created by " + highlightColor + author);
}
return true;
}
}

View File

@@ -0,0 +1,429 @@
package net.knarcraft.stargate.command;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.config.ConfigOption;
import net.knarcraft.stargate.config.ConfigTag;
import net.knarcraft.stargate.config.DynmapManager;
import net.knarcraft.stargate.config.OptionDataType;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalRegistry;
import net.knarcraft.stargate.portal.PortalSignDrawer;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
/**
* This command represents the config command for changing config values
*/
public class CommandConfig implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] args) {
if (commandSender instanceof Player player) {
if (!player.hasPermission("stargate.admin.config")) {
Stargate.getMessageSender().sendErrorMessage(commandSender, "Permission Denied");
return true;
}
}
if (args.length > 0) {
ConfigOption selectedOption = ConfigOption.getByName(args[0]);
if (selectedOption == null) {
return false;
}
if (args.length > 1) {
if (selectedOption.getDataType() == OptionDataType.STRING_LIST) {
updateListConfigValue(selectedOption, commandSender, args);
} else {
updateConfigValue(selectedOption, commandSender, args[1]);
}
} else {
//Display info and the current value of the given config value
printConfigOptionValue(commandSender, selectedOption);
}
return true;
} else {
//Display all config options
displayConfigValues(commandSender);
}
return true;
}
/**
* Updates a config value
*
* @param selectedOption <p>The option which should be updated</p>
* @param commandSender <p>The command sender that changed the value</p>
* @param value <p>The new value of the config option</p>
*/
private void updateConfigValue(ConfigOption selectedOption, CommandSender commandSender, String value) {
FileConfiguration configuration = Stargate.getInstance().getConfig();
//Validate any sign colors
if (ConfigTag.COLOR.isTagged(selectedOption)) {
try {
ChatColor.of(value.toUpperCase());
} catch (IllegalArgumentException | NullPointerException ignored) {
commandSender.sendMessage(ChatColor.RED + "Invalid color given");
return;
}
}
//Store the config values, accounting for the data type
switch (selectedOption.getDataType()) {
case BOOLEAN -> updateBooleanConfigValue(selectedOption, value, configuration);
case INTEGER -> {
Integer intValue = getInteger(commandSender, selectedOption, value);
if (intValue == null) {
return;
} else {
Stargate.getStargateConfig().getConfigOptionsReference().put(selectedOption, intValue);
configuration.set(selectedOption.getConfigNode(), intValue);
}
}
case DOUBLE -> {
Double doubleValue = getDouble(commandSender, selectedOption, value);
if (doubleValue == null) {
return;
} else {
Stargate.getStargateConfig().getConfigOptionsReference().put(selectedOption, doubleValue);
configuration.set(selectedOption.getConfigNode(), doubleValue);
}
}
case STRING -> {
updateStringConfigValue(selectedOption, commandSender, value);
configuration.set(selectedOption.getConfigNode(), value);
}
default -> {
Stargate.getStargateConfig().getConfigOptionsReference().put(selectedOption, value);
configuration.set(selectedOption.getConfigNode(), value);
}
}
saveAndReload(selectedOption, commandSender);
}
/**
* Updates a boolean config value
*
* @param selectedOption <p>The option which should be updated</p>
* @param value <p>The new value of the config option</p>
* @param configuration <p>The configuration file to save to</p>
*/
private void updateBooleanConfigValue(ConfigOption selectedOption, String value, FileConfiguration configuration) {
boolean newValue = Boolean.parseBoolean(value);
if (selectedOption == ConfigOption.ENABLE_BUNGEE && newValue != Stargate.getGateConfig().enableBungee()) {
Stargate.getStargateConfig().startStopBungeeListener(newValue);
}
Stargate.getStargateConfig().getConfigOptionsReference().put(selectedOption, newValue);
configuration.set(selectedOption.getConfigNode(), newValue);
}
/**
* Updates a string config value
*
* @param selectedOption <p>The option which should be updated</p>
* @param commandSender <p>The command sender that changed the value</p>
* @param value <p>The new value of the config option</p>
*/
private void updateStringConfigValue(ConfigOption selectedOption, CommandSender commandSender, String value) {
if (selectedOption == ConfigOption.GATE_FOLDER || selectedOption == ConfigOption.PORTAL_FOLDER ||
selectedOption == ConfigOption.DEFAULT_GATE_NETWORK) {
if (value.contains("../") || value.contains("..\\")) {
commandSender.sendMessage(ChatColor.RED + "Path traversal characters cannot be used");
return;
}
}
if (ConfigTag.COLOR.isTagged(selectedOption)) {
if (!registerColor(selectedOption, value, commandSender)) {
return;
}
}
if (selectedOption == ConfigOption.LANGUAGE) {
Stargate.getStargateConfig().getLanguageLoader().setChosenLanguage(value);
}
Stargate.getStargateConfig().getConfigOptionsReference().put(selectedOption, value);
}
/**
* Updates a config value
*
* @param selectedOption <p>The option which should be updated</p>
* @param commandSender <p>The command sender that changed the value</p>
* @param arguments <p>The arguments for the new config option</p>
*/
private void updateListConfigValue(ConfigOption selectedOption, CommandSender commandSender, String[] arguments) {
FileConfiguration configuration = Stargate.getInstance().getConfig();
if (selectedOption == ConfigOption.PER_SIGN_COLORS) {
if (arguments.length < 4) {
Stargate.getMessageSender().sendErrorMessage(commandSender, "Usage: /sg config perSignColors " +
"<SIGN_TYPE> <MAIN_COLOR> <HIGHLIGHTING_COLOR>");
return;
}
String colorString = parsePerSignColorInput(commandSender, arguments);
if (colorString == null) {
return;
}
//Update the per-sign colors according to input
updatePerSignColors(arguments[1], colorString, configuration);
}
saveAndReload(selectedOption, commandSender);
}
/**
* Parses the input given for changing the per-color string
*
* @param commandSender <p>The command sender that triggered the command</p>
* @param arguments <p>The arguments given by the user</p>
* @return <p>The per-sign color string to update with, or null if the input was invalid</p>
*/
private String parsePerSignColorInput(CommandSender commandSender, String[] arguments) {
//Make sure the sign type is an actual sign
if (Material.matchMaterial(arguments[1] + "_SIGN") == null) {
Stargate.getMessageSender().sendErrorMessage(commandSender, "The given sign type is invalid");
return null;
}
String colorString = arguments[1] + ":";
//Validate the colors given by the user
String[] errorMessage = new String[]{"The given main sign color is invalid!", "The given highlight sign color is invalid!"};
String[] newColors = new String[2];
for (int i = 0; i < 2; i++) {
if (validatePerSignColor(arguments[i + 2])) {
newColors[i] = arguments[i + 2];
} else {
Stargate.getMessageSender().sendErrorMessage(commandSender, errorMessage[i]);
return null;
}
}
colorString += String.join(",", newColors);
return colorString;
}
/**
* Updates the per-sign colors with the given input
*
* @param signType <p>The sign type that is updated</p>
* @param colorString <p>The new color string to replace any previous value with</p>
* @param configuration <p>The file configuration to update with the new per-sign colors</p>
*/
private void updatePerSignColors(String signType, String colorString, FileConfiguration configuration) {
List<String> newColorStrings = new ArrayList<>();
List<?> oldColors = (List<?>) Stargate.getStargateConfig().getConfigOptionsReference().get(ConfigOption.PER_SIGN_COLORS);
for (Object object : oldColors) {
newColorStrings.add(String.valueOf(object));
}
newColorStrings.removeIf((item) -> item.startsWith(signType));
newColorStrings.add(colorString);
Stargate.getStargateConfig().getConfigOptionsReference().put(ConfigOption.PER_SIGN_COLORS, newColorStrings);
configuration.set(ConfigOption.PER_SIGN_COLORS.getConfigNode(), newColorStrings);
}
/**
* Tries to validate one of the colors given when changing per-sign colors
*
* @param color <p>The color chosen by the user</p>
* @return <p>True if the given color is valid</p>
*/
private boolean validatePerSignColor(String color) {
ChatColor newHighlightColor = parseColor(color);
return newHighlightColor != null || color.equalsIgnoreCase("default") ||
color.equalsIgnoreCase("inverted");
}
/**
* Saves the configuration file and reloads as necessary
*
* @param selectedOption <p>The config option that was changed</p>
* @param commandSender <p>The command sender that executed the config command</p>
*/
private void saveAndReload(ConfigOption selectedOption, CommandSender commandSender) {
//Save the config file and reload if necessary
Stargate.getInstance().saveConfig();
Stargate.getMessageSender().sendSuccessMessage(commandSender, "Config updated");
//Reload whatever is necessary
reloadIfNecessary(commandSender, selectedOption);
}
/**
* Registers the chat color if
*
* @param selectedOption <p>The option to change</p>
* @param commandSender <p>The command sender to alert if the color is invalid</p>
* @param value <p>The new option value</p>
*/
private boolean registerColor(ConfigOption selectedOption, String value, CommandSender commandSender) {
ChatColor parsedColor = parseColor(value);
if (parsedColor == null) {
commandSender.sendMessage(ChatColor.RED + "Invalid color given");
return false;
}
if (selectedOption == ConfigOption.FREE_GATES_COLOR) {
PortalSignDrawer.setFreeColor(parsedColor);
} else if (selectedOption == ConfigOption.MAIN_SIGN_COLOR) {
PortalSignDrawer.setMainColor(parsedColor);
} else if (selectedOption == ConfigOption.HIGHLIGHT_SIGN_COLOR) {
PortalSignDrawer.setHighlightColor(parsedColor);
}
return true;
}
/**
* Parses a chat color
*
* @param value <p>The value to parse</p>
* @return <p>The parsed color or null</p>
*/
private ChatColor parseColor(String value) {
try {
return ChatColor.of(value.toUpperCase());
} catch (IllegalArgumentException | NullPointerException ignored) {
return null;
}
}
/**
* Gets an integer from a string
*
* @param commandSender <p>The command sender that sent the config command</p>
* @param selectedOption <p>The option the command sender is trying to change</p>
* @param value <p>The value given</p>
* @return <p>An integer, or null if it was invalid</p>
*/
private Integer getInteger(CommandSender commandSender, ConfigOption selectedOption, String value) {
try {
int intValue = Integer.parseInt(value);
if ((selectedOption == ConfigOption.USE_COST || selectedOption == ConfigOption.CREATE_COST) && intValue < 0) {
commandSender.sendMessage(ChatColor.RED + "This config option cannot be negative.");
return null;
}
return intValue;
} catch (NumberFormatException exception) {
commandSender.sendMessage(ChatColor.RED + "Invalid number given");
return null;
}
}
/**
* Gets a double from a string
*
* @param commandSender <p>The command sender that sent the config command</p>
* @param selectedOption <p>The option the command sender is trying to change</p>
* @param value <p>The value given</p>
* @return <p>A double, or null if it was invalid</p>
*/
private Double getDouble(CommandSender commandSender, ConfigOption selectedOption, String value) {
try {
double doubleValue = Double.parseDouble(value);
if (selectedOption == ConfigOption.EXIT_VELOCITY && doubleValue < 0) {
commandSender.sendMessage(ChatColor.RED + "This config option cannot be negative.");
return null;
}
return doubleValue;
} catch (NumberFormatException exception) {
commandSender.sendMessage(ChatColor.RED + "Invalid number given");
return null;
}
}
/**
* Reloads the config if necessary
*
* @param commandSender <p>The command sender initiating the reload</p>
* @param configOption <p>The changed config option</p>
*/
private void reloadIfNecessary(CommandSender commandSender, ConfigOption configOption) {
if (ConfigTag.requiresFullReload(configOption)) {
//Reload everything
Stargate.getStargateConfig().reload(commandSender);
} else {
if (ConfigTag.requiresColorReload(configOption)) {
Stargate.getStargateConfig().getStargateGateConfig().loadPerSignColors();
}
if (ConfigTag.requiresPortalReload(configOption)) {
//Just unload and reload the portals
Stargate.getStargateConfig().unloadAllPortals();
Stargate.getStargateConfig().loadAllPortals();
}
if (ConfigTag.requiresLanguageReload(configOption)) {
//Reload the language loader
Stargate.getStargateConfig().getLanguageLoader().reload();
//Re-draw all portal signs
for (Portal portal : PortalRegistry.getAllPortals()) {
portal.drawSign();
}
}
if (ConfigTag.requiresEconomyReload(configOption)) {
//Load or unload Vault and Economy as necessary
Stargate.getStargateConfig().reloadEconomy();
}
if (ConfigTag.requiresDynmapReload(configOption)) {
//Regenerate all Dynmap markers
DynmapManager.addAllPortalMarkers();
}
}
}
/**
* Prints information about a config option and its current value
*
* @param sender <p>The command sender that sent the command</p>
* @param option <p>The config option to print information about</p>
*/
private void printConfigOptionValue(CommandSender sender, ConfigOption option) {
Object value = Stargate.getStargateConfig().getConfigOptions().get(option);
sender.sendMessage(getOptionDescription(option));
sender.sendMessage(ChatColor.GREEN + "Current value: " + ChatColor.GOLD + value);
}
/**
* Displays the name and a small description of every config value
*
* @param sender <p>The command sender to display the config list to</p>
*/
private void displayConfigValues(CommandSender sender) {
sender.sendMessage(ChatColor.GREEN + Stargate.getBackupString("prefix") + ChatColor.GOLD +
"Config values:");
for (ConfigOption option : ConfigOption.values()) {
sender.sendMessage(getOptionDescription(option));
}
}
/**
* Gets the description of a single config option
*
* @param option <p>The option to describe</p>
* @return <p>A string describing the config option</p>
*/
private String getOptionDescription(ConfigOption option) {
Object defaultValue = option.getDefaultValue();
String stringValue = String.valueOf(defaultValue);
if (option.getDataType() == OptionDataType.STRING_LIST) {
stringValue = "[" + String.join(",", (String[]) defaultValue) + "]";
}
return ChatColor.GOLD + option.getName() + ChatColor.WHITE + " - " + ChatColor.GREEN + option.getDescription() +
ChatColor.DARK_GRAY + " (Default: " + ChatColor.GRAY + stringValue + ChatColor.DARK_GRAY + ")";
}
}

View File

@@ -0,0 +1,28 @@
package net.knarcraft.stargate.command;
import net.knarcraft.stargate.Stargate;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* This command represents the plugin's reload command
*/
public class CommandReload implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] args) {
if (commandSender instanceof Player player) {
if (!player.hasPermission("stargate.admin.reload")) {
Stargate.getMessageSender().sendErrorMessage(commandSender, "Permission Denied");
return true;
}
}
Stargate.getStargateConfig().reload(commandSender);
return true;
}
}

View File

@@ -0,0 +1,40 @@
package net.knarcraft.stargate.command;
import net.knarcraft.stargate.Stargate;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
/**
* This command represents any command which starts with stargate
*
* <p>This prefix command should only be used for commands which are certain to collide with others and which relate to
* the plugin itself, not commands for functions of the plugin.</p>
*/
public class CommandStarGate implements CommandExecutor {
@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] args) {
if (args.length > 0) {
if (args[0].equalsIgnoreCase("about")) {
return new CommandAbout().onCommand(commandSender, command, s, args);
} else if (args[0].equalsIgnoreCase("reload")) {
return new CommandReload().onCommand(commandSender, command, s, args);
} else if (args[0].equalsIgnoreCase("config")) {
String[] subArgs = Arrays.copyOfRange(args, 1, args.length);
return new CommandConfig().onCommand(commandSender, command, s, subArgs);
}
return false;
} else {
commandSender.sendMessage(ChatColor.GOLD + "Stargate version " +
ChatColor.GREEN + Stargate.getPluginVersion());
return true;
}
}
}

View File

@@ -0,0 +1,236 @@
package net.knarcraft.stargate.command;
import net.knarcraft.stargate.config.ConfigOption;
import net.knarcraft.stargate.config.OptionDataType;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Material;
import org.bukkit.Tag;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import static net.knarcraft.knarlib.util.TabCompletionHelper.filterMatchingStartsWith;
/**
* This is the completer for stargates config sub-command (/sg config)
*/
public class ConfigTabCompleter implements TabCompleter {
private List<String> signTypes;
private List<String> booleans;
private List<String> integers;
private List<String> chatColors;
private List<String> languages;
private List<String> extendedColors;
private List<String> doubles;
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] args) {
if (signTypes == null || booleans == null || integers == null || chatColors == null || languages == null) {
initializeAutoCompleteLists();
}
if (args.length > 1) {
ConfigOption selectedOption = ConfigOption.getByName(args[0]);
if (selectedOption == null) {
return new ArrayList<>();
} else if (selectedOption.getDataType() == OptionDataType.STRING_LIST) {
return getPossibleStringListOptionValues(selectedOption, args);
} else {
return getPossibleOptionValues(selectedOption, args[1]);
}
} else {
List<String> configOptionNames = new ArrayList<>();
for (ConfigOption option : ConfigOption.values()) {
configOptionNames.add(option.getName());
}
return filterMatchingStartsWith(configOptionNames, args[0]);
}
}
/**
* Get possible values for the selected option
*
* @param selectedOption <p>The selected option</p>
* @param typedText <p>The beginning of the typed text, for filtering matching results</p>
* @return <p>Some or all of the valid values for the option</p>
*/
private List<String> getPossibleOptionValues(ConfigOption selectedOption, String typedText) {
switch (selectedOption) {
case LANGUAGE:
//Return available languages
return filterMatchingStartsWith(languages, typedText);
case GATE_FOLDER:
case PORTAL_FOLDER:
case DEFAULT_GATE_NETWORK:
//Just return the default value as most values should be possible
if (typedText.trim().isEmpty()) {
return putStringInList((String) selectedOption.getDefaultValue());
} else {
return new ArrayList<>();
}
case MAIN_SIGN_COLOR:
case HIGHLIGHT_SIGN_COLOR:
case FREE_GATES_COLOR:
//Return all colors
return filterMatchingStartsWith(chatColors, typedText);
}
//If the config value is a boolean, show the two boolean values
if (selectedOption.getDataType() == OptionDataType.BOOLEAN) {
return filterMatchingStartsWith(booleans, typedText);
}
//If the config value is an integer, display some valid numbers
if (selectedOption.getDataType() == OptionDataType.INTEGER) {
if (typedText.trim().isEmpty()) {
return integers;
} else {
return new ArrayList<>();
}
}
//If the config value is a double, display some valid numbers
if (selectedOption.getDataType() == OptionDataType.DOUBLE) {
if (typedText.trim().isEmpty()) {
return doubles;
} else {
return new ArrayList<>();
}
}
return null;
}
/**
* Get possible values for the selected string list option
*
* @param selectedOption <p>The selected option</p>
* @param args <p>The arguments given by the user</p>
* @return <p>Some or all of the valid values for the option</p>
*/
private List<String> getPossibleStringListOptionValues(ConfigOption selectedOption, String[] args) {
if (selectedOption == ConfigOption.PER_SIGN_COLORS) {
return getPerSignColorCompletion(args);
} else {
return null;
}
}
/**
* Gets the tab completion values for completing the per-sign color text
*
* @param args <p>The arguments given by the user</p>
* @return <p>The options to give the user</p>
*/
private List<String> getPerSignColorCompletion(String[] args) {
if (args.length < 3) {
return filterMatchingStartsWith(signTypes, args[1]);
} else if (args.length < 4) {
return filterMatchingStartsWith(extendedColors, args[2]);
} else if (args.length < 5) {
return filterMatchingStartsWith(extendedColors, args[3]);
}
return new ArrayList<>();
}
/**
* Puts a single string value into a string list
*
* @param value <p>The string to make into a list</p>
* @return <p>A list containing the string value</p>
*/
private List<String> putStringInList(String value) {
List<String> list = new ArrayList<>();
list.add(value);
return list;
}
/**
* Initializes all lists of auto-completable values
*/
private void initializeAutoCompleteLists() {
booleans = new ArrayList<>();
booleans.add("true");
booleans.add("false");
integers = new ArrayList<>();
integers.add("0");
integers.add("5");
signTypes = new ArrayList<>();
for (Material material : Material.values()) {
if (Tag.STANDING_SIGNS.isTagged(material)) {
signTypes.add(material.toString().replace("_SIGN", ""));
}
}
getColors();
initializeLanguages();
extendedColors = new ArrayList<>(chatColors);
extendedColors.add("default");
extendedColors.add("inverted");
doubles = new ArrayList<>();
doubles.add("5");
doubles.add("1");
doubles.add("0.5");
doubles.add("0.1");
}
/**
* Initializes the list of chat colors
*/
private void getColors() {
chatColors = new ArrayList<>();
for (ChatColor color : getChatColors()) {
chatColors.add(color.getName());
}
}
/**
* Gets available chat colors
*
* @return <p>The available chat colors</p>
*/
private List<ChatColor> getChatColors() {
List<ChatColor> chatColors = new ArrayList<>();
char[] colors = new char[]{'a', 'b', 'c', 'd', 'e', 'f', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
for (char color : colors) {
chatColors.add(ChatColor.getByChar(color));
}
chatColors.add(ChatColor.of("#ed76d9"));
chatColors.add(ChatColor.of("#ffecb7"));
return chatColors;
}
/**
* Initializes the list of all available languages
*/
private void initializeLanguages() {
languages = new ArrayList<>();
languages.add("de");
languages.add("en");
languages.add("es");
languages.add("fr");
languages.add("hu");
languages.add("it");
languages.add("ja");
languages.add("nb-no");
languages.add("nl");
languages.add("nn-no");
languages.add("pt-br");
languages.add("ru");
languages.add("zh_cn");
//TODO: Generate this list dynamically by listing the language files in the jar and adding the user's custom
// language files
}
}

View File

@@ -0,0 +1,57 @@
package net.knarcraft.stargate.command;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* This is the tab completer for the /stargate (/sg) command
*/
public class StarGateTabCompleter implements TabCompleter {
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command,
@NotNull String s, @NotNull String[] args) {
if (args.length == 1) {
List<String> commands = getAvailableCommands(commandSender);
List<String> matchingCommands = new ArrayList<>();
for (String availableCommand : commands) {
if (availableCommand.startsWith(args[0])) {
matchingCommands.add(availableCommand);
}
}
return matchingCommands;
} else if (args.length > 1 && args[0].equalsIgnoreCase("config")) {
String[] subArgs = Arrays.copyOfRange(args, 1, args.length);
return new ConfigTabCompleter().onTabComplete(commandSender, command, s, subArgs);
} else {
return new ArrayList<>();
}
}
/**
* Gets the available commands
*
* @param commandSender <p>The command sender to get available commands for</p>
* @return <p>The commands available to the command sender</p>
*/
private List<String> getAvailableCommands(CommandSender commandSender) {
List<String> commands = new ArrayList<>();
commands.add("about");
if (!(commandSender instanceof Player player) || player.hasPermission("stargate.admin.reload")) {
commands.add("reload");
}
if (!(commandSender instanceof Player player) || player.hasPermission("stargate.admin.config")) {
commands.add("config");
}
return commands;
}
}

View File

@@ -0,0 +1,290 @@
package net.knarcraft.stargate.config;
/**
* A ConfigOption represents one of the available config options
*/
public enum ConfigOption {
/**
* The language used for player-interface text
*/
LANGUAGE("language", "The language used for all signs and all messages to players", "en"),
/**
* The folder for portal files
*/
PORTAL_FOLDER("folders.portalFolder", "The folder containing the portal databases", "plugins/Stargate/portals/"),
/**
* The folder for gate files
*/
GATE_FOLDER("folders.gateFolder", "The folder containing all gate files", "plugins/Stargate/gates/"),
/**
* The max number of portals on a single network
*/
MAX_GATES_EACH_NETWORK("gates.maxGatesEachNetwork", "The max number of stargates in a single network", 0),
/**
* The network used if not specified
*/
DEFAULT_GATE_NETWORK("gates.defaultGateNetwork", "The network used when no network is specified", "central"),
/**
* Whether to remember the lastly used destination
*/
REMEMBER_DESTINATION("gates.cosmetic.rememberDestination", "Whether to remember the last destination used", false),
/**
* Whether to sort the network destinations
*/
SORT_NETWORK_DESTINATIONS("gates.cosmetic.sortNetworkDestinations", "Whether to sort destinations by name", false),
/**
* The main color to use for all signs
*/
MAIN_SIGN_COLOR("gates.cosmetic.mainSignColor", "The main text color of all stargate signs", "BLACK"),
/**
* The color to use for highlighting sign text
*/
HIGHLIGHT_SIGN_COLOR("gates.cosmetic.highlightSignColor", "The text color used for highlighting stargate signs", "WHITE"),
/**
* The colors to use for each type of sign
*/
PER_SIGN_COLORS("gates.cosmetic.perSignColors", "The per-sign color specification", new String[]{
"'ACACIA:default,default'", "'BIRCH:default,default'", "'CRIMSON:inverted,inverted'", "'DARK_OAK:inverted,inverted'",
"'JUNGLE:default,default'", "'OAK:default,default'", "'SPRUCE:inverted,inverted'", "'WARPED:inverted,inverted'"}),
/**
* Whether to destroy portals when any blocks are broken by explosions
*/
DESTROYED_BY_EXPLOSION("gates.integrity.destroyedByExplosion", "Whether stargates should be destroyed by explosions", false),
/**
* Whether to verify each portal's gate layout after each load
*/
VERIFY_PORTALS("gates.integrity.verifyPortals", "Whether to verify that portals match their gate layout on load", false),
/**
* Whether to protect the entrance of portals
*/
PROTECT_ENTRANCE("gates.integrity.protectEntrance", "Whether to protect stargates' entrances", false),
/**
* Whether to enable BungeeCord support
*/
ENABLE_BUNGEE("gates.functionality.enableBungee", "Whether to enable BungeeCord support", false),
/**
* Whether to enable vehicle teleportation
*/
HANDLE_VEHICLES("gates.functionality.handleVehicles", "Whether to enable vehicle teleportation", true),
/**
* Whether to enable teleportation of empty vehicles
*/
HANDLE_EMPTY_VEHICLES("gates.functionality.handleEmptyVehicles", "Whether to enable teleportation of empty vehicles", true),
/**
* Whether to enable teleportation of creatures using vehicles
*/
HANDLE_CREATURE_TRANSPORTATION("gates.functionality.handleCreatureTransportation",
"Whether to enable teleportation of vehicles containing non-player creatures", true),
/**
* Whether to allow creatures to teleport alone, bypassing any access restrictions
*/
HANDLE_NON_PLAYER_VEHICLES("gates.functionality.handleNonPlayerVehicles",
"Whether to enable teleportation of non-empty vehicles without a player", true),
/**
* Whether to enable teleportations of creatures on a leash
*/
HANDLE_LEASHED_CREATURES("gates.functionality.handleLeashedCreatures",
"Whether to enable players to teleport a creature on a leash", true),
/**
* Whether to enable a fix that makes teleportation of minecarts/boats work even with craftbook's vehicle removal
*/
ENABLE_CRAFT_BOOK_REMOVE_ON_EJECT_FIX("gates.functionality.enableCraftBookRemoveOnEjectFix",
"Whether to enable a fix that causes loss of NBT data, but allows vehicle teleportation to work " +
"when CraftBook's remove minecart/boat on eject setting is enabled", false),
/**
* The delay between teleporting a vehicle and adding the player as passenger
*/
WAIT_FOR_PLAYER_AFTER_TELEPORT_DELAY("advanced.waitForPlayerAfterTeleportDelay",
"The amount of ticks to wait before adding a player as passenger of a vehicle. On slow servers, " +
"a value of 6 is required to avoid client glitches after teleporting on a vehicle.", 6),
/**
* Whether to enable economy support for taking payment from players creating/destroying/using stargates
*/
USE_ECONOMY("economy.useEconomy", "Whether to use economy to incur fees when stargates are used, created or destroyed", false),
/**
* The cost of creating a new stargate
*/
CREATE_COST("economy.createCost", "The cost of creating a new stargate", 0),
/**
* The cost of destroying a stargate
*/
DESTROY_COST("economy.destroyCost", "The cost of destroying a stargate. Negative to refund", 0),
/**
* The cost of using (teleporting through) a stargate
*/
USE_COST("economy.useCost", "The cost of using (teleporting through) a stargate", 0),
/**
* Whether any payments should go to the stargate's owner
*/
TO_OWNER("economy.toOwner", "Whether any teleportation fees should go to the owner of the used stargate", false),
/**
* Whether to charge for using a stargate, even if its destination is free
*/
CHARGE_FREE_DESTINATION("economy.chargeFreeDestination",
"Whether to require payment if the destination is free, but the entrance stargate is not", true),
/**
* Whether to mark free gates with a different color
*/
FREE_GATES_COLORED("economy.freeGatesColored", "Whether to use coloring to mark all free stargates", false),
/**
* The color to use for marking free stargates
*/
FREE_GATES_COLOR("economy.freeGatesColor", "The color to use for marking free stargates", "DARK_GREEN"),
/**
* Whether to enable debug output
*/
DEBUG("debugging.debug", "Whether to enable debugging output", false),
/**
* Whether to enable debug output for debugging permissions
*/
PERMISSION_DEBUG("debugging.permissionDebug", "Whether to enable permission debugging output", false),
/**
* Whether to alert admins about new updates
*/
ADMIN_UPDATE_ALERT("adminUpdateAlert", "Whether to alert admins about new plugin updates", true),
/**
* The velocity of players exiting a stargate, relative to the entry velocity
*/
EXIT_VELOCITY("gates.exitVelocity", "The velocity of players exiting stargates, relative to the entry velocity", 0.1D),
/**
* Whether to enable showing Stargates in Dynmap
*/
ENABLE_DYNMAP("dynmap.enableDynmap", "Whether to display Stargates in Dynmap's map", true),
/**
* Whether to hide Dynmap icons by default
*/
DYNMAP_ICONS_DEFAULT_HIDDEN("dynmap.dynmapIconsHiddenByDefault",
"Whether to hide Stargate's Dynmap icons by default, requiring the user to enable them.", true);
private final String configNode;
private final String description;
private final Object defaultValue;
private final OptionDataType dataType;
/**
* Instantiates a new config option
*
* @param configNode <p>The full path of this config option's config node</p>
* @param description <p>The description of what this config option does</p>
* @param defaultValue <p>The default value of this config option</p>
*/
ConfigOption(String configNode, String description, Object defaultValue) {
this.configNode = configNode;
this.description = description;
this.defaultValue = defaultValue;
if (defaultValue instanceof String[]) {
this.dataType = OptionDataType.STRING_LIST;
} else if (defaultValue instanceof String) {
this.dataType = OptionDataType.STRING;
} else if (defaultValue instanceof Boolean) {
this.dataType = OptionDataType.BOOLEAN;
} else if (defaultValue instanceof Integer) {
this.dataType = OptionDataType.INTEGER;
} else if (defaultValue instanceof Double) {
this.dataType = OptionDataType.DOUBLE;
} else {
throw new IllegalArgumentException("Unknown config data type encountered: " + defaultValue);
}
}
/**
* Gets a config option given its name
*
* @param name <p>The name of the config option to get</p>
* @return <p>The corresponding config option, or null if the name is invalid</p>
*/
public static ConfigOption getByName(String name) {
for (ConfigOption option : ConfigOption.values()) {
if (option.getName().equalsIgnoreCase(name)) {
return option;
}
}
return null;
}
/**
* Gets the name of this config option
*
* @return <p>The name of this config option</p>
*/
public String getName() {
if (!this.configNode.contains(".")) {
return this.configNode;
}
String[] pathParts = this.configNode.split("\\.");
return pathParts[pathParts.length - 1];
}
/**
* Gets the data type used for storing this config option
*
* @return <p>The data type used</p>
*/
public OptionDataType getDataType() {
return this.dataType;
}
/**
* Gets the config node of this config option
*
* @return <p>This config option's config node</p>
*/
public String getConfigNode() {
return this.configNode;
}
/**
* Gets the description of what this config option does
*
* @return <p>The description of this config option</p>
*/
public String getDescription() {
return this.description;
}
/**
* Gets this config option's default value
*
* @return <p>This config option's default value</p>
*/
public Object getDefaultValue() {
return this.defaultValue;
}
}

View File

@@ -0,0 +1,96 @@
package net.knarcraft.stargate.config;
import java.util.Arrays;
/**
* A config tag groups config values by a property
*/
public enum ConfigTag {
COLOR(new ConfigOption[]{ConfigOption.FREE_GATES_COLOR, ConfigOption.MAIN_SIGN_COLOR,
ConfigOption.HIGHLIGHT_SIGN_COLOR, ConfigOption.PER_SIGN_COLORS}),
FOLDER(new ConfigOption[]{ConfigOption.GATE_FOLDER, ConfigOption.PORTAL_FOLDER}),
DYNMAP(new ConfigOption[]{ConfigOption.ENABLE_DYNMAP, ConfigOption.DYNMAP_ICONS_DEFAULT_HIDDEN});
private final ConfigOption[] taggedOptions;
/**
* Instantiates a new config tag
*
* @param taggedOptions <p>The config options included in this tag</p>
*/
ConfigTag(ConfigOption[] taggedOptions) {
this.taggedOptions = taggedOptions;
}
/**
* Checks whether a config tag includes the given config option
*
* @param option <p>The config option to check</p>
* @return <p>True of the config option is tagged</p>
*/
public boolean isTagged(ConfigOption option) {
return Arrays.stream(taggedOptions).anyMatch((item) -> item == option);
}
/**
* Checks whether a given config option requires a "reload of colors" to take effect
*
* @param configOption <p>The config option to check</p>
* @return <p>True if changing the config option requires a "reload of colors" to take effect</p>
*/
public static boolean requiresColorReload(ConfigOption configOption) {
return (COLOR.isTagged(configOption) && configOption != ConfigOption.FREE_GATES_COLOR);
}
/**
* Checks whether a given config option requires a full reload to take effect
*
* @param option <p>The config option to check</p>
* @return <p>True if changing the config option requires a full reload to take effect</p>
*/
public static boolean requiresFullReload(ConfigOption option) {
return FOLDER.isTagged(option);
}
/**
* Checks whether a given config option requires a re-load of all Dynmap markers
*
* @param configOption <p>The config option to check</p>
* @return <p>True if changing the config option requires a reload of all dynmap markers</p>
*/
public static boolean requiresDynmapReload(ConfigOption configOption) {
return DYNMAP.isTagged(configOption);
}
/**
* Checks whether a given config option requires a portal reload to take effect
*
* @param option <p>The config option to check</p>
* @return <p>True if changing the config option requires a portal reload to take effect</p>
*/
public static boolean requiresPortalReload(ConfigOption option) {
return COLOR.isTagged(option) || FOLDER.isTagged(option) || option == ConfigOption.VERIFY_PORTALS;
}
/**
* Checks whether a given config option requires the language loader to be reloaded
*
* @param option <p>The config option to check</p>
* @return <p>True if the language loader requires a reload</p>
*/
public static boolean requiresLanguageReload(ConfigOption option) {
return option == ConfigOption.LANGUAGE;
}
/**
* Checks whether a given config option requires economy to be reloaded
*
* @param option <p>The config option to check</p>
* @return <p>True if economy requires a reload</p>
*/
public static boolean requiresEconomyReload(ConfigOption option) {
return option == ConfigOption.USE_ECONOMY;
}
}

View File

@@ -0,0 +1,134 @@
package net.knarcraft.stargate.config;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalRegistry;
import org.bukkit.Location;
import org.bukkit.World;
import org.dynmap.DynmapAPI;
import org.dynmap.markers.GenericMarker;
import org.dynmap.markers.Marker;
import org.dynmap.markers.MarkerIcon;
import org.dynmap.markers.MarkerSet;
/**
* A manager for dealing with everything Dynmap
*/
public final class DynmapManager {
private static MarkerSet markerSet;
private static MarkerIcon portalIcon;
private DynmapManager() {
}
/**
* Initializes the dynmap manager
*
* @param dynmapAPI <p>A reference</p>
*/
public static void initialize(DynmapAPI dynmapAPI) {
if (dynmapAPI == null || dynmapAPI.getMarkerAPI() == null) {
markerSet = null;
portalIcon = null;
} else {
markerSet = dynmapAPI.getMarkerAPI().createMarkerSet("stargate", "Stargate", null, false);
if (markerSet != null) {
markerSet.setHideByDefault(Stargate.getStargateConfig().hideDynmapIcons());
}
portalIcon = dynmapAPI.getMarkerAPI().getMarkerIcon("portal");
}
}
/**
* Adds all portal markers for all current portals
*/
public static void addAllPortalMarkers() {
if (markerSet == null || Stargate.getStargateConfig().isDynmapDisabled()) {
//Remove any existing markers if dynmap has been disabled after startup
if (markerSet != null) {
markerSet.getMarkers().forEach(GenericMarker::deleteMarker);
}
return;
}
markerSet.setHideByDefault(Stargate.getStargateConfig().hideDynmapIcons());
//Remove all existing markers for a clean start
markerSet.getMarkers().forEach(GenericMarker::deleteMarker);
for (Portal portal : PortalRegistry.getAllPortals()) {
addPortalMarker(portal);
}
}
/**
* Adds a portal marker for the given portal
*
* @param portal <p>The portal to add a marker for</p>
*/
public static void addPortalMarker(Portal portal) {
if (markerSet == null || Stargate.getStargateConfig().isDynmapDisabled()) {
return;
}
World world = portal.getWorld();
if (portal.getOptions().isHidden() || world == null) {
return;
}
Location location = portal.getBlockAt(portal.getGate().getLayout().getExit());
Marker marker = markerSet.createMarker(getPortalMarkerId(portal), portal.getName(), world.getName(),
location.getX(), location.getY(), location.getZ(), portalIcon, false);
if (marker == null) {
Stargate.logWarning(String.format(
"""
Unable to create marker for portal
Portal marker id: %s
Portal name: %s
Portal world: %s
Portal location: %s,%s,%s""",
getPortalMarkerId(portal), portal.getName(), world.getName(), location.getX(), location.getY(),
location.getZ()));
return;
}
String networkPrompt;
if (portal.getOptions().isBungee()) {
networkPrompt = "Server";
} else {
networkPrompt = "Network";
}
String markerDescription = String.format("<b>Name:</b> %s<br /><b>%s:</b> %s<br /><b>Destination:</b> " +
"%s<br /><b>Owner:</b> %s<br />", portal.getName(), networkPrompt, portal.getNetwork(),
portal.getDestinationName(), portal.getOwner().getName());
marker.setDescription(markerDescription);
marker.setLabel(portal.getName(), true);
if (portalIcon != null) {
marker.setMarkerIcon(portalIcon);
}
}
/**
* Removes the portal marker for the given portal
*
* @param portal <p>The portal to remove the marker for</p>
*/
public static void removePortalMarker(Portal portal) {
if (markerSet == null || Stargate.getStargateConfig().isDynmapDisabled()) {
return;
}
Marker marker = markerSet.findMarker(getPortalMarkerId(portal));
if (marker != null) {
marker.deleteMarker();
}
}
/**
* Gets the id used for the given portal's marker
*
* @param portal <p>The portal to get a marker id for</p>
* @return <p></p>
*/
private static String getPortalMarkerId(Portal portal) {
return portal.getNetwork() + "-:-" + portal.getName();
}
}

View File

@@ -0,0 +1,239 @@
package net.knarcraft.stargate.config;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.portal.PortalSignDrawer;
import net.knarcraft.stargate.portal.property.gate.Gate;
import net.knarcraft.stargate.utility.PermissionHelper;
import net.md_5.bungee.api.ChatColor;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.ServicesManager;
import java.util.Map;
/**
* The economy config keeps track of economy config values and performs economy actions such as payment for using a gate
*/
public final class EconomyConfig {
private Economy economy = null;
private Plugin vault = null;
private final Map<ConfigOption, Object> configOptions;
/**
* Instantiates a new economy config
*
* @param configOptions <p>The loaded config options to read</p>
*/
public EconomyConfig(Map<ConfigOption, Object> configOptions) {
this.configOptions = configOptions;
try {
String freeColor = (String) configOptions.get(ConfigOption.FREE_GATES_COLOR);
PortalSignDrawer.setFreeColor(ChatColor.of(freeColor.toUpperCase()));
} catch (IllegalArgumentException | NullPointerException ignored) {
PortalSignDrawer.setFreeColor(ChatColor.DARK_GREEN);
}
}
/**
* Gets the cost of using a gate without a specified cost
*
* @return <p>The gate use cost</p>
*/
public int getDefaultUseCost() {
return (Integer) configOptions.get(ConfigOption.USE_COST);
}
/**
* Gets whether economy is enabled
*
* @return <p>Whether economy is enabled</p>
*/
public boolean isEconomyEnabled() {
return (boolean) configOptions.get(ConfigOption.USE_ECONOMY);
}
/**
* Gets the economy object to use for transactions
*
* @return <p>An economy object, or null if economy is disabled or not initialized</p>
*/
public Economy getEconomy() {
return economy;
}
/**
* Gets an instance of the Vault plugin
*
* @return <p>An instance of the Vault plugin, or null if Vault is not loaded</p>
*/
public Plugin getVault() {
return vault;
}
/**
* Disables economy support by clearing relevant values
*/
public void disableEconomy() {
this.economy = null;
this.vault = null;
}
/**
* Gets whether free portals should be marked with a different coloring
*
* @return <p>Whether free portals should be colored</p>
*/
public boolean drawFreePortalsColored() {
return (boolean) configOptions.get(ConfigOption.FREE_GATES_COLORED);
}
/**
* Whether a gate whose destination is a free gate is still charged
*
* <p>If teleporting from a free portal, it's free regardless of destination. If chargeFreeDestination is disabled,
* it's also free to teleport back to the free portal. If chargeFreeDestination is enabled, it's only free to
* teleport back if teleporting from another free portal.</p>
*
* @return <p>Whether to charge for free destinations</p>
*/
public boolean freeIfFreeDestination() {
return !((boolean) configOptions.get(ConfigOption.CHARGE_FREE_DESTINATION));
}
/**
* Gets whether payments should be sent to the owner of the used portal
*
* @return <p>Whether to send payments to the portal owner</p>
*/
public boolean sendPaymentToOwner() {
return (boolean) configOptions.get(ConfigOption.TO_OWNER);
}
/**
* Gets the cost of creating a gate without a specified cost
*
* @return <p>The gate creation cost</p>
*/
public int getDefaultCreateCost() {
return (Integer) configOptions.get(ConfigOption.CREATE_COST);
}
/**
* Gets the cost of destroying a gate without a specified cost
*
* @return <p>The gate destruction cost</p>
*/
public int getDefaultDestroyCost() {
return (Integer) configOptions.get(ConfigOption.DESTROY_COST);
}
/**
* Checks whether the given player can afford the given fee
*
* @param player <p>The player to check</p>
* @param cost <p>The fee to pay</p>
* @return <p>True if the player can afford to pay the fee</p>
*/
public boolean canAffordFee(Player player, int cost) {
return economy.getBalance(player) > cost;
}
/**
* Gets a formatted string for an amount, adding the name of the currency
*
* @param amount <p>The amount to display</p>
* @return <p>A formatted text string describing the amount</p>
*/
public String format(int amount) {
if (isEconomyEnabled()) {
return economy.format(amount);
} else {
return "";
}
}
/**
* Sets up economy by initializing vault and the vault economy provider
*
* @param pluginManager <p>The plugin manager to get plugins from</p>
* @return <p>True if economy was enabled</p>
*/
public boolean setupEconomy(PluginManager pluginManager) {
if (!isEconomyEnabled()) {
return false;
}
//Check if vault is loaded
Plugin vault = pluginManager.getPlugin("Vault");
if (vault != null && vault.isEnabled()) {
ServicesManager servicesManager = Stargate.getInstance().getServer().getServicesManager();
RegisteredServiceProvider<Economy> economyProvider = servicesManager.getRegistration(Economy.class);
if (economyProvider != null) {
economy = economyProvider.getProvider();
this.vault = vault;
return true;
} else {
Stargate.logInfo(Stargate.getString("ecoLoadError"));
}
} else {
Stargate.logInfo(Stargate.getString("vaultLoadError"));
}
configOptions.put(ConfigOption.USE_ECONOMY, false);
return false;
}
/**
* Gets whether to use economy
*
* @return <p>True if the user has turned on economy and economy is available</p>
*/
public boolean useEconomy() {
return isEconomyEnabled() && economy != null;
}
/**
* Gets the cost of creating the given gate
*
* @param player <p>The player creating the gate</p>
* @param gate <p>The gate type used</p>
* @return <p>The cost of creating the gate</p>
*/
public int getCreateCost(Player player, Gate gate) {
if (isFree(player, "create")) {
return 0;
} else {
return gate.getCreateCost();
}
}
/**
* Gets the cost of destroying the given gate
*
* @param player <p>The player creating the gate</p>
* @param gate <p>The gate type used</p>
* @return <p>The cost of destroying the gate</p>
*/
public int getDestroyCost(Player player, Gate gate) {
if (isFree(player, "destroy")) {
return 0;
} else {
return gate.getDestroyCost();
}
}
/**
* Determines if a player can do a gate action for free
*
* @param player <p>The player to check</p>
* @param permissionNode <p>The free.permissionNode necessary to allow free gate {action}</p>
* @return <p></p>
*/
private boolean isFree(Player player, String permissionNode) {
return !useEconomy() || PermissionHelper.hasPermission(player, "stargate.free." + permissionNode);
}
}

View File

@@ -0,0 +1,257 @@
package net.knarcraft.stargate.config;
import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.knarlib.util.FileHelper;
import net.knarcraft.stargate.Stargate;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* This class is responsible for loading all strings which are translated into several languages
*/
public final class LanguageLoader {
private final String languageFolder;
private final Map<String, String> loadedBackupStrings;
private String chosenLanguage;
private Map<String, String> loadedStringTranslations;
/**
* Instantiates a new language loader
*
* <p>This will only load the backup language. Set the chosen language and reload afterwards.</p>
*
* @param languageFolder <p>The folder containing the language files</p>
*/
public LanguageLoader(String languageFolder) {
this.languageFolder = languageFolder;
File testFile = new File(languageFolder, "en.txt");
if (!testFile.exists()) {
if (testFile.getParentFile().mkdirs()) {
Stargate.debug("LanguageLoader", "Created language folder");
}
}
//Load english as backup language in case the chosen language is missing newly added text strings
InputStream inputStream = FileHelper.getInputStreamForInternalFile("/lang/en.txt");
if (inputStream != null) {
loadedBackupStrings = load("en", inputStream);
} else {
loadedBackupStrings = null;
Stargate.getConsoleLogger().severe("[stargate] Error loading backup language. " +
"There may be missing text in-game");
}
}
/**
* Reloads languages from the files on disk
*/
public void reload() {
//Extracts/Updates the language as needed
updateLanguage(chosenLanguage);
loadedStringTranslations = load(chosenLanguage);
}
/**
* Gets the string to display given its name/key
*
* @param name <p>The name/key of the string to display</p>
* @return <p>The string in the user's preferred language</p>
*/
public String getString(String name) {
String value = null;
if (loadedStringTranslations != null) {
value = loadedStringTranslations.get(name);
}
if (value == null) {
value = getBackupString(name);
}
return value;
}
/**
* Gets the string to display given its name/key
*
* @param name <p>The name/key of the string to display</p>
* @return <p>The string in the backup language (English)</p>
*/
public String getBackupString(String name) {
String value = null;
if (loadedBackupStrings != null) {
value = loadedBackupStrings.get(name);
}
if (value == null) {
return "";
}
return value;
}
/**
* Sets the chosen plugin language
*
* @param chosenLanguage <p>The new plugin language</p>
*/
public void setChosenLanguage(String chosenLanguage) {
this.chosenLanguage = chosenLanguage;
}
/**
* Updates files in the plugin directory with contents from the compiled .jar
*
* @param language <p>The language to update</p>
*/
private void updateLanguage(String language) {
Map<String, String> currentLanguageValues = load(language);
InputStream inputStream = getClass().getResourceAsStream("/lang/" + language + ".txt");
if (inputStream == null) {
Stargate.logInfo(String.format("The language %s is not available. Falling back to english, You can add a " +
"custom language by creating a new text file in the lang directory.", language));
Stargate.debug("LanguageLoader::updateLanguage", String.format("Unable to load /lang/%s.txt", language));
return;
}
try {
readChangedLanguageStrings(inputStream, language, currentLanguageValues);
} catch (IOException ex) {
ex.printStackTrace();
}
}
/**
* Reads language strings
*
* @param inputStream <p>The input stream to read from</p>
* @param language <p>The selected language</p>
* @param currentLanguageValues <p>The current values of the loaded/processed language</p>
* @throws IOException <p>if unable to read a language file</p>
*/
private void readChangedLanguageStrings(InputStream inputStream, String language, Map<String,
String> currentLanguageValues) throws IOException {
//Get language values
BufferedReader bufferedReader = FileHelper.getBufferedReaderFromInputStream(inputStream);
Map<String, String> internalLanguageValues = FileHelper.readKeyValuePairs(bufferedReader, "=",
ColorConversion.NORMAL);
//If currentLanguageValues is null; the chosen language has not been used before
if (currentLanguageValues == null) {
updateLanguageFile(language, internalLanguageValues, null);
Stargate.logInfo(String.format("Language (%s) has been loaded", language));
return;
}
//If a key is not found in the language file, add the one in the internal file. Must update the external file
if (!internalLanguageValues.keySet().equals(currentLanguageValues.keySet())) {
Map<String, String> newLanguageValues = new HashMap<>();
boolean updateNecessary = false;
for (String key : internalLanguageValues.keySet()) {
if (currentLanguageValues.get(key) == null) {
newLanguageValues.put(key, internalLanguageValues.get(key));
//Found at least one value in the internal file not in the external file. Need to update
updateNecessary = true;
} else {
newLanguageValues.put(key, currentLanguageValues.get(key));
currentLanguageValues.remove(key);
}
}
//Update the file itself
if (updateNecessary) {
updateLanguageFile(language, newLanguageValues, currentLanguageValues);
Stargate.logInfo(String.format("Your language file (%s.txt) has been updated", language));
}
}
}
/**
* Updates the language file for a given language
*
* @param language <p>The language to update</p>
* @param languageStrings <p>The updated language strings</p>
* @param customLanguageStrings <p>Any custom language strings not recognized</p>
* @throws IOException <p>If unable to write to the language file</p>
*/
private void updateLanguageFile(String language, Map<String, String> languageStrings,
Map<String, String> customLanguageStrings) throws IOException {
BufferedWriter bufferedWriter = FileHelper.getBufferedWriterFromString(languageFolder + language + ".txt");
//Output normal Language data
for (String key : languageStrings.keySet()) {
bufferedWriter.write(key + "=" + languageStrings.get(key));
bufferedWriter.newLine();
}
bufferedWriter.newLine();
//Output any custom language strings the user had
if (customLanguageStrings != null) {
for (String key : customLanguageStrings.keySet()) {
bufferedWriter.write(key + "=" + customLanguageStrings.get(key));
bufferedWriter.newLine();
}
}
bufferedWriter.close();
}
/**
* Loads the given language
*
* @param lang <p>The language to load</p>
* @return <p>A mapping between loaded string indexes and the strings to display</p>
*/
private Map<String, String> load(String lang) {
return load(lang, null);
}
/**
* Loads the given language
*
* @param lang <p>The language to load</p>
* @param inputStream <p>An optional input stream to use. Defaults to using a file input stream</p>
* @return <p>A mapping between loaded string indexes and the strings to display</p>
*/
private Map<String, String> load(String lang, InputStream inputStream) {
Map<String, String> strings;
BufferedReader bufferedReader;
try {
if (inputStream == null) {
bufferedReader = FileHelper.getBufferedReaderFromString(languageFolder + lang + ".txt");
} else {
bufferedReader = FileHelper.getBufferedReaderFromInputStream(inputStream);
}
strings = FileHelper.readKeyValuePairs(bufferedReader, "=", ColorConversion.NORMAL);
} catch (Exception e) {
if (Stargate.getStargateConfig().isDebuggingEnabled()) {
Stargate.getConsoleLogger().info("[Stargate] Unable to load language " + lang);
}
return null;
}
return strings;
}
/**
* Prints debug output to the console for checking loaded language strings/translations
*/
public void debug() {
if (loadedStringTranslations != null) {
Set<String> keys = loadedStringTranslations.keySet();
for (String key : keys) {
Stargate.debug("LanguageLoader::Debug::loadedStringTranslations", key + " => " +
loadedStringTranslations.get(key));
}
}
if (loadedBackupStrings == null) {
return;
}
Set<String> keys = loadedBackupStrings.keySet();
for (String key : keys) {
Stargate.debug("LanguageLoader::Debug::loadedBackupStrings", key + " => " +
loadedBackupStrings.get(key));
}
}
}

View File

@@ -0,0 +1,61 @@
package net.knarcraft.stargate.config;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.command.CommandSender;
/**
* The message sender is responsible sending messages to players with correct coloring and formatting
*/
public final class MessageSender {
private final LanguageLoader languageLoader;
/**
* Instantiates a new message sender
*
* @param languageLoader <p>The language loader to get translated strings from</p>
*/
public MessageSender(LanguageLoader languageLoader) {
this.languageLoader = languageLoader;
}
/**
* Sends an error message to a player
*
* @param player <p>The player to send the message to</p>
* @param message <p>The message to send</p>
*/
public void sendErrorMessage(CommandSender player, String message) {
sendMessage(player, message, true);
}
/**
* Sends a success message to a player
*
* @param player <p>The player to send the message to</p>
* @param message <p>The message to send</p>
*/
public void sendSuccessMessage(CommandSender player, String message) {
sendMessage(player, message, false);
}
/**
* Sends a message to a player
*
* @param sender <p>The player to send the message to</p>
* @param message <p>The message to send</p>
* @param error <p>Whether the message sent is an error</p>
*/
private void sendMessage(CommandSender sender, String message, boolean error) {
if (message.isEmpty()) {
return;
}
message = ChatColor.translateAlternateColorCodes('&', message);
if (error) {
sender.sendMessage(ChatColor.RED + languageLoader.getString("prefix") + ChatColor.WHITE + message);
} else {
sender.sendMessage(ChatColor.GREEN + languageLoader.getString("prefix") + ChatColor.WHITE + message);
}
}
}

View File

@@ -0,0 +1,33 @@
package net.knarcraft.stargate.config;
/**
* An enum defining the different data types an option can have
*/
public enum OptionDataType {
/**
* The data type if the option is a String
*/
STRING,
/**
* The data type if the option is a Boolean
*/
BOOLEAN,
/**
* The data type if the option is a string list
*/
STRING_LIST,
/**
* The data type if the option is an Integer
*/
INTEGER,
/**
* The data type if the option is a double
*/
DOUBLE
}

View File

@@ -0,0 +1,553 @@
package net.knarcraft.stargate.config;
import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.knarlib.util.FileHelper;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.BlockChangeRequest;
import net.knarcraft.stargate.listener.BungeeCordListener;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalHandler;
import net.knarcraft.stargate.portal.PortalRegistry;
import net.knarcraft.stargate.portal.property.gate.GateHandler;
import net.knarcraft.stargate.thread.BlockChangeThread;
import net.knarcraft.stargate.utility.PortalFileHelper;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.messaging.Messenger;
import org.dynmap.DynmapAPI;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Logger;
/**
* The stargate config is responsible for keeping track of all configuration values
*/
public final class StargateConfig {
private final Queue<Portal> activePortalsQueue = new ConcurrentLinkedQueue<>();
private final Queue<Portal> openPortalsQueue = new ConcurrentLinkedQueue<>();
private final HashSet<String> managedWorlds = new HashSet<>();
private StargateGateConfig stargateGateConfig;
private MessageSender messageSender;
private final LanguageLoader languageLoader;
private EconomyConfig economyConfig;
private final Logger logger;
private final String dataFolderPath;
private String gateFolder;
private String portalFolder;
private String languageName = "en";
private final Map<ConfigOption, Object> configOptions;
/**
* Instantiates a new stargate config
*
* @param logger <p>The logger to use for logging errors</p>
*/
public StargateConfig(Logger logger) {
this.logger = logger;
configOptions = new HashMap<>();
dataFolderPath = Stargate.getInstance().getDataFolder().getPath().replaceAll("\\\\", "/");
portalFolder = dataFolderPath + "/portals/";
gateFolder = dataFolderPath + "/gates/";
languageLoader = new LanguageLoader(dataFolderPath + "/lang/");
}
/**
* Gets a direct reference to the config option map
*
* <p>This reference can be used to alter the value of config options. Values should only be altered after it's
* been verified that the value is valid.</p>
*
* @return <p>A reference to the config options map</p>
*/
public Map<ConfigOption, Object> getConfigOptionsReference() {
return configOptions;
}
/**
* Finish the config setup by loading languages, gates and portals, and loading economy if vault is loaded
*/
public void finishSetup() {
this.loadConfig();
//Enable the required channels for Bungee support
if (stargateGateConfig.enableBungee()) {
startStopBungeeListener(true);
}
//Set the chosen language and reload the language loader
languageLoader.setChosenLanguage(languageName);
languageLoader.reload();
messageSender = new MessageSender(languageLoader);
if (isDebuggingEnabled()) {
languageLoader.debug();
}
this.createMissingFolders();
this.loadGates();
this.loadAllPortals();
//Set up vault economy if vault has been loaded
setupVaultEconomy();
DynmapAPI dynmapAPI = (DynmapAPI) Bukkit.getPluginManager().getPlugin("dynmap");
if (dynmapAPI != null) {
DynmapManager.initialize(dynmapAPI);
DynmapManager.addAllPortalMarkers();
}
}
/**
* Gets a copy of all loaded config options with its values
*
* @return <p>The loaded config options</p>
*/
public Map<ConfigOption, Object> getConfigOptions() {
return new HashMap<>(configOptions);
}
/**
* Gets the queue of open portals
*
* <p>The open portals queue is used to close open portals after some time has passed</p>
*
* @return <p>The open portals queue</p>
*/
public Queue<Portal> getOpenPortalsQueue() {
return openPortalsQueue;
}
/**
* Gets the queue of active portals
*
* <p>The active portals queue is used to de-activate portals after some time has passed</p>
*
* @return <p>The active portals queue</p>
*/
public Queue<Portal> getActivePortalsQueue() {
return activePortalsQueue;
}
/**
* Gets whether debugging is enabled
*
* @return <p>Whether debugging is enabled</p>
*/
public boolean isDebuggingEnabled() {
return (boolean) configOptions.get(ConfigOption.DEBUG);
}
/**
* Gets whether permission debugging is enabled
*
* @return <p>Whether permission debugging is enabled</p>
*/
public boolean isPermissionDebuggingEnabled() {
return (boolean) configOptions.get(ConfigOption.PERMISSION_DEBUG);
}
/**
* Gets whether Dynmap integration is disabled
*
* @return <p>Whether Dynmap integration is disabled</p>
*/
public boolean isDynmapDisabled() {
return !((boolean) configOptions.get(ConfigOption.ENABLE_DYNMAP));
}
/**
* Gets whether Dynmap icons should be hidden by default
*
* @return <p>Whether Dynmap icons should be hidden by default</p>
*/
public boolean hideDynmapIcons() {
return (boolean) configOptions.get(ConfigOption.DYNMAP_ICONS_DEFAULT_HIDDEN);
}
/**
* Gets the object containing economy config values
*
* @return <p>The object containing economy config values</p>
*/
public EconomyConfig getEconomyConfig() {
return this.economyConfig;
}
/**
* Reloads all portals and files
*
* @param sender <p>The sender of the reload request</p>
*/
public void reload(CommandSender sender) {
//Unload all saved data
unload();
//Perform all block change requests to prevent mismatch if a gate's open-material changes. Changing the
// closed-material still requires a restart.
BlockChangeRequest firstElement = Stargate.getBlockChangeRequestQueue().peek();
while (firstElement != null) {
BlockChangeThread.pollQueue();
firstElement = Stargate.getBlockChangeRequestQueue().peek();
}
//Store the old enable bungee state in case it changes
boolean oldEnableBungee = stargateGateConfig.enableBungee();
//Load all data
load();
//Enable or disable the required channels for Bungee support
if (oldEnableBungee != stargateGateConfig.enableBungee()) {
startStopBungeeListener(stargateGateConfig.enableBungee());
}
//Reload portal markers
DynmapManager.addAllPortalMarkers();
messageSender.sendErrorMessage(sender, languageLoader.getString("reloaded"));
}
/**
* Un-loads all loaded data
*/
private void unload() {
//De-activate, close and unload all loaded portals
unloadAllPortals();
//Clear all loaded gates
GateHandler.clearGates();
}
/**
* Un-loads all loaded portals
*/
public void unloadAllPortals() {
//De-activate all currently active portals
for (Portal activePortal : activePortalsQueue) {
activePortal.getPortalActivator().deactivate();
}
//Force all portals to close
closeAllOpenPortals();
PortalHandler.closeAllPortals();
//Clear queues and lists
activePortalsQueue.clear();
openPortalsQueue.clear();
managedWorlds.clear();
//Clear all loaded portals
PortalRegistry.clearPortals();
}
/**
* Clears the set of managed worlds
*/
public void clearManagedWorlds() {
managedWorlds.clear();
}
/**
* Gets a copy of the set of managed worlds
*
* @return <p>The managed worlds</p>
*/
public Set<String> getManagedWorlds() {
return new HashSet<>(managedWorlds);
}
/**
* Adds a world to the managed worlds
*
* @param worldName <p>The name of the world to manage</p>
*/
public void addManagedWorld(String worldName) {
managedWorlds.add(worldName);
}
/**
* Removes a world from the managed worlds
*
* @param worldName <p>The name of the world to stop managing</p>
*/
public void removeManagedWorld(String worldName) {
managedWorlds.remove(worldName);
}
/**
* Loads all necessary data
*/
private void load() {
//Load the config from disk
loadConfig();
//Load all gates
loadGates();
//Load all portals
loadAllPortals();
//Update the language loader in case the loaded language changed
languageLoader.setChosenLanguage(languageName);
languageLoader.reload();
if (isDebuggingEnabled()) {
languageLoader.debug();
}
//Load Economy support if enabled/clear if disabled
reloadEconomy();
}
/**
* Starts the listener for listening to BungeeCord messages
*/
public void startStopBungeeListener(boolean start) {
Messenger messenger = Bukkit.getMessenger();
String bungeeChannel = "BungeeCord";
if (start) {
messenger.registerOutgoingPluginChannel(Stargate.getInstance(), bungeeChannel);
messenger.registerIncomingPluginChannel(Stargate.getInstance(), bungeeChannel, new BungeeCordListener());
} else {
messenger.unregisterIncomingPluginChannel(Stargate.getInstance(), bungeeChannel);
messenger.unregisterOutgoingPluginChannel(Stargate.getInstance(), bungeeChannel);
}
}
/**
* Reloads economy by enabling or disabling it as necessary
*/
public void reloadEconomy() {
EconomyConfig economyConfig = getEconomyConfig();
if (economyConfig.isEconomyEnabled() && economyConfig.getEconomy() == null) {
setupVaultEconomy();
} else if (!economyConfig.isEconomyEnabled()) {
economyConfig.disableEconomy();
}
}
/**
* Forces all open portals to close
*/
public void closeAllOpenPortals() {
for (Portal openPortal : openPortalsQueue) {
openPortal.getPortalOpener().closePortal(false);
}
}
/**
* Gets whether admins should be alerted about new plugin updates
*
* @return <p>Whether admins should be alerted about new updates</p>
*/
public boolean alertAdminsAboutUpdates() {
return (boolean) configOptions.get(ConfigOption.ADMIN_UPDATE_ALERT);
}
/**
* Loads all config values
*/
public void loadConfig() {
Stargate.getInstance().reloadConfig();
FileConfiguration newConfig = Stargate.getInstance().getConfig();
boolean isMigrating = false;
if (newConfig.getString("lang") != null || newConfig.getString("economy.freeGatesGreen") != null) {
migrateConfig(newConfig);
isMigrating = true;
}
//Copy missing default values if any values are missing
newConfig.options().copyDefaults(true);
//Load all options
for (ConfigOption option : ConfigOption.values()) {
Object optionValue;
String configNode = option.getConfigNode();
//Load the option using its correct data type
switch (option.getDataType()) {
case STRING_LIST -> optionValue = newConfig.getStringList(configNode);
case STRING -> {
String value = newConfig.getString(configNode);
optionValue = value != null ? value.trim() : "";
}
case BOOLEAN -> optionValue = newConfig.getBoolean(configNode);
case INTEGER -> optionValue = newConfig.getInt(configNode);
case DOUBLE -> optionValue = newConfig.getDouble(configNode);
default -> throw new IllegalArgumentException("Invalid config data type encountered");
}
configOptions.put(option, optionValue);
}
//Get the language name from the config
languageName = (String) configOptions.get(ConfigOption.LANGUAGE);
//Get important folders from the config
portalFolder = (String) configOptions.get(ConfigOption.PORTAL_FOLDER);
gateFolder = (String) configOptions.get(ConfigOption.GATE_FOLDER);
//If users have an outdated config, assume they also need to update their default gates
if (isMigrating) {
GateHandler.writeDefaultGatesToFolder(gateFolder);
}
//Load all gate config values
stargateGateConfig = new StargateGateConfig(configOptions);
//Load all economy config values
economyConfig = new EconomyConfig(configOptions);
Stargate.getInstance().saveConfig();
}
/**
* Gets the object containing configuration values regarding gates
*
* @return <p>Gets the gate config</p>
*/
public StargateGateConfig getStargateGateConfig() {
return stargateGateConfig;
}
/**
* Loads all available gates
*/
public void loadGates() {
GateHandler.loadGates(gateFolder);
Stargate.logInfo(String.format("Loaded %s gate layouts", GateHandler.getGateCount()));
}
/**
* Changes all configuration values from the old name to the new name
*
* @param newConfig <p>The config to read from and write to</p>
*/
private void migrateConfig(FileConfiguration newConfig) {
//Save the old config just in case something goes wrong
try {
newConfig.save(dataFolderPath + "/config.yml.old");
} catch (IOException e) {
Stargate.debug("Stargate::migrateConfig", "Unable to save old backup and do migration");
e.printStackTrace();
return;
}
//Read all available config migrations
Map<String, String> migrationFields;
try {
migrationFields = FileHelper.readKeyValuePairs(FileHelper.getBufferedReaderFromInputStream(
FileHelper.getInputStreamForInternalFile("/config-migrations.txt")), "=",
ColorConversion.NORMAL);
} catch (IOException e) {
Stargate.debug("Stargate::migrateConfig", "Unable to load config migration file");
e.printStackTrace();
return;
}
//Replace old config names with the new ones
for (String key : migrationFields.keySet()) {
if (newConfig.contains(key)) {
String newPath = migrationFields.get(key);
Object oldValue = newConfig.get(key);
if (!newPath.trim().isEmpty()) {
newConfig.set(newPath, oldValue);
}
newConfig.set(key, null);
}
}
}
/**
* Loads economy from Vault
*/
private void setupVaultEconomy() {
EconomyConfig economyConfig = getEconomyConfig();
if (economyConfig.setupEconomy(Stargate.getPluginManager()) && economyConfig.getEconomy() != null) {
String vaultVersion = economyConfig.getVault().getDescription().getVersion();
Stargate.logInfo(Stargate.replaceVars(Stargate.getString("vaultLoaded"), "%version%", vaultVersion));
}
}
/**
* Loads all portals in all un-managed worlds
*/
public void loadAllPortals() {
for (World world : Stargate.getInstance().getServer().getWorlds()) {
if (!managedWorlds.contains(world.getName())) {
PortalFileHelper.loadAllPortals(world);
managedWorlds.add(world.getName());
}
}
}
/**
* Creates missing folders
*/
private void createMissingFolders() {
File newPortalDir = new File(portalFolder);
if (!newPortalDir.exists()) {
if (!newPortalDir.mkdirs()) {
logger.severe("Unable to create portal directory");
}
}
File newFile = new File(portalFolder, Stargate.getInstance().getServer().getWorlds().get(0).getName() +
".db");
if (!newFile.exists() && !newFile.getParentFile().exists()) {
if (!newFile.getParentFile().mkdirs()) {
logger.severe("Unable to create portal database folder: " + newFile.getParentFile().getPath());
}
}
}
/**
* Gets the folder all portals are stored in
*
* @return <p>The portal folder</p>
*/
public String getPortalFolder() {
return portalFolder;
}
/**
* Gets the folder storing gate files
*
* <p>The returned String path is the full path to the folder</p>
*
* @return <p>The folder storing gate files</p>
*/
public String getGateFolder() {
return gateFolder;
}
/**
* Gets the sender for sending messages to players
*
* @return <p>The sender for sending messages to players</p>
*/
public MessageSender getMessageSender() {
return messageSender;
}
/**
* Gets the language loader containing translated strings
*
* @return <p>The language loader</p>
*/
public LanguageLoader getLanguageLoader() {
return languageLoader;
}
}

View File

@@ -0,0 +1,328 @@
package net.knarcraft.stargate.config;
import net.knarcraft.knarlib.util.ColorHelper;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.portal.PortalSignDrawer;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Color;
import org.bukkit.Material;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* The Stargate gate config keeps track of all global config values related to gates
*/
public final class StargateGateConfig {
private static final int activeTime = 10;
private static final int openTime = 10;
private final Map<ConfigOption, Object> configOptions;
/**
* Instantiates a new stargate config
*
* @param configOptions <p>The loaded config options to use</p>
*/
public StargateGateConfig(Map<ConfigOption, Object> configOptions) {
this.configOptions = configOptions;
loadGateConfig();
}
/**
* Gets the amount of seconds a portal should be open before automatically closing
*
* @return <p>The open time of a gate</p>
*/
public int getOpenTime() {
return openTime;
}
/**
* Gets the amount of seconds a portal should be active before automatically deactivating
*
* @return <p>The active time of a gate</p>
*/
public int getActiveTime() {
return activeTime;
}
/**
* Gets the maximum number of gates allowed on each network
*
* @return <p>Maximum number of gates for each network</p>
*/
public int maxGatesEachNetwork() {
return (int) configOptions.get(ConfigOption.MAX_GATES_EACH_NETWORK);
}
/**
* Gets whether a portal's lastly used destination should be remembered
*
* @return <p>Whether a portal's lastly used destination should be remembered</p>
*/
public boolean rememberDestination() {
return (boolean) configOptions.get(ConfigOption.REMEMBER_DESTINATION);
}
/**
* Gets whether vehicle teleportation should be handled in addition to player teleportation
*
* @return <p>Whether vehicle teleportation should be handled</p>
*/
public boolean handleVehicles() {
return (boolean) configOptions.get(ConfigOption.HANDLE_VEHICLES);
}
/**
* Gets whether vehicles with no passengers should be handled
*
* <p>The handle vehicles option overrides this option if disabled. This option allows empty passenger
* minecarts/boats, but also chest/tnt/hopper/furnace minecarts to teleport through stargates.</p>
*
* @return <p>Whether vehicles without passengers should be handled</p>
*/
public boolean handleEmptyVehicles() {
return (boolean) configOptions.get(ConfigOption.HANDLE_EMPTY_VEHICLES);
}
/**
* Gets whether vehicles containing creatures should be handled
*
* <p>The handle vehicles option overrides this option if disabled. This option allows creatures (pigs, pandas,
* zombies, etc.) to teleport through stargates if in a vehicle.</p>
*
* @return <p>Whether vehicles with creatures should be handled</p>
*/
public boolean handleCreatureTransportation() {
return (boolean) configOptions.get(ConfigOption.HANDLE_CREATURE_TRANSPORTATION);
}
/**
* Gets whether vehicles containing a creature, but not a player should be handled
*
* <p>The handle vehicles option, and the handle creature transportation option, override this option if disabled.
* This option allows creatures (pigs, pandas, zombies, etc.) to teleport through stargates if in a vehicle, even
* if no player is in the vehicle.
* As it is not possible to check if a creature is allowed through a stargate, they will be able to go through
* regardless of whether the initiating player is allowed to enter the stargate. Enabling this is necessary to
* teleport creatures using minecarts, but only handleCreatureTransportation is required to teleport creatures
* using a boat manned by a player.</p>
*
* @return <p>Whether non-empty vehicles without a player should be handled</p>
*/
public boolean handleNonPlayerVehicles() {
return (boolean) configOptions.get(ConfigOption.HANDLE_NON_PLAYER_VEHICLES);
}
/**
* Gets whether leashed creatures should be teleported with a teleporting player
*
* @return <p>Whether leashed creatures should be handled</p>
*/
public boolean handleLeashedCreatures() {
return (boolean) configOptions.get(ConfigOption.HANDLE_LEASHED_CREATURES);
}
/**
* Gets whether the CraftBook vehicle removal fix is enabled
*
* <p>If enabled, minecarts and boats should be re-created instead of teleported.</p>
*
* @return <p>True if the CraftBook vehicle removal fix is enabled</p>
*/
public boolean enableCraftBookRemoveOnEjectFix() {
return (boolean) configOptions.get(ConfigOption.ENABLE_CRAFT_BOOK_REMOVE_ON_EJECT_FIX);
}
/**
* Gets the delay to use before adding a player as passenger of a teleported vehicle
*
* @return <p>The delay to use before adding a player as passenger of a teleported vehicle</p>
*/
public int waitForPlayerAfterTeleportDelay() {
if ((int) configOptions.get(ConfigOption.WAIT_FOR_PLAYER_AFTER_TELEPORT_DELAY) < 2) {
configOptions.put(ConfigOption.WAIT_FOR_PLAYER_AFTER_TELEPORT_DELAY, 6);
}
return (int) configOptions.get(ConfigOption.WAIT_FOR_PLAYER_AFTER_TELEPORT_DELAY);
}
/**
* Gets whether the list of destinations within a network should be sorted
*
* @return <p>Whether network destinations should be sorted</p>
*/
public boolean sortNetworkDestinations() {
return (boolean) configOptions.get(ConfigOption.SORT_NETWORK_DESTINATIONS);
}
/**
* Gets whether portal entrances should be protected from block breaking
*
* @return <p>Whether portal entrances should be protected</p>
*/
public boolean protectEntrance() {
return (boolean) configOptions.get(ConfigOption.PROTECT_ENTRANCE);
}
/**
* Gets whether BungeeCord support is enabled
*
* @return <p>Whether bungee support is enabled</p>
*/
public boolean enableBungee() {
return (boolean) configOptions.get(ConfigOption.ENABLE_BUNGEE);
}
/**
* Gets whether all portals' integrity has to be verified on startup and reload
*
* @return <p>Whether portals need to be verified</p>
*/
public boolean verifyPortals() {
return (boolean) configOptions.get(ConfigOption.VERIFY_PORTALS);
}
/**
* Gets whether portals should be destroyed by nearby explosions
*
* @return <p>Whether portals should be destroyed by explosions</p>
*/
public boolean destroyedByExplosion() {
return (boolean) configOptions.get(ConfigOption.DESTROYED_BY_EXPLOSION);
}
/**
* Gets the default portal network to use if no other network is given
*
* @return <p>The default portal network</p>
*/
public String getDefaultPortalNetwork() {
return (String) configOptions.get(ConfigOption.DEFAULT_GATE_NETWORK);
}
/**
* Gets the exit velocity of players using stargates, relative to the entry velocity
*
* @return <p>The relative exit velocity</p>
*/
public double getExitVelocity() {
return (double) configOptions.get(ConfigOption.EXIT_VELOCITY);
}
/**
* Loads all config values related to gates
*/
private void loadGateConfig() {
//Load the sign colors
String mainSignColor = (String) configOptions.get(ConfigOption.MAIN_SIGN_COLOR);
String highlightSignColor = (String) configOptions.get(ConfigOption.HIGHLIGHT_SIGN_COLOR);
loadPerSignColor(mainSignColor, highlightSignColor);
loadPerSignColors();
}
/**
* Loads the per-sign colors specified in the config file
*/
public void loadPerSignColors() {
List<?> perSignColors = (List<?>) configOptions.get(ConfigOption.PER_SIGN_COLORS);
ChatColor[] defaultColors = new ChatColor[]{PortalSignDrawer.getMainColor(), PortalSignDrawer.getHighlightColor()};
List<Map<Material, ChatColor>> colorMaps = new ArrayList<>();
colorMaps.add(new HashMap<>());
colorMaps.add(new HashMap<>());
for (Object signColorSpecification : perSignColors) {
parsePerSignColors(signColorSpecification, defaultColors, colorMaps);
}
PortalSignDrawer.setPerSignMainColors(colorMaps.get(0));
PortalSignDrawer.setPerSignHighlightColors(colorMaps.get(1));
}
/**
* Parses a per-sign color specification object and stores the result
*
* @param signColorSpecification <p>The sign color specification to parse</p>
* @param defaultColors <p>The specified default colors</p>
* @param colorMaps <p>The list of color maps to save the resulting colors to</p>
*/
private void parsePerSignColors(Object signColorSpecification, ChatColor[] defaultColors,
List<Map<Material, ChatColor>> colorMaps) {
String[] specificationData = String.valueOf(signColorSpecification).split(":");
Material[] signMaterials = new Material[]{Material.matchMaterial(specificationData[0] + "_SIGN"),
Material.matchMaterial(specificationData[0] + "_WALL_SIGN")};
if (specificationData.length != 2) {
Stargate.logWarning("You have an invalid per-sign line in your config.yml file. Please fix it!");
return;
}
String[] colors = specificationData[1].split(",");
if (colors.length != 2) {
Stargate.logWarning("You have an invalid per-sign line in your config.yml file. Please fix it!");
return;
}
for (int colorIndex = 0; colorIndex < 2; colorIndex++) {
if (colors[colorIndex].equalsIgnoreCase("default")) {
continue;
}
loadPerSignColor(colors, colorIndex, defaultColors, signMaterials, colorMaps);
}
}
/**
* Loads a per-sign color
*
* @param colors <p>The colors specified in the config file</p>
* @param colorIndex <p>The index of the color to load</p>
* @param defaultColors <p>The specified default colors</p>
* @param signMaterials <p>The materials to load this color for</p>
* @param colorMaps <p>The list of color maps to save the resulting color to</p>
*/
private void loadPerSignColor(String[] colors, int colorIndex, ChatColor[] defaultColors, Material[] signMaterials,
List<Map<Material, ChatColor>> colorMaps) {
ChatColor parsedColor;
if (colors[colorIndex].equalsIgnoreCase("inverted")) {
//Convert from ChatColor to awt.Color to Bukkit.Color then invert and convert to ChatColor
java.awt.Color color = defaultColors[colorIndex].getColor();
parsedColor = ColorHelper.fromColor(ColorHelper.invert(Color.fromRGB(color.getRed(), color.getGreen(),
color.getBlue())));
} else {
try {
parsedColor = ChatColor.of(colors[colorIndex]);
} catch (IllegalArgumentException | NullPointerException exception) {
Stargate.logWarning("You have specified an invalid per-sign color in your config.yml. Custom color for \"" +
signMaterials[0] + "\" disabled");
return;
}
}
if (parsedColor != null) {
for (Material signMaterial : signMaterials) {
colorMaps.get(colorIndex).put(signMaterial, parsedColor);
}
}
}
/**
* Loads the correct sign color given a sign color string
*
* @param mainSignColor <p>A string representing the main sign color</p>
*/
private void loadPerSignColor(String mainSignColor, String highlightSignColor) {
try {
PortalSignDrawer.setMainColor(ChatColor.of(mainSignColor.toUpperCase()));
} catch (IllegalArgumentException | NullPointerException exception) {
Stargate.logWarning("You have specified an invalid main sign color in your config.yml. Defaulting to BLACK");
PortalSignDrawer.setMainColor(ChatColor.BLACK);
}
try {
PortalSignDrawer.setHighlightColor(ChatColor.of(highlightSignColor.toUpperCase()));
} catch (IllegalArgumentException | NullPointerException exception) {
Stargate.logWarning("You have specified an invalid highlighting sign color in your config.yml. Defaulting to WHITE");
PortalSignDrawer.setHighlightColor(ChatColor.WHITE);
}
}
}

View File

@@ -0,0 +1,55 @@
package net.knarcraft.stargate.container;
import org.bukkit.Axis;
import org.bukkit.Material;
/**
* Represents a request for changing a block into another material
*/
public class BlockChangeRequest {
private final BlockLocation blockLocation;
private final Material newMaterial;
private final Axis newAxis;
/**
* Instantiates a new block change request
*
* @param blockLocation <p>The location of the block to change</p>
* @param material <p>The new material to change the block to</p>
* @param axis <p>The new axis to orient the block along</p>
*/
public BlockChangeRequest(BlockLocation blockLocation, Material material, Axis axis) {
this.blockLocation = blockLocation;
newMaterial = material;
newAxis = axis;
}
/**
* Gets the location of the block to change
*
* @return <p>The location of the block</p>
*/
public BlockLocation getBlockLocation() {
return blockLocation;
}
/**
* Gets the material to change the block into
*
* @return <p>The material to change the block into</p>
*/
public Material getMaterial() {
return newMaterial;
}
/**
* Gets the axis to orient the block along
*
* @return <p>The axis to orient the block along</p>
*/
public Axis getAxis() {
return newAxis;
}
}

View File

@@ -0,0 +1,227 @@
package net.knarcraft.stargate.container;
import net.knarcraft.stargate.utility.DirectionHelper;
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.type.Sign;
import org.bukkit.util.Vector;
/**
* This class represents a block location
*
* <p>The BlockLocation class is basically a Location with some extra functionality.
* Warning: Because of differences in the equals methods between Location and BlockLocation, a BlockLocation which
* equals another BlockLocation does not necessarily equal the same BlockLocation if treated as a Location.</p>
*/
public class BlockLocation extends Location {
private BlockLocation parent = null;
/**
* Creates a new block location
*
* @param world <p>The world the block exists in</p>
* @param x <p>The x coordinate of the block</p>
* @param y <p>The y coordinate of the block</p>
* @param z <p>The z coordinate of the block</p>
*/
public BlockLocation(World world, int x, int y, int z) {
super(world, x, y, z);
}
/**
* Creates a block location from a block
*
* @param block <p>The block to get the location of</p>
*/
public BlockLocation(Block block) {
super(block.getWorld(), block.getX(), block.getY(), block.getZ());
}
/**
* Gets a block location from a string
*
* @param world <p>The world the block exists in</p>
* @param string <p>A comma separated list of x, y and z coordinates as integers</p>
*/
public BlockLocation(World world, String string) {
super(world, Integer.parseInt(string.split(",")[0]), Integer.parseInt(string.split(",")[1]),
Integer.parseInt(string.split(",")[2]));
}
/**
* Creates a new block location in a relative position to this block location
*
* @param x <p>The number of blocks to move in the x-direction</p>
* @param y <p>The number of blocks to move in the y-direction</p>
* @param z <p>The number of blocks to move in the z-direction</p>
* @return <p>A new block location</p>
*/
public BlockLocation makeRelativeBlockLocation(int x, int y, int z) {
return (BlockLocation) this.clone().add(x, y, z);
}
/**
* Creates a location in a relative position to this block location
*
* @param x <p>The number of blocks to move in the x-direction</p>
* @param y <p>The number of blocks to move in the y-direction</p>
* @param z <p>The z position relative to this block's position</p>
* @param yaw <p>The number of blocks to move in the z-direction</p>
* @return <p>A new location</p>
*/
public Location makeRelativeLocation(double x, double y, double z, float yaw) {
Location newLocation = this.clone();
newLocation.setYaw(yaw);
newLocation.setPitch(0);
return newLocation.add(x, y, z);
}
/**
* Gets a location relative to this block location
*
* @param relativeVector <p>The relative block vector describing the relative location</p>
* @param yaw <p>The yaw pointing outwards from a portal (in the relative vector's out direction)</p>
* @return <p>A location relative to this location</p>
*/
public BlockLocation getRelativeLocation(RelativeBlockVector relativeVector, double yaw) {
Vector realVector = DirectionHelper.getCoordinateVectorFromRelativeVector(relativeVector.getRight(),
relativeVector.getDown(), relativeVector.getOut(), yaw);
return makeRelativeBlockLocation(realVector.getBlockX(), realVector.getBlockY(), realVector.getBlockZ());
}
/**
* Makes a location relative to the current location according to given parameters
*
* <p>Out goes in the direction of the yaw. Right goes in the direction of (yaw - 90) degrees.
* Depth goes downwards following the -y direction.</p>
*
* @param right <p>The amount of blocks to go right when looking towards a portal</p>
* @param down <p>The amount of blocks to go downwards when looking towards a portal</p>
* @param out <p>The amount of blocks to go outwards when looking towards a portal</p>
* @param portalYaw <p>The yaw when looking out from the portal</p>
* @return A new location relative to this block location
*/
public Location getRelativeLocation(double right, double down, double out, float portalYaw) {
Vector realVector = DirectionHelper.getCoordinateVectorFromRelativeVector(right, down, out, portalYaw);
return makeRelativeLocation(0.5 + realVector.getBlockX(), realVector.getBlockY(),
0.5 + realVector.getBlockZ(), portalYaw);
}
/**
* Gets the type of block at this block location
*
* @return <p>The block's material type</p>
*/
public Material getType() {
return this.getBlock().getType();
}
/**
* Sets the type of block at this location
*
* @param type <p>The block's new material type</p>
*/
public void setType(Material type) {
this.getBlock().setType(type);
}
/**
* Gets the location representing this block location
*
* @return <p>The location representing this block location</p>
*/
public Location getLocation() {
return this.clone();
}
/**
* Gets this block location's parent block
*
* <p>The parent block is the block the item at this block location is attached to. Usually this is the block a
* sign or wall sign is attached to.</p>
*
* @return <p>This block location's parent block</p>
*/
public Block getParent() {
if (parent == null) {
findParent();
}
if (parent == null) {
return null;
}
return parent.getBlock();
}
/**
* Tries to find the parent block location
*
* <p>If this block location is a sign, the parent is the block location of the block the sign is connected to.</p>
*/
private void findParent() {
int offsetX = 0;
int offsetY = 0;
int offsetZ = 0;
BlockData blockData = getBlock().getBlockData();
if (blockData instanceof Directional) {
//Get the offset of the block "behind" this block
BlockFace facing = ((Directional) blockData).getFacing().getOppositeFace();
offsetX = facing.getModX();
offsetZ = facing.getModZ();
} else if (blockData instanceof Sign) {
//Get offset the block beneath the sign
offsetY = -1;
} else {
return;
}
parent = this.makeRelativeBlockLocation(offsetX, offsetY, offsetZ);
}
@Override
public String toString() {
return String.valueOf(this.getBlockX()) + ',' + this.getBlockY() + ',' + this.getBlockZ();
}
@Override
public int hashCode() {
int result = 18;
result = result * 27 + this.getBlockX();
result = result * 27 + this.getBlockY();
result = result * 27 + this.getBlockZ();
if (this.getWorld() != null) {
result = result * 27 + this.getWorld().getName().hashCode();
}
return result;
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object == null || getClass() != object.getClass()) {
return false;
}
BlockLocation blockLocation = (BlockLocation) object;
World thisWorld = this.getWorld();
World otherWorld = blockLocation.getWorld();
//Check if the worlds of the two locations match
boolean worldsEqual = (thisWorld == null && otherWorld == null) || ((thisWorld != null && otherWorld != null)
&& thisWorld == otherWorld);
//As this is a block location, only the block coordinates are compared
return blockLocation.getBlockX() == this.getBlockX() && blockLocation.getBlockY() == this.getBlockY() &&
blockLocation.getBlockZ() == this.getBlockZ() && worldsEqual;
}
}

View File

@@ -0,0 +1,55 @@
package net.knarcraft.stargate.container;
import org.bukkit.Chunk;
import org.jetbrains.annotations.NotNull;
/**
* Represents a requests for the unloading of a chunk which has been previously loaded by the Stargate plugin
*/
public class ChunkUnloadRequest implements Comparable<ChunkUnloadRequest> {
private final Long unloadNanoTime;
private final Chunk chunkToUnload;
/**
* Instantiates a new chunk unloading request
*
* @param chunkToUnload <p>The chunk to request the unloading of</p>
* @param timeUntilUnload <p>The time in milliseconds to wait before unloading the chunk</p>
*/
public ChunkUnloadRequest(Chunk chunkToUnload, Long timeUntilUnload) {
this.chunkToUnload = chunkToUnload;
long systemNanoTime = System.nanoTime();
this.unloadNanoTime = systemNanoTime + (timeUntilUnload * 1000000);
}
/**
* Gets the chunk to unload
*
* @return <p>The chunk to unload</p>
*/
public Chunk getChunkToUnload() {
return this.chunkToUnload;
}
/**
* Gets the system nano time denoting at which time the unload request should be executed
*
* @return <p>The system nano time denoting when the chunk is to be unloaded</p>
*/
public Long getUnloadNanoTime() {
return this.unloadNanoTime;
}
@Override
public String toString() {
return "{" + chunkToUnload + ", " + unloadNanoTime + "}";
}
@Override
public int compareTo(@NotNull ChunkUnloadRequest otherRequest) {
//Prioritize requests based on time until unload
return unloadNanoTime.compareTo(otherRequest.unloadNanoTime);
}
}

View File

@@ -0,0 +1,60 @@
package net.knarcraft.stargate.container;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.entity.Player;
/**
* This class represents a player teleporting from the end to the over-world using an artificial end portal
*
* <p>This is necessary because a player entering an end portal in the end is a special case. Instead of being
* teleported, the player is respawned. Because of this, the teleportation needs to be saved and later used to hijack
* the position of where the player is to respawn.</p>
*/
public class FromTheEndTeleportation {
private final Player teleportingPlayer;
private final Portal exitPortal;
/**
* Instantiates a new teleportation from the end
*
* @param teleportingPlayer <p>The teleporting player</p>
* @param exitPortal <p>The portal to exit from</p>
*/
public FromTheEndTeleportation(Player teleportingPlayer, Portal exitPortal) {
this.teleportingPlayer = teleportingPlayer;
this.exitPortal = exitPortal;
}
/**
* Gets the teleporting player
*
* @return <p>The teleporting player</p>
*/
public Player getPlayer() {
return this.teleportingPlayer;
}
/**
* Gets the portal to exit from
*
* @return <p>The portal to exit from</p>
*/
public Portal getExit() {
return this.exitPortal;
}
@Override
public int hashCode() {
return teleportingPlayer.hashCode();
}
@Override
public boolean equals(Object other) {
if (!(other instanceof FromTheEndTeleportation otherTeleportation)) {
return false;
}
return teleportingPlayer.equals(otherTeleportation.teleportingPlayer);
}
}

View File

@@ -0,0 +1,122 @@
package net.knarcraft.stargate.container;
/**
* This stores a block location as a vector relative to a position
*
* <p>A relative block vector stores a vector relative to some origin. The origin in this plugin is usually the
* top-left block of a gate (top-left when looking at the side with the sign). The right is therefore the distance
* from the top-left corner towards the top-right corner. Down is the distance from the top-left corner towards the
* bottom-left corner. Out is the distance outward from the gate.</p>
*/
public class RelativeBlockVector {
private final int right;
private final int down;
private final int out;
/**
* A specifier for one of the relative block vector's three properties
*/
public enum Property {
/**
* Specifies the relative block vector's right property
*/
RIGHT,
/**
* Specifies the relative block vector's down property
*/
DOWN,
/**
* Specifies the relative block vector's out property
*/
OUT
}
/**
* Instantiates a new relative block vector
*
* <p>Relative block vectors start from a top-left corner. A yaw is used to orient a relative block vector in the
* "real world".
* In terms of a gate layout, the origin is 0,0. Right is towards the end of the line. Down is to the
* next line. Out is towards the observer.</p>
*
* @param right <p>The distance rightward relative to the origin</p>
* @param down <p>The distance downward relative to the origin</p>
* @param out <p>The distance outward relative to the origin</p>
*/
public RelativeBlockVector(int right, int down, int out) {
this.right = right;
this.down = down;
this.out = out;
}
/**
* Adds a value to one of the properties of this relative block vector
*
* @param propertyToAddTo <p>The property to add to</p>
* @param valueToAdd <p>The value to add to the property (negative to move in the opposite direction)</p>
* @return <p>A new relative block vector with the property altered</p>
*/
public RelativeBlockVector addToVector(Property propertyToAddTo, int valueToAdd) {
return switch (propertyToAddTo) {
case RIGHT -> new RelativeBlockVector(this.right + valueToAdd, this.down, this.out);
case DOWN -> new RelativeBlockVector(this.right, this.down + valueToAdd, this.out);
case OUT -> new RelativeBlockVector(this.right, this.down, this.out + valueToAdd);
};
}
/**
* Gets a relative block vector which is this inverted (pointing in the opposite direction)
*
* @return <p>This vector, but inverted</p>
*/
public RelativeBlockVector invert() {
return new RelativeBlockVector(-this.right, -this.down, -this.out);
}
/**
* Gets the distance to the right relative to the origin
*
* @return <p>The distance to the right relative to the origin</p>
*/
public int getRight() {
return right;
}
/**
* Gets the distance downward relative to the origin
*
* @return <p>The distance downward relative to the origin</p>
*/
public int getDown() {
return down;
}
/**
* Gets the distance outward relative to the origin
*
* @return <p>The distance outward relative to the origin</p>
*/
public int getOut() {
return out;
}
@Override
public String toString() {
return String.format("(right = %d, down = %d, out = %d)", right, down, out);
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (other == null || this.getClass() != other.getClass()) {
return false;
}
RelativeBlockVector otherVector = (RelativeBlockVector) other;
return this.right == otherVector.right && this.down == otherVector.down &&
this.out == otherVector.out;
}
}

View File

@@ -0,0 +1,67 @@
package net.knarcraft.stargate.container;
import net.knarcraft.knarlib.util.ColorHelper;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.DyeColor;
import org.bukkit.block.Sign;
/**
* A class that keeps track of the sign colors for a given sign
*/
public class SignData {
private final Sign sign;
private final ChatColor mainSignColor;
private final ChatColor highlightSignColor;
private final DyeColor dyedColor;
/**
* Instantiates a new sign colors object
*
* @param sign <p>The sign the colors belong to</p>
* @param mainSignColor <p>The main color to use for the sign</p>
* @param highlightSignColor <p>The highlighting color to use for the sign</p>
*/
public SignData(Sign sign, ChatColor mainSignColor, ChatColor highlightSignColor) {
this.sign = sign;
this.mainSignColor = mainSignColor;
this.highlightSignColor = highlightSignColor;
this.dyedColor = sign.getColor();
}
/**
* Gets the sign of this sign colors object
*
* @return <p>The sign of this sign colors object</p>
*/
public Sign getSign() {
return sign;
}
/**
* Gets the main color of the sign
*
* @return <p>The main color of the sign</p>
*/
public ChatColor getMainSignColor() {
if (dyedColor != DyeColor.BLACK) {
return ColorHelper.fromColor(dyedColor.getColor());
} else {
return mainSignColor;
}
}
/**
* Gets the highlighting color of the sign
*
* @return <p>The highlighting color of the sign</p>
*/
public ChatColor getHighlightSignColor() {
if (dyedColor != DyeColor.BLACK) {
return ColorHelper.fromColor(ColorHelper.invert(dyedColor.getColor()));
} else {
return highlightSignColor;
}
}
}

View File

@@ -0,0 +1,66 @@
package net.knarcraft.stargate.event;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* This event should be called whenever a player attempts to access a stargate
*
* <p>This event is triggered whenever a player enters or activates a stargate. This event can be used to override
* whether the player should be allowed to access the stargate.</p>
*/
@SuppressWarnings("unused")
public class StargateAccessEvent extends StargatePlayerEvent {
private static final HandlerList handlers = new HandlerList();
private boolean deny;
/**
* Instantiates a new stargate access event
*
* @param player <p>The player involved in the event</p>
* @param portal <p>The portal involved in the event</p>
* @param deny <p>Whether the stargate access should be denied</p>
*/
public StargateAccessEvent(Player player, Portal portal, boolean deny) {
super(portal, player);
this.deny = deny;
}
/**
* Gets whether the player should be denied access
*
* @return <p>Whether the player should be denied access</p>
*/
public boolean getDeny() {
return this.deny;
}
/**
* Sets whether to deny access to the player
*
* @param deny <p>Whether to deny access to the player</p>
*/
public void setDeny(boolean deny) {
this.deny = deny;
}
/**
* Gets a handler-list containing all event handlers
*
* @return <p>A handler-list with all event handlers</p>
*/
public static HandlerList getHandlerList() {
return handlers;
}
@Override
@NotNull
public HandlerList getHandlers() {
return handlers;
}
}

View File

@@ -0,0 +1,89 @@
package net.knarcraft.stargate.event;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* This event should be called whenever a player activates a stargate
*
* <p>Activation of a stargate happens when a player right-clicks the sign of a stargate.
* This event can be used to overwrite the selected destination, and all destinations the player can see.</p>
*/
@SuppressWarnings("unused")
public class StargateActivateEvent extends StargatePlayerEvent {
private static final HandlerList handlers = new HandlerList();
private List<String> destinations;
private String destination;
/**
* Instantiates a new stargate activate event
*
* @param portal <p>The activated portal</p>
* @param player <p>The player activating the portal</p>
* @param destinations <p>The destinations available to the player using the portal</p>
* @param destination <p>The currently selected destination</p>
*/
public StargateActivateEvent(Portal portal, Player player, List<String> destinations, String destination) {
super(portal, player);
this.destinations = destinations;
this.destination = destination;
}
/**
* Gets the destinations available for the portal
*
* @return <p>The destinations available for the portal</p>
*/
public List<String> getDestinations() {
return destinations;
}
/**
* Sets the destinations available to the player using the portal
*
* @param destinations <p>The new list of available destinations</p>
*/
public void setDestinations(List<String> destinations) {
this.destinations = destinations;
}
/**
* Gets the selected destination
*
* @return <p>The selected destination</p>
*/
public String getDestination() {
return destination;
}
/**
* Sets (changes) the selected destination
*
* @param destination <p>The new selected destination</p>
*/
public void setDestination(String destination) {
this.destination = destination;
}
/**
* Gets a handler-list containing all event handlers
*
* @return <p>A handler-list with all event handlers</p>
*/
public static HandlerList getHandlerList() {
return handlers;
}
@Override
@NotNull
public HandlerList getHandlers() {
return handlers;
}
}

View File

@@ -0,0 +1,64 @@
package net.knarcraft.stargate.event;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* This event should be called whenever a stargate is closed
*
* <p>This event can be used to overwrite whether the stargate should be forced to close, even if it's set as
* always-on.</p>
*/
@SuppressWarnings("unused")
public class StargateCloseEvent extends StargateEvent {
private static final HandlerList handlers = new HandlerList();
private boolean force;
/**
* Instantiates a new stargate closing event
*
* @param portal <p>The portal to close</p>
* @param force <p>Whether to force the gate to close, even if set as always-on</p>
*/
public StargateCloseEvent(Portal portal, boolean force) {
super(portal);
this.force = force;
}
/**
* Gets whether to force the stargate to close
*
* @return <p>Whether to force the stargate to close</p>
*/
public boolean getForce() {
return force;
}
/**
* Sets whether the stargate should be forced to close
*
* @param force <p>Whether the stargate should be forced to close</p>
*/
public void setForce(boolean force) {
this.force = force;
}
/**
* Gets a handler-list containing all event handlers
*
* @return <p>A handler-list with all event handlers</p>
*/
public static HandlerList getHandlerList() {
return handlers;
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
}
}

View File

@@ -0,0 +1,120 @@
package net.knarcraft.stargate.event;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* This event should be called whenever a stargate is created
*
* <p>This event can be used to deny or change the cost of a stargate creation.</p>
*/
@SuppressWarnings("unused")
public class StargateCreateEvent extends StargatePlayerEvent {
private static final HandlerList handlers = new HandlerList();
private final String[] lines;
private boolean deny;
private String denyReason;
private int cost;
/**
* Instantiates a new stargate creation event
*
* @param player <p>Thg player creating the stargate</p>
* @param portal <p>The created portal</p>
* @param lines <p>The lines of the sign creating the star gate</p>
* @param deny <p>Whether to deny the creation of the new gate</p>
* @param denyReason <p>The reason stargate creation was denied</p>
* @param cost <p>The cost of creating the new star gate</p>
*/
public StargateCreateEvent(Player player, Portal portal, String[] lines, boolean deny, String denyReason, int cost) {
super(portal, player);
this.lines = lines;
this.deny = deny;
this.denyReason = denyReason;
this.cost = cost;
}
/**
* Gets a given line from the sign creating the star gate
*
* @param index <p>The line number to get</p>
* @return <p>The text on the given line</p>
* @throws IndexOutOfBoundsException <p>If given a line index less than zero or above three</p>
*/
public String getLine(int index) throws IndexOutOfBoundsException {
return lines[index];
}
/**
* Gets whether the stargate creation should be denied
*
* @return <p>Whether the stargate creation should be denied</p>
*/
public boolean getDeny() {
return deny;
}
/**
* Sets whether the stargate creation should be denied
*
* @param deny <p>Whether the stargate creation should be denied</p>
*/
public void setDeny(boolean deny) {
this.deny = deny;
}
/**
* Gets the reason the stargate creation was denied
*
* @return <p>The reason the stargate creation was denied</p>
*/
public String getDenyReason() {
return denyReason;
}
/**
* Sets the reason the stargate creation was denied
*
* @param denyReason <p>The new reason why the stargate creation was denied</p>
*/
public void setDenyReason(String denyReason) {
this.denyReason = denyReason;
}
/**
* Gets the cost of creating the stargate
*
* @return <p>The cost of creating the stargate</p>
*/
public int getCost() {
return cost;
}
/**
* Sets the cost of creating the stargate
*
* @param cost <p>The new cost of creating the stargate</p>
*/
public void setCost(int cost) {
this.cost = cost;
}
/**
* Gets a handler-list containing all event handlers
*
* @return <p>A handler-list with all event handlers</p>
*/
public static HandlerList getHandlerList() {
return handlers;
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
}
}

View File

@@ -0,0 +1,42 @@
package net.knarcraft.stargate.event;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* This event should be called whenever a stargate is deactivated
*
* <p>A deactivation is usually caused by no activity for a set amount of time.
* This event can only be used to listen for de-activation events.</p>
*/
@SuppressWarnings("unused")
public class StargateDeactivateEvent extends StargateEvent {
private static final HandlerList handlers = new HandlerList();
/**
* Instantiates a new stargate deactivation event
*
* @param portal <p>The portal which was deactivated</p>
*/
public StargateDeactivateEvent(Portal portal) {
super(portal);
}
/**
* Gets a handler-list containing all event handlers
*
* @return <p>A handler-list with all event handlers</p>
*/
public static HandlerList getHandlerList() {
return handlers;
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
}
}

View File

@@ -0,0 +1,106 @@
package net.knarcraft.stargate.event;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* This event should be called whenever a stargate is destroyed
*
* <p>This event can be used to deny or change the cost of a stargate destruction.</p>
*/
@SuppressWarnings("unused")
public class StargateDestroyEvent extends StargatePlayerEvent {
private static final HandlerList handlers = new HandlerList();
private boolean deny;
private String denyReason;
private int cost;
/**
* Instantiates a new Stargate Destroy Event
*
* @param portal <p>The destroyed portal</p>
* @param player <p>The player destroying the portal</p>
* @param deny <p>Whether the event should be denied (cancelled)</p>
* @param denyMsg <p>The message to display if the event is denied</p>
* @param cost <p>The cost of destroying the portal</p>
*/
public StargateDestroyEvent(Portal portal, Player player, boolean deny, String denyMsg, int cost) {
super(portal, player);
this.deny = deny;
this.denyReason = denyMsg;
this.cost = cost;
}
/**
* Gets whether this event should be denied
*
* @return <p>Whether this event should be denied</p>
*/
public boolean getDeny() {
return deny;
}
/**
* Sets whether this event should be denied
*
* @param deny <p>Whether this event should be denied</p>
*/
public void setDeny(boolean deny) {
this.deny = deny;
}
/**
* Gets the reason the event was denied
*
* @return <p>The reason the event was denied</p>
*/
public String getDenyReason() {
return denyReason;
}
/**
* Sets the reason the event was denied
*
* @param denyReason <p>The reason the event was denied</p>
*/
public void setDenyReason(String denyReason) {
this.denyReason = denyReason;
}
/**
* Gets the cost of destroying the portal
*
* @return <p>The cost of destroying the portal</p>
*/
public int getCost() {
return cost;
}
/**
* Sets the cost of destroying the portal
*
* @param cost <p>The cost of destroying the portal</p>
*/
public void setCost(int cost) {
this.cost = cost;
}
/**
* Gets a handler-list containing all event handlers
*
* @return <p>A handler-list with all event handlers</p>
*/
public static HandlerList getHandlerList() {
return handlers;
}
@NotNull
@Override
public HandlerList getHandlers() {
return handlers;
}
}

View File

@@ -0,0 +1,90 @@
package net.knarcraft.stargate.event;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* This event should be called whenever a non-player teleports through a stargate
*
* <p>This event can be used to overwrite the location the entity is teleported to.</p>
*/
@SuppressWarnings("unused")
public class StargateEntityPortalEvent extends StargateEvent implements StargateTeleportEvent {
private static final HandlerList handlers = new HandlerList();
final Entity travellingEntity;
private final Portal destination;
private Location exit;
/**
* Instantiates a new stargate portal event
*
* @param travellingEntity <p>The entity travelling through this portal</p>
* @param portal <p>The portal the entity entered from</p>
* @param destination <p>The destination the entity should exit from</p>
* @param exit <p>The exit location of the destination portal the entity will be teleported to</p>
*/
public StargateEntityPortalEvent(Entity travellingEntity, Portal portal, Portal destination, Location exit) {
super(portal);
this.travellingEntity = travellingEntity;
this.destination = destination;
this.exit = exit;
}
/**
* Return the non-player entity teleporting
*
* @return <p>The non-player teleporting</p>
*/
public Entity getEntity() {
return travellingEntity;
}
/**
* Return the destination portal
*
* @return <p>The destination portal</p>
*/
public Portal getDestination() {
return destination;
}
/**
* Return the location of the players exit point
*
* @return <p>Location of the exit point</p>
*/
@Override
public Location getExit() {
return exit;
}
/**
* Set the location of the entity's exit point
*
* @param location <p>The new location of the entity's exit point</p>
*/
public void setExit(Location location) {
this.exit = location;
}
/**
* Gets a handler-list containing all event handlers
*
* @return <p>A handler-list with all event handlers</p>
*/
public static HandlerList getHandlerList() {
return handlers;
}
@Override
@NotNull
public HandlerList getHandlers() {
return handlers;
}
}

View File

@@ -0,0 +1,45 @@
package net.knarcraft.stargate.event;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
/**
* An abstract event describing any stargate event
*/
@SuppressWarnings("unused")
public abstract class StargateEvent extends Event implements Cancellable {
private final Portal portal;
private boolean cancelled;
/**
* Instantiates a new stargate event
*
* @param portal <p>The portal involved in this stargate event</p>
*/
StargateEvent(Portal portal) {
this.portal = portal;
this.cancelled = false;
}
/**
* Gets the portal involved in this stargate event
*
* @return <p>The portal involved in this stargate event</p>
*/
public Portal getPortal() {
return portal;
}
@Override
public boolean isCancelled() {
return this.cancelled;
}
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
}

View File

@@ -0,0 +1,65 @@
package net.knarcraft.stargate.event;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* This event should be called whenever a player opens a stargate
*
* <p>This event can be used to overwrite whether the stargate should be forced to open, even if it's already open.</p>
*/
@SuppressWarnings({"unused"})
public class StargateOpenEvent extends StargatePlayerEvent {
private static final HandlerList handlers = new HandlerList();
private boolean force;
/**
* Instantiates a new stargate open event
*
* @param player <p>The player opening the stargate</p>
* @param portal <p>The opened portal</p>
* @param force <p>Whether to force the portal open</p>
*/
public StargateOpenEvent(Player player, Portal portal, boolean force) {
super(portal, player);
this.force = force;
}
/**
* Gets whether the portal should be forced open
*
* @return <p>Whether the portal should be forced open</p>
*/
public boolean getForce() {
return force;
}
/**
* Sets whether the portal should be forced open
*
* @param force <p>Whether the portal should be forced open</p>
*/
public void setForce(boolean force) {
this.force = force;
}
/**
* Gets a handler-list containing all event handlers
*
* @return <p>A handler-list with all event handlers</p>
*/
public static HandlerList getHandlerList() {
return handlers;
}
@Override
@NotNull
public HandlerList getHandlers() {
return handlers;
}
}

View File

@@ -0,0 +1,33 @@
package net.knarcraft.stargate.event;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.entity.Player;
/**
* An abstract event describing any stargate event where a player is involved
*/
@SuppressWarnings("unused")
public abstract class StargatePlayerEvent extends StargateEvent {
private final Player player;
/**
* Instantiates a new stargate player event
*
* @param portal <p>The portal involved in this stargate event</p>
*/
StargatePlayerEvent(Portal portal, Player player) {
super(portal);
this.player = player;
}
/**
* Gets the player creating the star gate
*
* @return <p>The player creating the star gate</p>
*/
public Player getPlayer() {
return player;
}
}

View File

@@ -0,0 +1,79 @@
package net.knarcraft.stargate.event;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* This event should be called whenever a player teleports through a stargate
*
* <p>This event can be used to overwrite the location the player is teleported to.</p>
*/
@SuppressWarnings("unused")
public class StargatePlayerPortalEvent extends StargatePlayerEvent implements StargateTeleportEvent {
private static final HandlerList handlers = new HandlerList();
private final Portal destination;
private Location exit;
/**
* Instantiates a new stargate player portal event
*
* @param player <p>The player teleporting</p>
* @param portal <p>The portal the player entered from</p>
* @param destination <p>The destination the player should exit from</p>
* @param exit <p>The exit location of the destination portal the user will be teleported to</p>
*/
public StargatePlayerPortalEvent(Player player, Portal portal, Portal destination, Location exit) {
super(portal, player);
this.destination = destination;
this.exit = exit;
}
/**
* Return the destination portal
*
* @return <p>The destination portal</p>
*/
public Portal getDestination() {
return destination;
}
/**
* Return the location of the players exit point
*
* @return <p>Location of the exit point</p>
*/
@Override
public Location getExit() {
return exit;
}
/**
* Set the location of the player's exit point
*
* @param location <p>The new location of the player's exit point</p>
*/
public void setExit(Location location) {
this.exit = location;
}
/**
* Gets a handler-list containing all event handlers
*
* @return <p>A handler-list with all event handlers</p>
*/
public static HandlerList getHandlerList() {
return handlers;
}
@Override
@NotNull
public HandlerList getHandlers() {
return handlers;
}
}

View File

@@ -0,0 +1,18 @@
package net.knarcraft.stargate.event;
import org.bukkit.Location;
import org.bukkit.event.Cancellable;
/**
* A generic teleportation event
*/
public interface StargateTeleportEvent extends Cancellable {
/**
* Return the location of the players exit point
*
* @return <p>Location of the exit point</p>
*/
Location getExit();
}

View File

@@ -0,0 +1,25 @@
/**
* Events for any plugins wanting to interact with the Stargate plugin
*
* <p>This package contains several events used for interactions with Stargate. All events can be cancelled. For
* several of the events, it is possible to overrule permissions. A general overview of the events' usage:</p>
*
* <ul>
* <li>The StargateAccessEvent is called whenever a player clicks a stargate's sign, and when a player enters a
* Stargate. It can be used to override whether the access should be allowed or denied.</li>
* <li>The StargateActivateEvent is called whenever a player activates a stargate (uses the stargate's sign). It
* can be used to override which destinations are available to the player.</li>
* <li>The StargateCloseEvent is called whenever a stargate is closed. Forcing the stargate closed can be toggled.</li>
* <li>The StargateCreateEvent is called whenever a new stargate is created. Its deny value can be overridden, the
* cost can be changed</li>
* <li>The StargateDeactivateEvent is called whenever a stargate is deactivated.</li>
* <li>The StargateDestroyEvent is called whenever a stargate is destroyed. Its deny value can be overridden or the
* cost can be changed.</li>
* <li>The StargateEntityPortalEvent is called whenever an entity teleports through a stargate. The exit location
* can be changed.</li>
* <li>The StargateOpenEvent is called whenever a stargate is opened. Forcing the stargate open can be toggled.</li>
* <li>The StargatePlayerPortalEvent is called whenever a player teleports through a stargate. The exit location can
* be changed.</li>
* </ul>
*/
package net.knarcraft.stargate.event;

View File

@@ -0,0 +1,280 @@
package net.knarcraft.stargate.listener;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.BlockChangeRequest;
import net.knarcraft.stargate.event.StargateDestroyEvent;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalCreator;
import net.knarcraft.stargate.portal.PortalHandler;
import net.knarcraft.stargate.portal.PortalRegistry;
import net.knarcraft.stargate.utility.EconomyHelper;
import net.knarcraft.stargate.utility.MaterialHelper;
import net.knarcraft.stargate.utility.PermissionHelper;
import net.knarcraft.stargate.utility.PortalFileHelper;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.data.type.WallSign;
import org.bukkit.entity.Player;
import org.bukkit.entity.Snowman;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockFromToEvent;
import org.bukkit.event.block.BlockPhysicsEvent;
import org.bukkit.event.block.BlockPistonEvent;
import org.bukkit.event.block.BlockPistonExtendEvent;
import org.bukkit.event.block.BlockPistonRetractEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.block.EntityBlockFormEvent;
import org.bukkit.event.block.SignChangeEvent;
import java.util.List;
/**
* This class is responsible for listening to relevant block events related to creating and breaking portals
*/
@SuppressWarnings("unused")
public class BlockEventListener implements Listener {
/**
* Detects snowmen ruining portals
*
* <p>If entrance protection or portal verification is enabled, the snowman will be prevented from placing snow in
* the portal entrance.</p>
*
* @param event <p>The triggered event</p>
*/
@EventHandler
public void onBlockFormedByEntity(EntityBlockFormEvent event) {
if (event.isCancelled() || (!Stargate.getGateConfig().protectEntrance() &&
!Stargate.getGateConfig().verifyPortals())) {
return;
}
//We are only interested in snowman events
if (!(event.getEntity() instanceof Snowman)) {
return;
}
//Cancel the event if a snowman is trying to place snow in the portal's entrance
if (PortalHandler.getByEntrance(event.getBlock()) != null) {
event.setCancelled(true);
}
}
/**
* Detects sign changes to detect if the user is creating a new gate
*
* @param event <p>The triggered event</p>
*/
@EventHandler
public void onSignChange(SignChangeEvent event) {
if (event.isCancelled()) {
return;
}
Player player = event.getPlayer();
Block block = event.getBlock();
//Ignore normal signs
if (!(block.getBlockData() instanceof WallSign)) {
return;
}
final Portal portal = new PortalCreator(event, player).createPortal();
//Not creating a gate, just placing a sign
if (portal == null) {
return;
}
//Remove the sign if the no sign option is enabled
if (portal.getOptions().hasNoSign()) {
Material replaceMaterial = PortalFileHelper.decideRemovalMaterial(portal.getSignLocation(), portal);
BlockChangeRequest request = new BlockChangeRequest(portal.getSignLocation(), replaceMaterial, null);
Stargate.addBlockChangeRequest(request);
}
Stargate.getMessageSender().sendSuccessMessage(player, Stargate.getString("createMsg"));
Stargate.debug("onSignChange", "Initialized stargate: " + portal.getName());
Stargate.getInstance().getServer().getScheduler().scheduleSyncDelayedTask(Stargate.getInstance(),
portal::drawSign, 1);
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onBlockPlace(BlockPlaceEvent event) {
if (event.isCancelled() || !Stargate.getGateConfig().protectEntrance()) {
return;
}
Block block = event.getBlock();
Player player = event.getPlayer();
Portal portal = PortalHandler.getByEntrance(block);
if (portal != null) {
//Prevent blocks from being placed in the entrance, if protectEntrance is enabled, as breaking the block
// would destroy the portal
event.setCancelled(true);
}
}
/**
* Detects block breaking to detect if the user is destroying a gate
*
* @param event <p>The triggered event</p>
*/
@EventHandler(priority = EventPriority.HIGHEST)
public void onBlockBreak(BlockBreakEvent event) {
if (event.isCancelled()) {
return;
}
Block block = event.getBlock();
Player player = event.getPlayer();
//Decide if a portal is broken
Portal portal = PortalHandler.getByBlock(block);
if (portal == null && Stargate.getGateConfig().protectEntrance()) {
portal = PortalHandler.getByEntrance(block);
}
if (portal == null) {
return;
}
boolean deny = false;
String denyMessage = "";
//Decide if the user can destroy the portal
if (!PermissionHelper.canDestroyPortal(player, portal)) {
denyMessage = Stargate.getString("denyMsg");
deny = true;
Stargate.logInfo(String.format("%s tried to destroy gate", player.getName()));
}
int cost = Stargate.getEconomyConfig().getDestroyCost(player, portal.getGate());
//Create and call a StarGateDestroyEvent
StargateDestroyEvent destroyEvent = new StargateDestroyEvent(portal, player, deny, denyMessage, cost);
Stargate.getInstance().getServer().getPluginManager().callEvent(destroyEvent);
if (destroyEvent.isCancelled()) {
event.setCancelled(true);
return;
}
//Destroy denied
if (destroyEvent.getDeny()) {
if (!destroyEvent.getDenyReason().trim().isEmpty()) {
Stargate.getMessageSender().sendErrorMessage(player, destroyEvent.getDenyReason());
}
event.setCancelled(true);
return;
}
//Take care of payment transactions
if (!handleEconomyPayment(destroyEvent, player, portal, event)) {
return;
}
PortalRegistry.unregisterPortal(portal, true);
Stargate.getMessageSender().sendSuccessMessage(player, Stargate.getString("destroyMsg"));
}
/**
* Handles economy payment for breaking the portal
*
* @param destroyEvent <p>The destroy event</p>
* @param player <p>The player which triggered the event</p>
* @param portal <p>The broken portal</p>
* @param event <p>The break event</p>
* @return <p>True if the payment was successful. False if the event was cancelled</p>
*/
private boolean handleEconomyPayment(StargateDestroyEvent destroyEvent, Player player, Portal portal,
BlockBreakEvent event) {
int cost = destroyEvent.getCost();
if (cost != 0) {
String portalName = portal.getName();
//Cannot pay
if (!EconomyHelper.chargePlayerIfNecessary(player, cost)) {
Stargate.debug("onBlockBreak", "Insufficient Funds");
EconomyHelper.sendInsufficientFundsMessage(portalName, player, cost);
event.setCancelled(true);
return false;
}
//Tell the player they've paid or deceived money
if (cost > 0) {
EconomyHelper.sendDeductMessage(portalName, player, cost);
} else {
EconomyHelper.sendRefundMessage(portalName, player, cost);
}
}
return true;
}
/**
* Prevents any block physics events which may damage parts of the portal
*
* @param event <p>The event to check and possibly cancel</p>
*/
@EventHandler
public void onBlockPhysics(BlockPhysicsEvent event) {
Block block = event.getBlock();
Portal portal = null;
if (block.getType() == Material.NETHER_PORTAL) {
portal = PortalHandler.getByEntrance(block);
} else if (MaterialHelper.isButtonCompatible(block.getType())) {
portal = PortalHandler.getByControl(block);
}
if (portal != null) {
event.setCancelled(true);
}
}
/**
* Cancels any block move events which may cause a block to enter the opening of a portal
*
* @param event <p>The event to check and possibly cancel</p>
*/
@EventHandler
public void onBlockFromTo(BlockFromToEvent event) {
Portal portal = PortalHandler.getByEntrance(event.getBlock());
if (portal != null) {
event.setCancelled((event.getBlock().getY() == event.getToBlock().getY()));
}
}
/**
* Cancels any piston extend events if the target block is part of a portal
*
* @param event <p>The event to check and possibly cancel</p>
*/
@EventHandler
public void onPistonExtend(BlockPistonExtendEvent event) {
cancelPistonEvent(event, event.getBlocks());
}
/**
* Cancels any piston retract events if the target block is part of a portal
*
* @param event <p>The event to check and possibly cancel</p>
*/
@EventHandler
public void onPistonRetract(BlockPistonRetractEvent event) {
if (!event.isSticky()) {
return;
}
cancelPistonEvent(event, event.getBlocks());
}
/**
* Cancels a piston event if it would destroy a portal
*
* @param event <p>The event to cancel</p>
* @param blocks <p>The blocks included in the event</p>
*/
private void cancelPistonEvent(BlockPistonEvent event, List<Block> blocks) {
for (Block block : blocks) {
Portal portal = PortalHandler.getByBlock(block);
if (portal != null) {
event.setCancelled(true);
return;
}
}
}
}

View File

@@ -0,0 +1,42 @@
package net.knarcraft.stargate.listener;
import net.knarcraft.stargate.utility.BungeeHelper;
import org.bukkit.entity.Player;
import org.bukkit.plugin.messaging.PluginMessageListener;
import org.jetbrains.annotations.NotNull;
/**
* This listener teleports a user if a valid message is received from BungeeCord
*
* <p>Specifically, if a string starts with SGBungee encoded to be readable by readUTF followed by
* [PlayerUUID]delimiter[DestinationPortal] is received on the BungeeCord channel, this listener will teleport the
* player to the destination portal.</p>
*/
public class BungeeCordListener implements PluginMessageListener {
/**
* Receives plugin messages
*
* @param channel <p>The channel the message was received on</p>
* @param unused <p>Unused.</p>
* @param message <p>The message received from the plugin</p>
*/
@Override
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player unused, byte[] message) {
//Ignore plugin messages if some other plugin message is received
if (!channel.equals(BungeeHelper.getBungeeChannel())) {
return;
}
//Try to read the plugin message
String receivedMessage = BungeeHelper.readPluginMessage(message);
if (receivedMessage == null) {
return;
}
//Use the message to initiate teleportation
BungeeHelper.handleTeleportMessage(receivedMessage);
}
}

View File

@@ -0,0 +1,67 @@
package net.knarcraft.stargate.listener;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalHandler;
import net.knarcraft.stargate.portal.PortalRegistry;
import net.knarcraft.stargate.utility.EntityHelper;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.event.entity.EntityPortalEvent;
/**
* This listener listens for any relevant events on portal entities
*/
@SuppressWarnings("unused")
public class EntityEventListener implements Listener {
/**
* This event handler prevents sending entities to the normal nether instead of the stargate target
*
* @param event <p>The event to check and possibly cancel</p>
*/
@EventHandler(priority = EventPriority.LOWEST)
public void onPortalEvent(EntityPortalEvent event) {
if (event.isCancelled()) {
return;
}
Entity entity = event.getEntity();
//Cancel normal portal event is near a stargate
if (PortalHandler.getByAdjacentEntrance(event.getFrom(), EntityHelper.getEntityMaxSizeInt(entity)) != null) {
event.setCancelled(true);
}
}
/**
* This method catches any explosion events
*
* <p>If destroyed by explosions is enabled, any portals destroyed by the explosion will be unregistered. If not,
* the explosion will be cancelled.</p>
*
* @param event <p>The triggered explosion event</p>
*/
@EventHandler
public void onEntityExplode(EntityExplodeEvent event) {
if (event.isCancelled()) {
return;
}
for (Block block : event.blockList()) {
Portal portal = PortalHandler.getByBlock(block);
if (portal == null) {
continue;
}
if (Stargate.getGateConfig().destroyedByExplosion()) {
PortalRegistry.unregisterPortal(portal, true);
} else {
event.setCancelled(true);
break;
}
}
}
}

View File

@@ -0,0 +1,25 @@
package net.knarcraft.stargate.listener;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.portal.PortalHandler;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.CreatureSpawnEvent;
/**
* A listener that listens for any relevant events causing entities to spawn
*/
public class EntitySpawnListener implements Listener {
@EventHandler
public void onCreatureSpawn(CreatureSpawnEvent event) {
//Prevent Zombified Piglins and other creatures form spawning at stargates
if (event.getSpawnReason() == CreatureSpawnEvent.SpawnReason.NETHER_PORTAL) {
if (PortalHandler.getByEntrance(event.getLocation()) != null) {
event.setCancelled(true);
Stargate.debug("EntitySpawnListener", "Prevented creature from spawning at Stargate");
}
}
}
}

View File

@@ -0,0 +1,401 @@
package net.knarcraft.stargate.listener;
import net.knarcraft.knarlib.util.UpdateChecker;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.config.MessageSender;
import net.knarcraft.stargate.container.BlockLocation;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalActivator;
import net.knarcraft.stargate.portal.PortalHandler;
import net.knarcraft.stargate.portal.teleporter.PlayerTeleporter;
import net.knarcraft.stargate.portal.teleporter.VehicleTeleporter;
import net.knarcraft.stargate.utility.BungeeHelper;
import net.knarcraft.stargate.utility.MaterialHelper;
import net.knarcraft.stargate.utility.PermissionHelper;
import net.knarcraft.stargate.utility.TeleportHelper;
import net.knarcraft.stargate.utility.UUIDMigrationHelper;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.data.type.WallSign;
import org.bukkit.entity.AbstractHorse;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Vehicle;
import org.bukkit.event.Event;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import java.util.HashMap;
import java.util.Map;
/**
* This listener listens to any player-related events related to stargates
*/
@SuppressWarnings("unused")
public class PlayerEventListener implements Listener {
private static final Map<Player, Long> previousEventTimes = new HashMap<>();
/**
* This event handler handles detection of any player teleporting through a bungee gate
*
* @param event <p>The event to check for a teleporting player</p>
*/
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
//Migrate player name to UUID if necessary
UUIDMigrationHelper.migrateUUID(player);
//Notify joining admins about the available update
String availableUpdate = Stargate.getUpdateAvailable();
if (availableUpdate != null && Stargate.getStargateConfig().alertAdminsAboutUpdates() &&
player.hasPermission("stargate.admin")) {
String updateMessage = UpdateChecker.getUpdateAvailableString(availableUpdate, Stargate.getPluginVersion());
Stargate.getMessageSender().sendErrorMessage(player, updateMessage);
}
if (!Stargate.getGateConfig().enableBungee()) {
return;
}
//Check if the player is waiting to be teleported to a stargate
String destination = BungeeHelper.removeFromQueue(player.getUniqueId());
if (destination == null) {
return;
}
Portal portal = PortalHandler.getBungeePortal(destination);
if (portal == null) {
Stargate.debug("PlayerJoin", "Error fetching destination portal: " + destination);
return;
}
//Teleport the player to the stargate
new PlayerTeleporter(portal, player).teleport(portal, null);
}
/**
* This event handler detects if a player moves into a portal
*
* @param event <p>The player move event which was triggered</p>
*/
@EventHandler
public void onPlayerMove(PlayerMoveEvent event) {
if (event.isCancelled() || event.getTo() == null) {
return;
}
BlockLocation fromLocation = new BlockLocation(event.getFrom().getBlock());
BlockLocation toLocation = new BlockLocation(event.getTo().getBlock());
Player player = event.getPlayer();
//Check whether the event needs to be considered
if (!isRelevantMoveEvent(event, player, fromLocation, toLocation)) {
return;
}
Portal entrancePortal = PortalHandler.getByEntrance(toLocation);
//Check an additional block away in case the portal is a bungee portal using END_PORTAL
if (entrancePortal == null) {
entrancePortal = PortalHandler.getByAdjacentEntrance(toLocation);
}
Portal destination = entrancePortal.getPortalActivator().getDestination(player);
Entity playerVehicle = player.getVehicle();
//If the player is in a vehicle, but vehicle handling is disabled, just ignore the player
if (playerVehicle == null || (playerVehicle instanceof LivingEntity &&
Stargate.getGateConfig().handleVehicles())) {
teleportPlayer(playerVehicle, player, entrancePortal, destination, event);
}
}
/**
* Teleports a player, also teleports the player's vehicle if it's a living entity
*
* @param playerVehicle <p>The vehicle the player is currently sitting in</p>
* @param player <p>The player which moved</p>
* @param entrancePortal <p>The entrance the player entered</p>
* @param destination <p>The destination of the entrance portal</p>
* @param event <p>The move event causing the teleportation to trigger</p>
*/
private void teleportPlayer(Entity playerVehicle, Player player, Portal entrancePortal, Portal destination,
PlayerMoveEvent event) {
if (playerVehicle instanceof LivingEntity) {
//Make sure any horses are properly tamed
if (playerVehicle instanceof AbstractHorse horse && !horse.isTamed()) {
horse.setTamed(true);
horse.setOwner(player);
}
//Teleport the player's vehicle
player.setVelocity(new Vector());
new VehicleTeleporter(destination, (Vehicle) playerVehicle).teleportEntity(entrancePortal);
} else {
//Just teleport the player like normal
new PlayerTeleporter(destination, player).teleportPlayer(entrancePortal, event);
}
if (!entrancePortal.getOptions().isSilent()) {
Stargate.getMessageSender().sendSuccessMessage(player, Stargate.getString("teleportMsg"));
}
entrancePortal.getPortalOpener().closePortal(false);
}
/**
* Checks whether a player move event is relevant for this plugin
*
* @param event <p>The player move event to check</p>
* @param player <p>The player which moved</p>
* @param fromLocation <p>The location the player is moving from</p>
* @param toLocation <p>The location the player is moving to</p>
* @return <p>True if the event is relevant</p>
*/
private boolean isRelevantMoveEvent(PlayerMoveEvent event, Player player, BlockLocation fromLocation,
BlockLocation toLocation) {
//Check to see if the player moved to another block
if (fromLocation.equals(toLocation)) {
return false;
}
//Check if the player moved from a portal
Portal entrancePortal = PortalHandler.getByEntrance(toLocation);
if (entrancePortal == null) {
//Check an additional block away for BungeeCord portals using END_PORTAL as its material
entrancePortal = PortalHandler.getByAdjacentEntrance(toLocation);
if (entrancePortal == null || !entrancePortal.getOptions().isBungee() ||
entrancePortal.getGate().getPortalOpenBlock() != Material.END_PORTAL) {
return false;
}
}
Portal destination = entrancePortal.getPortalActivator().getDestination(player);
//Catch always open portals without a valid destination to prevent the user for being teleported and denied
if (!entrancePortal.getOptions().isBungee() && destination == null) {
return false;
}
//Decide if the anything stops the player from teleport
if (PermissionHelper.playerCannotTeleport(entrancePortal, destination, player, event)) {
return false;
}
//Decide if the user should be teleported to another bungee server
if (entrancePortal.getOptions().isBungee()) {
if (BungeeHelper.bungeeTeleport(player, entrancePortal, event) && !entrancePortal.getOptions().isSilent()) {
Stargate.getMessageSender().sendSuccessMessage(player, Stargate.getString("teleportMsg"));
}
return false;
}
//Make sure to check if the player has any leashed creatures, even though leashed teleportation is disabled
return TeleportHelper.noLeashedCreaturesPreventTeleportation(player);
}
/**
* This event handler detects if a player clicks a button or a sign
*
* @param event <p>The player interact event which was triggered</p>
*/
@EventHandler
public void onPlayerInteract(PlayerInteractEvent event) {
Player player = event.getPlayer();
Block block = event.getClickedBlock();
if (block == null) {
return;
}
if (event.getAction() == Action.RIGHT_CLICK_BLOCK) {
handleRightClickBlock(event, player, block, event.getHand());
} else if (event.getAction() == Action.LEFT_CLICK_BLOCK && block.getBlockData() instanceof WallSign) {
//Handle left click of a wall sign
handleSignClick(event, player, block, true);
}
}
/**
* This method handles left- or right-clicking of a sign
*
* @param event <p>The event causing the click</p>
* @param player <p>The player clicking the sign</p>
* @param block <p>The block that was clicked</p>
* @param leftClick <p>Whether the player performed a left click as opposed to a right click</p>
*/
private void handleSignClick(PlayerInteractEvent event, Player player, Block block, boolean leftClick) {
Portal portal = PortalHandler.getByBlock(block);
if (portal == null) {
return;
}
//Allow players with permissions to apply dye to signs
EquipmentSlot hand = event.getHand();
if (hand != null && (PermissionHelper.hasPermission(player, "stargate.admin.dye") ||
portal.isOwner(player))) {
ItemStack item = player.getInventory().getItem(hand);
if (item != null) {
String itemName = item.getType().toString();
if (itemName.endsWith("DYE") || itemName.endsWith("INK_SAC")) {
event.setUseInteractedBlock(Event.Result.ALLOW);
Bukkit.getScheduler().scheduleSyncDelayedTask(Stargate.getInstance(), portal::drawSign, 1);
return;
}
}
}
event.setUseInteractedBlock(Event.Result.DENY);
if (leftClick) {
//Cancel event in creative mode to prevent breaking the sign
if (player.getGameMode().equals(GameMode.CREATIVE)) {
event.setCancelled(true);
}
} else {
//Prevent usage of item in the player's hand (placing block and such)
event.setUseItemInHand(Event.Result.DENY);
}
//Check if the user can use the portal
if (cannotAccessPortal(player, portal)) {
return;
}
//Cycle portal destination
if ((!portal.isOpen()) && (!portal.getOptions().isFixed())) {
PortalActivator destinations = portal.getPortalActivator();
if (leftClick) {
destinations.cycleDestination(player, -1);
} else {
destinations.cycleDestination(player);
}
}
}
/**
* Check if a player should be denied from accessing (using) a portal
*
* @param player <p>The player trying to access the portal</p>
* @param portal <p>The portal the player is trying to use</p>
* @return <p>True if the player should be denied</p>
*/
private boolean cannotAccessPortal(Player player, Portal portal) {
boolean deny = PermissionHelper.cannotAccessNetwork(player, portal.getCleanNetwork());
if (PermissionHelper.portalAccessDenied(player, portal, deny)) {
if (!portal.getOptions().isSilent()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("denyMsg"));
}
return true;
}
return false;
}
/**
* This method handles right-clicking of a sign or button belonging to a stargate
*
* @param event <p>The event triggering the right-click</p>
* @param player <p>The player doing the right-click</p>
* @param block <p>The block the player clicked</p>
* @param hand <p>The hand the player used to interact with the stargate</p>
*/
private void handleRightClickBlock(PlayerInteractEvent event, Player player, Block block, EquipmentSlot hand) {
if (block.getBlockData() instanceof WallSign) {
handleSignClick(event, player, block, false);
return;
}
//Prevent a double click caused by a Spigot bug
if (clickIsBug(event.getPlayer(), block)) {
return;
}
if (MaterialHelper.isButtonCompatible(block.getType())) {
Portal portal = PortalHandler.getByBlock(block);
if (portal == null) {
return;
}
//Prevent the held item from being placed
event.setUseItemInHand(Event.Result.DENY);
event.setUseInteractedBlock(Event.Result.DENY);
//Check if the user can use the portal
if (cannotAccessPortal(player, portal)) {
return;
}
PermissionHelper.openPortal(player, portal);
if (portal.getPortalOpener().isOpenFor(player) && !MaterialHelper.isContainer(block.getType())) {
event.setUseInteractedBlock(Event.Result.ALLOW);
}
} else {
//Display information about the portal if it has no sign
ItemStack heldItem = player.getInventory().getItem(hand);
if (heldItem != null && (heldItem.getType().isAir() || !heldItem.getType().isBlock())) {
displayPortalInfo(block, player);
}
}
}
/**
* Displays information about a clicked portal
*
* <p>This will only display portal info if the portal has no sign and is not silent.</p>
*
* @param block <p>The clicked block</p>
* @param player <p>The player that clicked the block</p>
*/
private void displayPortalInfo(Block block, Player player) {
Portal portal = PortalHandler.getByBlock(block);
if (portal == null) {
return;
}
//Display portal information as a portal without a sign does not display any
if (portal.getOptions().hasNoSign() && (!portal.getOptions().isSilent() || player.isSneaking())) {
MessageSender sender = Stargate.getMessageSender();
sender.sendSuccessMessage(player, ChatColor.GOLD + Stargate.getString("portalInfoTitle"));
sender.sendSuccessMessage(player, Stargate.replaceVars(Stargate.getString("portalInfoName"),
"%name%", portal.getName()));
sender.sendSuccessMessage(player, Stargate.replaceVars(Stargate.getString("portalInfoDestination"),
"%destination%", portal.getDestinationName()));
if (portal.getOptions().isBungee()) {
sender.sendSuccessMessage(player, Stargate.replaceVars(Stargate.getString("portalInfoServer"),
"%server%", portal.getNetwork()));
} else {
sender.sendSuccessMessage(player, Stargate.replaceVars(Stargate.getString("portalInfoNetwork"),
"%network%", portal.getNetwork()));
}
}
}
/**
* This function decides if a right click of a block is caused by a Spigot bug
*
* <p>The Spigot bug currently makes every right click of some blocks trigger twice, causing the portal to close
* immediately, or causing portal information printing twice. This fix should detect the bug without breaking
* clicking once the bug is fixed.</p>
*
* @param player <p>The player performing the right-click</p>
* @param block <p>The block to check</p>
* @return <p>True if the click is a bug and should be cancelled</p>
*/
private boolean clickIsBug(Player player, Block block) {
Long previousEventTime = previousEventTimes.get(player);
if (previousEventTime != null && previousEventTime + 50 > System.currentTimeMillis()) {
previousEventTimes.put(player, null);
return true;
}
previousEventTimes.put(player, System.currentTimeMillis());
return false;
}
}

View File

@@ -0,0 +1,53 @@
package net.knarcraft.stargate.listener;
import net.knarcraft.stargate.Stargate;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.event.server.PluginEnableEvent;
/**
* This listener listens for any plugins being enabled or disabled to catch the loading of vault
*/
@SuppressWarnings("unused")
public class PluginEventListener implements Listener {
private final Stargate stargate;
/**
* Instantiates a new plugin event listener
*
* @param stargate <p>A reference to the stargate plugin to </p>
*/
public PluginEventListener(Stargate stargate) {
this.stargate = stargate;
}
/**
* This event listens for and announces that the vault plugin was detected and enabled
*
* <p>Each time this event is called, the economy handler will try to enable vault</p>
*
* @param ignored <p>The actual event called. This is currently not used</p>
*/
@EventHandler
public void onPluginEnable(PluginEnableEvent ignored) {
if (Stargate.getEconomyConfig().setupEconomy(stargate.getServer().getPluginManager())) {
String vaultVersion = Stargate.getEconomyConfig().getVault().getDescription().getVersion();
Stargate.logInfo(Stargate.replaceVars(Stargate.getString("vaultLoaded"), "%version%", vaultVersion));
}
}
/**
* This event listens for the vault plugin being disabled and notifies the console
*
* @param event <p>The event caused by disabling a plugin</p>
*/
@EventHandler
public void onPluginDisable(PluginDisableEvent event) {
if (event.getPlugin().equals(Stargate.getEconomyConfig().getVault())) {
Stargate.logInfo("Vault plugin lost.");
}
}
}

View File

@@ -0,0 +1,122 @@
package net.knarcraft.stargate.listener;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.FromTheEndTeleportation;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalHandler;
import net.knarcraft.stargate.portal.teleporter.PlayerTeleporter;
import net.knarcraft.stargate.utility.PermissionHelper;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityPortalEnterEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.event.world.PortalCreateEvent;
import java.util.ArrayList;
import java.util.List;
/**
* Listens for and cancels relevant portal events
*/
public class PortalEventListener implements Listener {
private static final List<FromTheEndTeleportation> playersFromTheEnd = new ArrayList<>();
/**
* Listens for and aborts vanilla portal creation caused by stargate creation
*
* @param event <p>The triggered event</p>
*/
@EventHandler
public void onPortalCreation(PortalCreateEvent event) {
if (event.isCancelled()) {
return;
}
//Unnecessary nether portal creation is only triggered by nether pairing
if (event.getReason() == PortalCreateEvent.CreateReason.NETHER_PAIR) {
//If an entity is standing in a Stargate entrance, it can be assumed that the creation is a mistake
Entity entity = event.getEntity();
if (entity != null && PortalHandler.getByAdjacentEntrance(entity.getLocation()) != null) {
Stargate.debug("PortalEventListener::onPortalCreation",
"Cancelled nether portal create event");
event.setCancelled(true);
}
}
}
/**
* Listen for entities entering an artificial end portal
*
* @param event <p>The triggered event</p>
*/
@EventHandler
public void onEntityPortalEnter(EntityPortalEnterEvent event) {
Location location = event.getLocation();
World world = location.getWorld();
Entity entity = event.getEntity();
//Hijack normal portal teleportation if teleporting from a stargate
if (entity instanceof Player player && location.getBlock().getType() == Material.END_PORTAL && world != null &&
world.getEnvironment() == World.Environment.THE_END) {
Portal portal = PortalHandler.getByAdjacentEntrance(location);
if (portal == null) {
return;
}
Stargate.debug("PortalEventListener::onEntityPortalEnter",
"Found player " + player + " entering END_PORTAL " + portal);
//Remove any old player teleportations in case weird things happen
playersFromTheEnd.removeIf((teleportation -> teleportation.getPlayer() == player));
//Decide if the anything stops the player from teleporting
if (PermissionHelper.playerCannotTeleport(portal, portal.getPortalActivator().getDestination(), player, null) ||
portal.getOptions().isBungee()) {
//Teleport the player back to the portal they came in, just in case
playersFromTheEnd.add(new FromTheEndTeleportation(player, portal));
Stargate.debug("PortalEventListener::onEntityPortalEnter",
"Sending player back to the entrance");
} else {
playersFromTheEnd.add(new FromTheEndTeleportation(player, portal.getPortalActivator().getDestination()));
Stargate.debug("PortalEventListener::onEntityPortalEnter",
"Sending player to destination");
}
}
}
/**
* Listen for the respawn event to catch players teleporting from the end in an artificial end portal
*
* @param event <p>The triggered event</p>
*/
@EventHandler
public void onRespawn(PlayerRespawnEvent event) {
Player respawningPlayer = event.getPlayer();
int playerIndex = playersFromTheEnd.indexOf(new FromTheEndTeleportation(respawningPlayer, null));
if (playerIndex == -1) {
return;
}
FromTheEndTeleportation teleportation = playersFromTheEnd.get(playerIndex);
playersFromTheEnd.remove(playerIndex);
Portal exitPortal = teleportation.getExit();
//Overwrite respawn location to respawn in front of the portal
PlayerTeleporter teleporter = new PlayerTeleporter(exitPortal, respawningPlayer);
Location respawnLocation = teleporter.getExit();
event.setRespawnLocation(respawnLocation);
//Try and force the player if for some reason the changing of respawn location isn't properly handled
Bukkit.getScheduler().scheduleSyncDelayedTask(Stargate.getInstance(), () ->
respawningPlayer.teleport(respawnLocation), 1);
//Properly close the portal to prevent it from staying in a locked state until it times out
exitPortal.getPortalOpener().closePortal(false);
Stargate.debug("PortalEventListener::onRespawn", "Overwriting respawn for " + respawningPlayer +
" to " + respawnLocation.getWorld() + ":" + respawnLocation);
}
}

View File

@@ -0,0 +1,36 @@
package net.knarcraft.stargate.listener;
import net.knarcraft.stargate.portal.PortalHandler;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerTeleportEvent;
/**
* This listener listens to teleportation-related events
*/
@SuppressWarnings("unused")
public class TeleportEventListener implements Listener {
/**
* This event handler handles some special teleportation events
*
* <p>This event cancels nether portal, end gateway and end portal teleportation if the user teleported from a
* stargate entrance. This prevents the user from just teleporting to the nether or the end with portals using
* the special teleportation blocks.</p>
*
* @param event <p>The event to check and possibly cancel</p>
*/
@EventHandler
public void onPlayerTeleport(PlayerTeleportEvent event) {
PlayerTeleportEvent.TeleportCause cause = event.getCause();
//Block normal portal teleportation if teleporting from a stargate
if (!event.isCancelled() && (cause == PlayerTeleportEvent.TeleportCause.NETHER_PORTAL ||
cause == PlayerTeleportEvent.TeleportCause.END_GATEWAY ||
cause == PlayerTeleportEvent.TeleportCause.END_PORTAL)
&& PortalHandler.getByAdjacentEntrance(event.getFrom()) != null) {
event.setCancelled(true);
}
}
}

View File

@@ -0,0 +1,147 @@
package net.knarcraft.stargate.listener;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalHandler;
import net.knarcraft.stargate.portal.teleporter.VehicleTeleporter;
import net.knarcraft.stargate.utility.EconomyHelper;
import net.knarcraft.stargate.utility.EntityHelper;
import net.knarcraft.stargate.utility.TeleportHelper;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.entity.Vehicle;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.vehicle.VehicleMoveEvent;
import java.util.List;
/**
* This listener listens for the vehicle move event to teleport vehicles through portals
*/
@SuppressWarnings("unused")
public class VehicleEventListener implements Listener {
/**
* Check for a vehicle moving through a portal
*
* @param event <p>The triggered move event</p>
*/
@EventHandler
public void onVehicleMove(VehicleMoveEvent event) {
if (!Stargate.getGateConfig().handleVehicles()) {
return;
}
List<Entity> passengers = event.getVehicle().getPassengers();
Vehicle vehicle = event.getVehicle();
Portal entrancePortal;
int entitySize = EntityHelper.getEntityMaxSizeInt(vehicle);
if (EntityHelper.getEntityMaxSize(vehicle) > 1) {
entrancePortal = PortalHandler.getByAdjacentEntrance(event.getTo(), entitySize - 1);
} else {
entrancePortal = PortalHandler.getByEntrance(event.getTo());
}
//Return if the portal cannot be teleported through
if (entrancePortal == null || !entrancePortal.isOpen() || entrancePortal.getOptions().isBungee()) {
return;
}
teleportVehicle(passengers, entrancePortal, vehicle);
}
/**
* Teleports a vehicle through a stargate
*
* @param passengers <p>The passengers inside the vehicle</p>
* @param entrancePortal <p>The portal the vehicle is entering</p>
* @param vehicle <p>The vehicle passing through</p>
*/
private static void teleportVehicle(List<Entity> passengers, Portal entrancePortal, Vehicle vehicle) {
String route = "VehicleEventListener::teleportVehicle";
if (!passengers.isEmpty() && TeleportHelper.containsPlayer(passengers)) {
Stargate.debug(route, "Found passenger vehicle");
teleportPlayerAndVehicle(entrancePortal, vehicle);
} else {
Stargate.debug(route, "Found vehicle without players");
Portal destinationPortal = entrancePortal.getPortalActivator().getDestination();
if (destinationPortal == null) {
Stargate.debug(route, "Unable to find portal destination");
return;
}
Stargate.debug("vehicleTeleport", destinationPortal.getWorld() + " " +
destinationPortal.getSignLocation());
new VehicleTeleporter(destinationPortal, vehicle).teleportEntity(entrancePortal);
}
}
/**
* Teleports a player and the vehicle the player sits in
*
* @param entrancePortal <p>The portal the minecart entered</p>
* @param vehicle <p>The vehicle to teleport</p>
*/
private static void teleportPlayerAndVehicle(Portal entrancePortal, Vehicle vehicle) {
Entity rootEntity = vehicle;
while (rootEntity.getVehicle() != null) {
rootEntity = rootEntity.getVehicle();
}
List<Player> players = TeleportHelper.getPlayers(rootEntity.getPassengers());
Portal destinationPortal = null;
for (Player player : players) {
//The entrance portal must be open for one player for the teleportation to happen
if (!entrancePortal.getPortalOpener().isOpenFor(player)) {
continue;
}
//Check if any of the players has selected the destination
Portal possibleDestinationPortal = entrancePortal.getPortalActivator().getDestination(player);
if (possibleDestinationPortal != null) {
destinationPortal = possibleDestinationPortal;
}
}
//Cancel the teleport if no players activated the portal, or if any players are denied access
boolean cancelTeleport = false;
for (Player player : players) {
if (destinationPortal == null) {
cancelTeleport = true;
if (!entrancePortal.getOptions().isSilent()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("invalidMsg"));
}
} else if (!TeleportHelper.playerCanTeleport(player, entrancePortal, destinationPortal)) {
cancelTeleport = true;
}
}
if (cancelTeleport) {
return;
}
//Take payment from all players
for (Player player : players) {
//To prevent the case where the first passenger pays and then the second passenger is denied, this has to be
// run after it has been confirmed that all passengers are able to pay
int cost = EconomyHelper.getUseCost(player, entrancePortal, destinationPortal);
if (cost > 0) {
if (EconomyHelper.cannotPayTeleportFee(entrancePortal, player, cost)) {
return;
}
}
}
//Teleport the vehicle and inform the user if the vehicle was teleported
boolean teleported = new VehicleTeleporter(destinationPortal, vehicle).teleportEntity(entrancePortal);
if (teleported) {
if (!entrancePortal.getOptions().isSilent()) {
for (Player player : players) {
Stargate.getMessageSender().sendSuccessMessage(player, Stargate.getString("teleportMsg"));
}
}
entrancePortal.getPortalOpener().closePortal(false);
}
}
}

View File

@@ -0,0 +1,50 @@
package net.knarcraft.stargate.listener;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.config.StargateConfig;
import net.knarcraft.stargate.portal.PortalRegistry;
import net.knarcraft.stargate.utility.PortalFileHelper;
import org.bukkit.World;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.world.WorldLoadEvent;
import org.bukkit.event.world.WorldUnloadEvent;
/**
* This listener listens for the loading and unloading of worlds to load and unload stargates
*/
@SuppressWarnings("unused")
public class WorldEventListener implements Listener {
/**
* This listener listens for the loading of a world and loads all gates from the world if not already loaded
*
* @param event <p>The triggered world load event</p>
*/
@EventHandler
public void onWorldLoad(WorldLoadEvent event) {
StargateConfig config = Stargate.getStargateConfig();
if (!config.getManagedWorlds().contains(event.getWorld().getName()) &&
PortalFileHelper.loadAllPortals(event.getWorld())) {
config.addManagedWorld(event.getWorld().getName());
}
}
/**
* This listener listens for the unloading of a world
*
* @param event <p>The triggered world unload event</p>
*/
@EventHandler
public void onWorldUnload(WorldUnloadEvent event) {
Stargate.debug("onWorldUnload", "Reloading all Stargates");
World world = event.getWorld();
String worldName = world.getName();
StargateConfig config = Stargate.getStargateConfig();
if (config.getManagedWorlds().contains(worldName)) {
config.removeManagedWorld(worldName);
PortalRegistry.clearPortals(world);
}
}
}

View File

@@ -0,0 +1,4 @@
/**
* The root package of the Stargate plugin. Contains the main Stargate.java file
*/
package net.knarcraft.stargate;

View File

@@ -0,0 +1,349 @@
package net.knarcraft.stargate.portal;
import net.knarcraft.stargate.container.BlockLocation;
import net.knarcraft.stargate.container.RelativeBlockVector;
import net.knarcraft.stargate.portal.property.PortalLocation;
import net.knarcraft.stargate.portal.property.PortalOption;
import net.knarcraft.stargate.portal.property.PortalOptions;
import net.knarcraft.stargate.portal.property.PortalOwner;
import net.knarcraft.stargate.portal.property.PortalStructure;
import net.knarcraft.stargate.portal.property.gate.Gate;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.World;
import org.bukkit.entity.Player;
import java.util.Map;
/**
* This class represents a portal in space which points to one or several portals
*/
public class Portal {
private final String name;
private final String cleanName;
private final String network;
private final String cleanNetwork;
private final PortalOwner portalOwner;
private boolean isRegistered;
private final PortalOptions options;
private final PortalOpener portalOpener;
private final PortalLocation location;
private final PortalSignDrawer signDrawer;
private final PortalStructure structure;
private final PortalActivator portalActivator;
/**
* Instantiates a new portal
*
* @param portalLocation <p>Object containing locations of all relevant blocks</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. "" for non-fixed gates</p>
* @param name <p>The name of the portal defined on the sign's first line</p>
* @param network <p>The network the portal belongs to, defined on the sign's third</p>
* @param gate <p>The gate type to use for this portal</p>
* @param portalOwner <p>The portal's owner</p>
* @param options <p>A map containing all possible portal options, with true for the ones enabled</p>
*/
public Portal(PortalLocation portalLocation, BlockLocation button, String destination, String name, String network,
Gate gate, PortalOwner portalOwner, Map<PortalOption, Boolean> options) {
this.location = portalLocation;
this.network = network;
this.name = name;
this.portalOwner = portalOwner;
this.options = new PortalOptions(options, destination.length() > 0);
this.signDrawer = new PortalSignDrawer(this);
this.portalOpener = new PortalOpener(this, destination);
this.structure = new PortalStructure(this, gate, button);
this.portalActivator = portalOpener.getPortalActivator();
this.cleanName = cleanString(name);
this.cleanNetwork = cleanString(network);
}
/**
* Checks if this portal is registered
*
* @return <p>True if this portal is registered</p>
*/
public boolean isRegistered() {
return isRegistered;
}
/**
* Sets whether this portal is registered
*
* @param isRegistered <p>True if this portal is registered</p>
*/
public void setRegistered(boolean isRegistered) {
this.isRegistered = isRegistered;
}
/**
* Gets the location data for this portal
*
* @return <p>This portal's location data</p>
*/
public PortalLocation getLocation() {
return this.location;
}
/**
* Gets the structure of this portal
*
* <p>The structure contains information about the portal's gate, button and real locations of frames and
* entrances. The structure is also responsible for verifying built StarGates to make sure they match the gate.</p>
*
* @return <p>This portal's structure</p>
*/
public PortalStructure getStructure() {
return this.structure;
}
/**
* Gets this portal's activator
*
* <p>The activator is responsible for activating/de-activating the portal and contains information about
* available destinations and which player activated the portal.</p>
*
* @return <p>This portal's activator</p>
*/
public PortalActivator getPortalActivator() {
return this.portalActivator;
}
/**
* Re-draws the sign on this portal
*/
public void drawSign() {
this.signDrawer.drawSign();
}
/**
* Gets the portal options for this portal
*
* @return <p>This portal's portal options</p>
*/
public PortalOptions getOptions() {
return this.options;
}
/**
* Gets whether this portal is currently open
*
* @return <p>Whether this portal is open</p>
*/
public boolean isOpen() {
return portalOpener.isOpen();
}
/**
* Gets the player currently using this portal
*
* @return <p>The player currently using this portal</p>
*/
public Player getActivePlayer() {
return portalActivator.getActivePlayer();
}
/**
* Gets the network this portal belongs to
*
* @return <p>The network this portal belongs to</p>
*/
public String getNetwork() {
return network;
}
/**
* Gets the clean name of the network this portal belongs to
*
* @return <p>The clean network name</p>
*/
public String getCleanNetwork() {
return cleanNetwork;
}
/**
* Gets the time this portal was triggered (activated/opened)
*
* <p>The time is given in the equivalent of a Unix timestamp. It's used to decide when a portal times out and
* automatically closes/deactivates.</p>
*
* @return <p>The time this portal was triggered (activated/opened)</p>
*/
public long getTriggeredTime() {
return portalOpener.getTriggeredTime();
}
/**
* Gets the name of this portal
*
* @return <p>The name of this portal</p>
*/
public String getName() {
return name;
}
/**
* Gets the clean name of this portal
*
* @return <p>The clean name of this portal</p>
*/
public String getCleanName() {
return cleanName;
}
/**
* Gets the portal opener used by this portal
*
* <p>The portal opener is responsible for opening and closing this portal.</p>
*
* @return <p>This portal's portal opener</p>
*/
public PortalOpener getPortalOpener() {
return portalOpener;
}
/**
* Gets the name of this portal's destination portal
*
* @return <p>The name of this portal's destination portal</p>
*/
public String getDestinationName() {
return portalOpener.getPortalActivator().getDestinationName();
}
/**
* Gets the gate type used by this portal
*
* @return <p>The gate type used by this portal</p>
*/
public Gate getGate() {
return structure.getGate();
}
/**
* Gets this portal's owner
*
* <p>The owner is the player which created the portal.</p>
*
* @return <p>This portal's owner</p>
*/
public PortalOwner getOwner() {
return portalOwner;
}
/**
* Checks whether a given player is the owner of this portal
*
* @param player <p>The player to check</p>
* @return <p>True if the player is the owner of this portal</p>
*/
public boolean isOwner(Player player) {
if (this.portalOwner.getUUID() != null) {
return player.getUniqueId().compareTo(this.portalOwner.getUUID()) == 0;
} else {
return player.getName().equalsIgnoreCase(this.portalOwner.getName());
}
}
/**
* Gets the world this portal belongs to
*
* @return <p>The world this portal belongs to</p>
*/
public World getWorld() {
return location.getWorld();
}
/**
* Gets the location of this portal's sign
*
* @return <p>The location of this portal's sign</p>
*/
public BlockLocation getSignLocation() {
return this.location.getSignLocation();
}
/**
* Gets the rotation (yaw) of this portal
*
* <p>The yaw is used to calculate all kinds of directions. See DirectionHelper to see how the yaw is used to
* calculate to/from other direction types.</p>
*
* @return <p>The rotation (yaw) of this portal</p>
*/
public float getYaw() {
return this.location.getYaw();
}
/**
* 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.location.getTopLeft();
}
/**
* Gets the block at the given location relative to this portal's top-left block
*
* @param vector <p>The relative block vector explaining the position of the block</p>
* @return <p>The block at the given relative position</p>
*/
public BlockLocation getBlockAt(RelativeBlockVector vector) {
return getTopLeft().getRelativeLocation(vector, getYaw());
}
/**
* Cleans a string by removing color codes, lower-casing and replacing spaces with underscores
*
* @param string <p>The string to clean</p>
* @return <p>The clean string</p>
*/
public static String cleanString(String string) {
return ChatColor.stripColor(ChatColor.translateAlternateColorCodes('&', string)).toLowerCase();
}
@Override
public String toString() {
return String.format("Portal [id=%s, network=%s name=%s, type=%s]", getSignLocation(), network, name,
structure.getGate().getFilename());
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((cleanName == null) ? 0 : cleanName.hashCode());
result = prime * result + ((cleanNetwork == null) ? 0 : cleanNetwork.hashCode());
return result;
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object == null || getClass() != object.getClass()) {
return false;
}
Portal other = (Portal) object;
if (cleanName == null) {
if (other.cleanName != null) {
return false;
}
} else if (!cleanName.equalsIgnoreCase(other.cleanName)) {
return false;
}
//If none of the portals have a name, check if the network is the same
if (cleanNetwork == null) {
return other.cleanNetwork == null;
} else {
return cleanNetwork.equalsIgnoreCase(other.cleanNetwork);
}
}
}

View File

@@ -0,0 +1,288 @@
package net.knarcraft.stargate.portal;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.event.StargateActivateEvent;
import net.knarcraft.stargate.event.StargateDeactivateEvent;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
/**
* The portal activator activates/de-activates portals and keeps track of a portal's destinations
*
* <p>The portal activator is responsible for activating/de-activating the portal and contains information about
* available destinations and which player activated the portal.</p>
*/
public class PortalActivator {
private final Portal portal;
private final PortalOpener opener;
private List<String> destinations = new ArrayList<>();
private String destination;
private String lastDestination = "";
private Player activePlayer;
/**
* Instantiates a new portal destinations object
*
* @param portal <p>The portal which this this object stores destinations for</p>
* @param portalOpener <p>The portal opener to trigger when the activation causes the portal to open</p>
* @param destination <p>The fixed destination specified on the portal's sign</p>
*/
public PortalActivator(Portal portal, PortalOpener portalOpener, String destination) {
this.portal = portal;
this.opener = portalOpener;
this.destination = destination;
}
/**
* Gets the player which this activator's portal is currently activated for
*
* @return <p>The player this activator's portal is currently activated for</p>
*/
public Player getActivePlayer() {
return activePlayer;
}
/**
* Gets the available portal destinations
*
* @return <p>The available portal destinations</p>
*/
public List<String> getDestinations() {
return new ArrayList<>(this.destinations);
}
/**
* Gets the portal destination given a player
*
* @param player <p>Used for random gates to determine which destinations are available</p>
* @return <p>The destination portal the player should teleport to</p>
*/
public Portal getDestination(Player player) {
String portalNetwork = portal.getCleanNetwork();
if (portal.getOptions().isRandom()) {
//Find possible destinations
List<String> destinations = PortalHandler.getDestinations(portal, player, portalNetwork);
if (destinations.size() == 0) {
return null;
}
//Get one random destination
String destination = destinations.get((new Random()).nextInt(destinations.size()));
return PortalHandler.getByName(Portal.cleanString(destination), portalNetwork);
} else {
//Just return the normal fixed destination
return PortalHandler.getByName(Portal.cleanString(destination), portalNetwork);
}
}
/**
* Gets the portal's destination
*
* <p>For random portals, getDestination must be given a player to decide which destinations are valid. Without a
* player, or with a null player, behavior is only defined for a non-random gate.</p>
*
* @return <p>The portal destination</p>
*/
public Portal getDestination() {
return getDestination(null);
}
/**
* Sets the destination of this portal activator's portal
*
* @param destination <p>The new destination of this portal activator's portal</p>
*/
public void setDestination(Portal destination) {
setDestination(destination.getName());
}
/**
* Sets the destination of this portal activator's portal
*
* @param destination <p>The new destination of this portal activator's portal</p>
*/
public void setDestination(String destination) {
this.destination = destination;
}
/**
* Gets the name of the selected destination
*
* @return <p>The name of the selected destination</p>
*/
public String getDestinationName() {
return destination;
}
/**
* Activates this activator's 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>
*/
boolean activate(Player player) {
//Clear previous destination data
this.destination = "";
this.destinations.clear();
//Adds the active gate to the active queue to allow it to be remotely deactivated
Stargate.getStargateConfig().getActivePortalsQueue().add(portal);
//Set the given player as the active player
activePlayer = player;
String network = portal.getCleanNetwork();
destinations = PortalHandler.getDestinations(portal, player, network);
//Sort destinations if enabled
if (Stargate.getGateConfig().sortNetworkDestinations()) {
destinations.sort(Comparator.comparing(Portal::cleanString));
}
//Select last used destination if remember destination is enabled
if (Stargate.getGateConfig().rememberDestination() && !lastDestination.isEmpty() &&
destinations.contains(lastDestination)) {
destination = lastDestination;
}
//Trigger an activation event to allow the cancellation to be cancelled
return triggerStargateActivationEvent(player);
}
/**
* Triggers a stargate activation event to allow other plugins to cancel the activation
*
* <p>The event may also end up changing destinations.</p>
*
* @param player <p>The player trying to activate this activator's portal</p>
* @return <p>True if the portal was activated. False otherwise</p>
*/
private boolean triggerStargateActivationEvent(Player player) {
StargateActivateEvent event = new StargateActivateEvent(portal, player, destinations, destination);
Stargate.getInstance().getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
Stargate.getStargateConfig().getActivePortalsQueue().remove(portal);
return false;
}
//Update destinations in case they changed, and update the sign
destination = event.getDestination();
destinations = event.getDestinations();
portal.drawSign();
return true;
}
/**
* Deactivates this portal
*/
public void deactivate() {
//Trigger a stargate deactivate event to allow other plugins to cancel the event
StargateDeactivateEvent event = new StargateDeactivateEvent(portal);
Stargate.getInstance().getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
}
//Un-mark the portal as activated
Stargate.getStargateConfig().getActivePortalsQueue().remove(portal);
//Fixed portals are active by definition, but should never be de-activated
if (portal.getOptions().isFixed()) {
return;
}
//Clear destinations and the active player before re-drawing the sign to show that it's deactivated
destinations.clear();
destination = "";
activePlayer = null;
portal.drawSign();
}
/**
* Gets whether this portal activator's portal is active
*
* @return <p>Whether this portal activator's portal is active</p>
*/
public boolean isActive() {
return portal.getOptions().isFixed() || (destinations.size() > 0);
}
/**
* Cycles destination for a non-fixed gate by one forwards step
*
* @param player <p>The player to cycle the gate for</p>
*/
public void cycleDestination(Player player) {
cycleDestination(player, 1);
}
/**
* Cycles destination for a non-fixed gate
*
* @param player <p>The player cycling destinations</p>
* @param direction <p>The direction of the cycle (+1 for next, -1 for previous)</p>
*/
public void cycleDestination(Player player, int direction) {
//Only allow going exactly one step in either direction
if (direction != 1 && direction != -1) {
throw new IllegalArgumentException("The destination direction must be 1 or -1.");
}
boolean activate = false;
if (!isActive() || getActivePlayer() != player) {
//If not active or not active for the given player, and the activation is denied, just abort
if (!activate(player)) {
return;
}
activate = true;
Stargate.debug("cycleDestination", "Network Size: " +
PortalHandler.getNetwork(portal.getCleanNetwork()).size());
Stargate.debug("cycleDestination", "Player has access to: " + destinations.size());
}
//If no destinations are available, just tell the player and quit
if (destinations.size() == 0) {
if (!portal.getOptions().isSilent()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("destEmpty"));
}
return;
}
//Cycle if destination remembering is disabled, if the portal was already active, or it has no last destination
if (!Stargate.getGateConfig().rememberDestination() || !activate || lastDestination.isEmpty()) {
cycleDestination(direction);
}
//Update the activated time to allow it to be deactivated after a timeout, and re-draw the sign to show the
// selected destination
opener.setTriggeredTime(System.currentTimeMillis() / 1000);
portal.drawSign();
}
/**
* Performs the actual destination cycling with no input checks
*
* @param direction <p>The direction of the cycle (+1 for next, -1 for previous)</p>
*/
private void cycleDestination(int direction) {
int index = destinations.indexOf(destination);
index += direction;
//Wrap around if the last destination has been reached
if (index >= destinations.size()) {
index = 0;
} else if (index < 0) {
index = destinations.size() - 1;
}
//Store selected destination
destination = destinations.get(index);
lastDestination = destination;
}
}

View File

@@ -0,0 +1,327 @@
package net.knarcraft.stargate.portal;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.BlockLocation;
import net.knarcraft.stargate.container.RelativeBlockVector;
import net.knarcraft.stargate.event.StargateCreateEvent;
import net.knarcraft.stargate.portal.property.PortalLocation;
import net.knarcraft.stargate.portal.property.PortalOption;
import net.knarcraft.stargate.portal.property.PortalOptions;
import net.knarcraft.stargate.portal.property.PortalOwner;
import net.knarcraft.stargate.portal.property.gate.Gate;
import net.knarcraft.stargate.portal.property.gate.GateHandler;
import net.knarcraft.stargate.utility.DirectionHelper;
import net.knarcraft.stargate.utility.EconomyHelper;
import net.knarcraft.stargate.utility.PermissionHelper;
import net.knarcraft.stargate.utility.PortalFileHelper;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Player;
import org.bukkit.event.block.SignChangeEvent;
import java.util.List;
import java.util.Map;
import static net.knarcraft.stargate.Stargate.getMaxNameNetworkLength;
/**
* The portal creator can create and validate a new portal
*/
public class PortalCreator {
private Portal portal;
private final SignChangeEvent event;
private final Player player;
/**
* Instantiates a new portal creator
*
* @param event <p>The sign change event which initialized the creation</p>
* @param player <p>The player creating the portal</p>
*/
public PortalCreator(SignChangeEvent event, Player player) {
this.event = event;
this.player = player;
}
/**
* Creates a new portal
*
* @return <p>The created portal</p>
*/
public Portal createPortal() {
BlockLocation signLocation = new BlockLocation(event.getBlock());
Block signControlBlock = signLocation.getParent();
//Return early if the sign is not placed on a block, or the block is not a control block
if (signControlBlock == null || GateHandler.getGatesByControlBlock(signControlBlock).length == 0) {
Stargate.debug("createPortal", "Control block not registered");
return null;
}
//The control block is already part of another portal
if (PortalHandler.getByBlock(signControlBlock) != null) {
Stargate.debug("createPortal", "idParent belongs to existing stargate");
return null;
}
//Get necessary information from the gate's sign
String portalName = PortalHandler.filterName(event.getLine(0));
String destinationName = PortalHandler.filterName(event.getLine(1));
String network = PortalHandler.filterName(event.getLine(2));
String options = PortalHandler.filterName(event.getLine(3)).toLowerCase();
//Get portal options available to the player creating the portal
Map<PortalOption, Boolean> portalOptions = PortalHandler.getPortalOptions(player, destinationName, options);
//Get the yaw
float yaw = DirectionHelper.getYawFromLocationDifference(signControlBlock.getLocation(),
signLocation.getLocation());
//Get the direction the button should be facing
BlockFace buttonFacing = DirectionHelper.getBlockFaceFromYaw(yaw);
PortalLocation portalLocation = new PortalLocation();
portalLocation.setButtonFacing(buttonFacing).setYaw(yaw).setSignLocation(signLocation);
Stargate.debug("createPortal", "Finished getting all portal info");
//Try and find a gate matching the new portal
Gate gate = PortalHandler.findMatchingGate(portalLocation, player.getWorld());
if ((gate == null) || (portalLocation.getButtonVector() == null)) {
Stargate.debug("createPortal", "Could not find matching gate layout");
return null;
}
//If the portal is a bungee portal and invalid, abort here
if (!PortalHandler.isValidBungeePortal(portalOptions, player, destinationName, network)) {
Stargate.debug("createPortal", "Portal is an invalid bungee portal");
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());
//Use default network if a proper alternative is not set
if (!portalOptions.get(PortalOption.BUNGEE) && (network.length() < 1 || network.length() >
getMaxNameNetworkLength())) {
network = Stargate.getDefaultNetwork();
}
boolean deny = false;
String denyMessage = "";
//Check if the player can create portals on this network. If not, create a personal portal
if (!portalOptions.get(PortalOption.BUNGEE) && !PermissionHelper.canCreateNetworkGate(player, network)) {
Stargate.debug("createPortal", "Player doesn't have create permissions on network. Trying personal");
if (PermissionHelper.canCreatePersonalPortal(player)) {
network = player.getName();
if (network.length() > getMaxNameNetworkLength()) {
network = network.substring(0, getMaxNameNetworkLength());
}
Stargate.debug("createPortal", "Creating personal portal");
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("createPersonal"));
} else {
Stargate.debug("createPortal", "Player does not have access to network");
deny = true;
denyMessage = Stargate.getString("createNetDeny");
}
}
//Check if the player can create this gate layout
String gateName = gate.getFilename();
gateName = gateName.substring(0, gateName.indexOf('.'));
if (!deny && !PermissionHelper.canCreatePortal(player, gateName)) {
Stargate.debug("createPortal", "Player does not have access to gate layout");
deny = true;
denyMessage = Stargate.getString("createGateDeny");
}
//Check if the user can create portals to this world.
if (!portalOptions.get(PortalOption.BUNGEE) && !deny && destinationName.length() > 0) {
Portal portal = PortalHandler.getByName(destinationName, network);
if (portal != null) {
String world = portal.getWorld().getName();
if (PermissionHelper.cannotAccessWorld(player, world)) {
Stargate.debug("canCreateNetworkGate", "Player does not have access to destination world");
deny = true;
denyMessage = Stargate.getString("createWorldDeny");
}
}
}
//Check if a conflict exists
if (conflictsWithExistingPortal(gate, portalLocation.getTopLeft(), yaw, player)) {
return null;
}
PortalOwner owner = new PortalOwner(player);
this.portal = new Portal(portalLocation, null, destinationName, portalName, network, gate, owner,
portalOptions);
return validatePortal(denyMessage, event.getLines(), deny);
}
/**
* Validates the newly created portal assigned to this portal validator
*
* @param denyMessage <p>The deny message to displayed if the creation has already been denied</p>
* @param lines <p>The lines on the sign causing the portal to be created</p>
* @param deny <p>Whether the portal creation has already been denied</p>
* @return <p>The portal or null if its creation was denied</p>
*/
public Portal validatePortal(String denyMessage, String[] lines, boolean deny) {
PortalLocation portalLocation = portal.getLocation();
Gate gate = portal.getStructure().getGate();
PortalOptions portalOptions = portal.getOptions();
String portalName = portal.getName();
String destinationName = portal.getDestinationName();
int createCost = Stargate.getEconomyConfig().getCreateCost(player, gate);
//Call StargateCreateEvent to let other plugins cancel or overwrite denial
StargateCreateEvent stargateCreateEvent = new StargateCreateEvent(player, portal, lines, deny,
denyMessage, createCost);
Stargate.getInstance().getServer().getPluginManager().callEvent(stargateCreateEvent);
if (stargateCreateEvent.isCancelled()) {
return null;
}
//Tell the user why it was denied from creating the portal
if (stargateCreateEvent.getDeny()) {
if (!stargateCreateEvent.getDenyReason().trim().isEmpty()) {
Stargate.getMessageSender().sendErrorMessage(player, stargateCreateEvent.getDenyReason());
}
return null;
}
createCost = stargateCreateEvent.getCost();
//Check if the new portal is valid
if (!checkIfNewPortalIsValid(createCost, portalName)) {
return null;
}
//Add button if the portal is not always on
if (!portalOptions.isAlwaysOn()) {
PortalFileHelper.generatePortalButton(portal, portalLocation.getButtonFacing());
}
//Register the new portal
PortalHandler.registerPortal(portal);
updateNewPortalOpenState(destinationName);
//Update portals pointing at this one if it's not a bungee portal
if (!portal.getOptions().isBungee()) {
PortalHandler.updatePortalsPointingAtNewPortal(portal);
}
PortalFileHelper.saveAllPortals(portal.getWorld());
return portal;
}
/**
* Checks whether the newly created, but unregistered portal is valid
*
* @param cost <p>The cost of creating the portal</p>
* @param portalName <p>The name of the newly created portal</p>
* @return <p>True if the portal is completely valid</p>
*/
private boolean checkIfNewPortalIsValid(int cost, String portalName) {
//Check if the portal name can fit on the sign with padding (>name<)
if (portal.getCleanName().length() < 1 || portal.getCleanName().length() > getMaxNameNetworkLength()) {
Stargate.debug("createPortal", String.format("Name length error. %s is too long.",
portal.getCleanName()));
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("createNameLength"));
return false;
}
if (portal.getOptions().isBungee()) {
//Check if the bungee portal's name has been duplicated
if (PortalHandler.getBungeePortals().get(portal.getCleanName()) != null) {
Stargate.debug("createPortal::Bungee", "Gate name duplicate");
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("createExists"));
return false;
}
} else {
//Check if the portal name has been duplicated on the network
if (PortalHandler.getByName(portal.getCleanName(), portal.getCleanNetwork()) != null) {
Stargate.debug("createPortal", "Gate name duplicate");
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("createExists"));
return false;
}
//Check if the number of portals in the network has been surpassed
List<String> networkList = PortalHandler.getAllPortalNetworks().get(portal.getCleanNetwork());
int maxGates = Stargate.getGateConfig().maxGatesEachNetwork();
if (maxGates > 0 && networkList != null && networkList.size() >= maxGates) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("createFull"));
return false;
}
}
if (cost > 0) {
//Deduct the required fee from the player
if (!EconomyHelper.chargePlayerIfNecessary(player, cost)) {
EconomyHelper.sendInsufficientFundsMessage(portalName, player, cost);
Stargate.debug("createPortal", "Insufficient Funds");
return false;
} else {
EconomyHelper.sendDeductMessage(portalName, player, cost);
}
}
return true;
}
/**
* Updates the open state of the newly created portal
*
* @param destinationName <p>The name of the destination portal. Only used if set as always on</p>
*/
private void updateNewPortalOpenState(String destinationName) {
portal.drawSign();
if (portal.getOptions().isRandom() || portal.getOptions().isBungee()) {
//Open the implicitly always on portal
portal.getPortalOpener().openPortal(true);
} else if (portal.getOptions().isAlwaysOn()) {
//For a normal always-on portal, open both the portal and the destination
Portal destinationPortal = PortalHandler.getByName(destinationName, portal.getCleanNetwork());
if (destinationPortal != null) {
portal.getPortalOpener().openPortal(true);
destinationPortal.drawSign();
}
} else {
//Update the block type for the portal's opening to the closed block as the closed block can be anything,
// not just air or water
for (BlockLocation entrance : portal.getStructure().getEntrances()) {
entrance.setType(portal.getGate().getPortalClosedBlock());
}
}
}
/**
* Checks whether the new portal conflicts with an existing portal
*
* @param gate <p>The gate type of the new portal</p>
* @param topLeft <p>The top-left block of the new portal</p>
* @param yaw <p>The yaw when looking directly outwards from the portal</p>
* @param player <p>The player creating the new portal</p>
* @return <p>True if a conflict was found. False otherwise</p>
*/
private static boolean conflictsWithExistingPortal(Gate gate, BlockLocation topLeft, double yaw, Player player) {
for (RelativeBlockVector borderVector : gate.getLayout().getBorder()) {
BlockLocation borderBlockLocation = topLeft.getRelativeLocation(borderVector, yaw);
if (PortalHandler.getByBlock(borderBlockLocation.getBlock()) != null) {
Stargate.debug("createPortal", "Gate conflicts with existing gate");
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("createConflict"));
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,452 @@
package net.knarcraft.stargate.portal;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.BlockLocation;
import net.knarcraft.stargate.container.RelativeBlockVector;
import net.knarcraft.stargate.portal.property.PortalLocation;
import net.knarcraft.stargate.portal.property.PortalOption;
import net.knarcraft.stargate.portal.property.PortalStructure;
import net.knarcraft.stargate.portal.property.gate.Gate;
import net.knarcraft.stargate.portal.property.gate.GateHandler;
import net.knarcraft.stargate.utility.PermissionHelper;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Keeps track of all loaded portals, and handles portal creation
*/
public class PortalHandler {
private PortalHandler() {
}
/**
* Gets a copy of all portal networks
*
* @return <p>A copy of all portal networks</p>
*/
public static Map<String, List<String>> getAllPortalNetworks() {
return PortalRegistry.getAllPortalNetworks();
}
/**
* Gets a copy of all bungee portals
*
* @return <p>A copy of all bungee portals</p>
*/
public static Map<String, Portal> getBungeePortals() {
return PortalRegistry.getBungeePortals();
}
/**
* Gets names of all portals within a network
*
* @param network <p>The network to get portals from</p>
* @return <p>A list of portal names</p>
*/
public static List<String> getNetwork(String network) {
return PortalRegistry.getNetwork(network);
}
/**
* 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 : PortalRegistry.getAllPortalNetworks().get(network)) {
Portal portal = getByName(destination, network);
if (portal == null) {
continue;
}
//Check if destination is a random portal
if (portal.getOptions().isRandom()) {
continue;
}
//Check if destination is always open (Don't show if so)
if (portal.getOptions().isAlwaysOn() && !portal.getOptions().isShown()) {
continue;
}
//Check if destination is this portal
if (destination.equals(entrancePortal.getCleanName())) {
continue;
}
//Check if destination is a fixed portal not pointing to this portal
if (portal.getOptions().isFixed() &&
!Portal.cleanString(portal.getDestinationName()).equals(entrancePortal.getCleanName())) {
continue;
}
//Allow random use by non-players (Minecarts)
if (player == null) {
destinations.add(portal.getName());
continue;
}
//Check if this player can access the destination world
if (PermissionHelper.cannotAccessWorld(player, portal.getWorld().getName())) {
continue;
}
//The portal is visible to the player
if (PermissionHelper.canSeePortal(player, portal)) {
destinations.add(portal.getName());
}
}
return destinations;
}
/**
* Registers a portal
*
* @param portal <p>The portal to register</p>
*/
public static void registerPortal(Portal portal) {
PortalRegistry.registerPortal(portal);
}
/**
* Checks if the new portal is a valid bungee portal
*
* @param portalOptions <p>The enabled portal options</p>
* @param player <p>The player trying to create the new portal</p>
* @param destinationName <p>The name of the portal's destination</p>
* @param network <p>The name of the portal's network</p>
* @return <p>False if the portal is an invalid bungee portal. True otherwise</p>
*/
static boolean isValidBungeePortal(Map<PortalOption, Boolean> portalOptions, Player player,
String destinationName, String network) {
if (portalOptions.get(PortalOption.BUNGEE)) {
if (!PermissionHelper.hasPermission(player, "stargate.admin.bungee")) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("bungeeDeny"));
return false;
} else if (!Stargate.getGateConfig().enableBungee()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("bungeeDisabled"));
return false;
} else if (destinationName.isEmpty() || network.isEmpty()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("bungeeEmpty"));
return false;
}
}
return true;
}
/**
* Tries to find a gate matching the portal the user is trying to create
*
* @param portalLocation <p>The location data for the new portal</p>
* @param world <p>The world the player is located in</p>
* @return <p>The matching gate type, or null if no such gate could be found</p>
*/
static Gate findMatchingGate(PortalLocation portalLocation, World world) {
Block signParent = portalLocation.getSignLocation().getParent();
BlockLocation parent = new BlockLocation(world, signParent.getX(), signParent.getY(),
signParent.getZ());
//Get all gates with the used type of control blocks
Gate[] possibleGates = GateHandler.getGatesByControlBlock(signParent);
double yaw = portalLocation.getYaw();
Gate gate = null;
for (Gate possibleGate : possibleGates) {
//Get gate controls
RelativeBlockVector[] vectors = possibleGate.getLayout().getControls();
portalLocation.setButtonVector(null);
for (RelativeBlockVector controlVector : vectors) {
//Assuming the top-left location is pointing to the gate's top-left location, check if it's a match
BlockLocation possibleTopLocation = parent.getRelativeLocation(controlVector.invert(), yaw);
if (possibleGate.matches(possibleTopLocation, portalLocation.getYaw(), true)) {
gate = possibleGate;
portalLocation.setTopLeft(possibleTopLocation);
} else {
portalLocation.setButtonVector(controlVector);
}
}
}
return gate;
}
/**
* Updates the sign and open state of portals pointing at the newly created portal
*
* @param portal <p>The newly created portal</p>
*/
static void updatePortalsPointingAtNewPortal(Portal portal) {
for (String originName : PortalRegistry.getAllPortalNetworks().get(portal.getCleanNetwork())) {
Portal origin = getByName(originName, portal.getCleanNetwork());
if (origin == null ||
!Portal.cleanString(origin.getDestinationName()).equals(portal.getCleanName()) ||
!origin.getStructure().isVerified()) {
continue;
}
//Update sign of fixed gates pointing at this gate
if (origin.getOptions().isFixed()) {
origin.drawSign();
}
//Open any always on portal pointing at this portal
if (origin.getOptions().isAlwaysOn()) {
origin.getPortalOpener().openPortal(true);
}
}
}
/**
* Gets all portal options to be applied to a new portal
*
* @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>
*/
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 &&
PermissionHelper.canUseOption(player, option));
}
//Can not create a non-fixed always-on portal
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 portals 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 portals 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) {
Map<String, Map<String, Portal>> lookupMap = PortalRegistry.getPortalLookupByNetwork();
if (!lookupMap.containsKey(network.toLowerCase())) {
return null;
}
return lookupMap.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 PortalRegistry.getLookupEntrances().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 PortalRegistry.getLookupEntrances().get(new BlockLocation(block));
}
/**
* Gets a portal given a location adjacent to its entrance
*
* @param location <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 location) {
return getByAdjacentEntrance(location, 1);
}
/**
* Gets a portal given a location adjacent to its entrance
*
* @param location <p>A location adjacent to the portal's entrance</p>
* @param range <p>The range to scan for portals</p>
* @return <p>The portal adjacent to the given location</p>
*/
public static Portal getByAdjacentEntrance(Location location, int range) {
List<BlockLocation> adjacentPositions = new ArrayList<>();
BlockLocation centerLocation = new BlockLocation(location.getBlock());
adjacentPositions.add(centerLocation);
for (int index = 1; index <= range; index++) {
adjacentPositions.add(centerLocation.makeRelativeBlockLocation(index, 0, 0));
adjacentPositions.add(centerLocation.makeRelativeBlockLocation(-index, 0, 0));
adjacentPositions.add(centerLocation.makeRelativeBlockLocation(0, 0, index));
adjacentPositions.add(centerLocation.makeRelativeBlockLocation(0, 0, -index));
if (index < range) {
adjacentPositions.add(centerLocation.makeRelativeBlockLocation(index, 0, index));
adjacentPositions.add(centerLocation.makeRelativeBlockLocation(-index, 0, -index));
adjacentPositions.add(centerLocation.makeRelativeBlockLocation(index, 0, -index));
adjacentPositions.add(centerLocation.makeRelativeBlockLocation(-index, 0, index));
}
}
for (BlockLocation adjacentPosition : adjacentPositions) {
Portal portal = PortalRegistry.getLookupEntrances().get(adjacentPosition);
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 portal with the given control block</p>
*/
public static Portal getByControl(Block block) {
return PortalRegistry.getLookupControls().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 PortalRegistry.getLookupBlocks().get(new BlockLocation(block));
}
/**
* Gets a bungee portal given its name
*
* @param name <p>The name of the bungee portal to get</p>
* @return <p>A bungee portal</p>
*/
public static Portal getBungeePortal(String name) {
return PortalRegistry.getBungeePortals().get(name.toLowerCase());
}
/**
* 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>
*/
public 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-on portals
*
* @return <p>The number of always open portals enabled</p>
*/
public static int openAlwaysOpenPortals() {
int alwaysOpenCount = 0;
for (Portal portal : PortalRegistry.getAllPortals()) {
//Open the gate if it's set as always open or if it's a bungee gate
if (portal.getOptions().isFixed() && (Stargate.getGateConfig().enableBungee() &&
portal.getOptions().isBungee() || portal.getPortalActivator().getDestination() != null &&
portal.getOptions().isAlwaysOn())) {
portal.getPortalOpener().openPortal(true);
alwaysOpenCount++;
}
}
return alwaysOpenCount;
}
/**
* Tries to verify all portals and un-registers non-verifiable portals
*/
public static void verifyAllPortals() {
List<Portal> invalidPortals = new ArrayList<>();
for (Portal portal : PortalRegistry.getAllPortals()) {
//Try and verify the portal. Invalidate it if it cannot be validated
PortalStructure structure = portal.getStructure();
if (!structure.wasVerified() && (!structure.isVerified() || !structure.checkIntegrity())) {
invalidPortals.add(portal);
}
}
//Un-register any invalid portals found
for (Portal portal : invalidPortals) {
unregisterInvalidPortal(portal);
}
}
/**
* Un-registers a portal which has failed its integrity tests
*
* @param portal <p>The portal of the star portal</p>
*/
private static void unregisterInvalidPortal(Portal portal) {
//Show debug information
for (RelativeBlockVector control : portal.getGate().getLayout().getControls()) {
Block block = portal.getBlockAt(control).getBlock();
//Log control blocks not matching the gate layout
if (!block.getType().equals(portal.getGate().getControlBlock())) {
Stargate.debug("PortalHandler::destroyInvalidPortal", "Control Block Type == " +
block.getType().name());
}
}
PortalRegistry.unregisterPortal(portal, false);
Stargate.logInfo(String.format("Destroying stargate at %s", portal));
}
/**
* Closes all portals
*/
public static void closeAllPortals() {
Stargate.logInfo("Closing all stargates.");
for (Portal portal : PortalRegistry.getAllPortals()) {
if (portal != null) {
portal.getPortalOpener().closePortal(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,232 @@
package net.knarcraft.stargate.portal;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.BlockChangeRequest;
import net.knarcraft.stargate.container.BlockLocation;
import net.knarcraft.stargate.event.StargateCloseEvent;
import net.knarcraft.stargate.event.StargateOpenEvent;
import net.knarcraft.stargate.portal.property.PortalOptions;
import org.bukkit.Axis;
import org.bukkit.Material;
import org.bukkit.block.data.Orientable;
import org.bukkit.entity.Player;
/**
* The portal opener is responsible for opening and closing a portal
*/
public class PortalOpener {
private boolean isOpen = false;
private final Portal portal;
private long triggeredTime;
private Player player;
private final PortalActivator portalActivator;
/**
* Instantiates a new portal opener
*
* @param portal <p>The portal this portal opener should open</p>
* @param destination <p>The fixed destination defined on the portal's sign</p>
*/
public PortalOpener(Portal portal, String destination) {
this.portal = portal;
this.portalActivator = new PortalActivator(portal, this, destination);
}
/**
* Gets whether this portal opener's portal is currently open
*
* @return <p>Whether this portal opener's portal is open</p>
*/
public boolean isOpen() {
return isOpen || portal.getOptions().isAlwaysOn();
}
/**
* Sets the time when this portal was triggered (activated/opened)
*
* @param triggeredTime <p>Unix timestamp when portal was triggered</p>
*/
public void setTriggeredTime(long triggeredTime) {
this.triggeredTime = triggeredTime;
}
/**
* Gets the portal activator belonging to this portal opener
*
* @return <p>The portal activator belonging to this portal opener</p>
*/
public PortalActivator getPortalActivator() {
return this.portalActivator;
}
/**
* Open this portal opener's portal
*
* @param force <p>Whether to force the portal open, even if it's already open for some player</p>
*/
public void openPortal(boolean force) {
openPortal(null, force);
}
/**
* Open this portal opener's portal
*
* @param openFor <p>The player to open the portal for</p>
* @param force <p>Whether to force the portal open, even if it's already open for some player</p>
*/
public void openPortal(Player openFor, boolean force) {
//Call the StargateOpenEvent to allow the opening to be cancelled
StargateOpenEvent event = new StargateOpenEvent(openFor, portal, force);
Stargate.getInstance().getServer().getPluginManager().callEvent(event);
if (event.isCancelled() || (isOpen() && !event.getForce())) {
return;
}
//Get the material to change the opening to
Material openType = portal.getGate().getPortalOpenBlock();
//Adjust orientation if applicable
Axis axis = (openType.createBlockData() instanceof Orientable) ? portal.getLocation().getRotationAxis() : null;
//Change the entrance blocks to the correct type
for (BlockLocation inside : portal.getStructure().getEntrances()) {
Stargate.addBlockChangeRequest(new BlockChangeRequest(inside, openType, axis));
}
//Update the portal state to make is actually open
updatePortalOpenState(openFor);
}
/**
* Updates this portal opener's portal to be recognized as open and opens its destination portal
*
* @param openFor <p>The player to open this portal opener's portal for</p>
*/
private void updatePortalOpenState(Player openFor) {
//Update the open state of this portal
isOpen = true;
triggeredTime = System.currentTimeMillis() / 1000;
//Change state from active to open
Stargate.getStargateConfig().getOpenPortalsQueue().add(portal);
Stargate.getStargateConfig().getActivePortalsQueue().remove(portal);
PortalOptions options = portal.getOptions();
//If this portal is always open, opening the destination is not necessary
if (options.isAlwaysOn()) {
return;
}
//Update the player the portal is open for
this.player = openFor;
Portal destination = portal.getPortalActivator().getDestination();
if (destination == null) {
return;
}
boolean thisIsDestination = Portal.cleanString(destination.getDestinationName()).equals(portal.getCleanName());
//Only open destination if it's not-fixed or points at this portal, and is not already open
if (!options.isRandom() && (!destination.getOptions().isFixed() || thisIsDestination) && !destination.isOpen()) {
//Open the destination portal
destination.getPortalOpener().openPortal(openFor, false);
//Set the destination portal to this opener's portal
destination.getPortalActivator().setDestination(portal);
//Update the destination's sign if it's verified
if (destination.getStructure().isVerified()) {
destination.drawSign();
}
}
}
/**
* Closes this portal opener's portal
*
* @param force <p>Whether to force the portal closed, even if it's set as always on</p>
*/
public void closePortal(boolean force) {
//No need to close a portal which is already closed
if (!isOpen()) {
return;
}
//Call the StargateCloseEvent to allow other plugins to cancel the closing, or change whether to force it closed
StargateCloseEvent event = new StargateCloseEvent(portal, force);
Stargate.getInstance().getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
}
//Only close an always-open portal if forced to
if (portal.getOptions().isAlwaysOn() && !event.getForce()) {
return;
}
//Close the portal by requesting the opening blocks to change
Material closedType = portal.getGate().getPortalClosedBlock();
for (BlockLocation entrance : portal.getStructure().getEntrances()) {
Stargate.addBlockChangeRequest(new BlockChangeRequest(entrance, closedType, null));
}
//Update the portal state to make it actually closed
updatePortalClosedState();
//Finally, deactivate the portal
portalActivator.deactivate();
}
/**
* Updates this portal to be recognized as closed and closes its destination portal
*/
private void updatePortalClosedState() {
//Unset the stored player and set the portal to closed
player = null;
isOpen = false;
//Un-mark the portal as active and open
Stargate.getStargateConfig().getOpenPortalsQueue().remove(portal);
Stargate.getStargateConfig().getActivePortalsQueue().remove(portal);
//Close the destination portal if not always open
if (!portal.getOptions().isAlwaysOn()) {
Portal destination = portal.getPortalActivator().getDestination();
if (destination != null && destination.isOpen()) {
//De-activate and close the destination portal
destination.getPortalActivator().deactivate();
destination.getPortalOpener().closePortal(false);
}
}
}
/**
* Gets whether this portal opener's portal is open for the given player
*
* @param player <p>The player to check portal state for</p>
* @return <p>True if this portal opener's portal is open to the given player</p>
*/
public boolean isOpenFor(Player player) {
//If closed, it's closed for everyone
if (!isOpen) {
return false;
}
//If always on, or player is null which only happens with an always on portal, allow the player to pass
if (portal.getOptions().isAlwaysOn() || this.player == null) {
return true;
}
//If the player is the player which the portal opened for, allow it to pass
return player != null && player.getName().equalsIgnoreCase(this.player.getName());
}
/**
* Gets the time this portal opener's portal was triggered (activated/opened)
*
* @return <p>The time this portal opener's portal was triggered</p>
*/
public long getTriggeredTime() {
return triggeredTime;
}
}

View File

@@ -0,0 +1,297 @@
package net.knarcraft.stargate.portal;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.config.DynmapManager;
import net.knarcraft.stargate.container.BlockLocation;
import net.knarcraft.stargate.utility.PortalFileHelper;
import org.bukkit.World;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* The portal registry keeps track of all registered portals and all their lookup blocks
*/
public class PortalRegistry {
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 Map<String, Map<String, Portal>> portalLookupByNetwork = new HashMap<>();
private static final Map<String, List<String>> allPortalNetworks = new HashMap<>();
private static final Map<String, Portal> bungeePortals = new HashMap<>();
private static final List<Portal> allPortals = new ArrayList<>();
/**
* Clears all portals and all data held by the portal registry
*/
public static void clearPortals() {
lookupBlocks.clear();
portalLookupByNetwork.clear();
lookupEntrances.clear();
lookupControls.clear();
allPortals.clear();
allPortalNetworks.clear();
bungeePortals.clear();
}
/**
* Clears all portals loaded in a given world
*
* @param world <p>The world containing the portals to clear</p>
*/
public static void clearPortals(World world) {
//Storing the portals to clear is necessary to avoid a concurrent modification exception
List<Portal> portalsToRemove = new ArrayList<>();
allPortals.forEach((portal) -> {
if (portal.getWorld().equals(world)) {
portalsToRemove.add(portal);
}
});
clearPortals(portalsToRemove);
}
/**
* Clears a given list of portals from all relevant variables
*
* @param portalsToRemove <p>A list of portals to remove</p>
*/
private static void clearPortals(List<Portal> portalsToRemove) {
//Store the names of the portals to remove as some maps require the name, not the object
List<String> portalNames = new ArrayList<>();
portalsToRemove.forEach((portal) -> portalNames.add(portal.getCleanName()));
//Clear all the lookup locations for the portals
lookupBlocks.keySet().removeIf((key) -> portalsToRemove.contains(lookupBlocks.get(key)));
lookupEntrances.keySet().removeIf((key) -> portalsToRemove.contains(lookupEntrances.get(key)));
lookupControls.keySet().removeIf((key) -> portalsToRemove.contains(lookupControls.get(key)));
//Remove the portals from all networks, and then remove any empty networks. This is done for both network maps
portalLookupByNetwork.keySet().forEach((network) -> portalLookupByNetwork.get(network).keySet().removeIf((key) ->
portalsToRemove.contains(portalLookupByNetwork.get(network).get(key))));
portalLookupByNetwork.keySet().removeIf((key) -> portalLookupByNetwork.get(key).isEmpty());
allPortalNetworks.keySet().forEach((network) -> allPortalNetworks.get(network).removeIf(portalNames::contains));
allPortalNetworks.keySet().removeIf((network) -> allPortalNetworks.get(network).isEmpty());
//Finally, remove the portals from the portal list
allPortals.removeIf(portalsToRemove::contains);
}
/**
* Gets a copy of the list of all portals
*
* @return <p>A copy of the list of all portals</p>
*/
public static List<Portal> getAllPortals() {
return new ArrayList<>(allPortals);
}
/**
* Gets a copy of the lookup map for finding a portal by its frame
*
* @return <p>A copy of the frame block lookup map</p>
*/
public static Map<BlockLocation, Portal> getLookupBlocks() {
return new HashMap<>(lookupBlocks);
}
/**
* Gets a copy of the lookup map for finding a portal by its control block
*
* @return <p>A copy of the control block lookup map</p>
*/
public static Map<BlockLocation, Portal> getLookupControls() {
return new HashMap<>(lookupControls);
}
/**
* Gets a copy of the lookup map for finding all portals in a network
*
* @return <p>A copy of the network portal lookup map</p>
*/
public static Map<String, Map<String, Portal>> getPortalLookupByNetwork() {
return new HashMap<>(portalLookupByNetwork);
}
/**
* Gets a copy of all portal entrances available for lookup
*
* @return <p>A copy of all entrances to portal mappings</p>
*/
public static Map<BlockLocation, Portal> getLookupEntrances() {
return new HashMap<>(lookupEntrances);
}
/**
* Gets a copy of all portal networks
*
* @return <p>A copy of all portal networks</p>
*/
public static Map<String, List<String>> getAllPortalNetworks() {
return new HashMap<>(allPortalNetworks);
}
/**
* Gets a copy of all bungee portals
*
* @return <p>A copy of all bungee portals</p>
*/
public static Map<String, Portal> getBungeePortals() {
return new HashMap<>(bungeePortals);
}
/**
* Gets names of all portals within a network
*
* @param network <p>The network to get portals from</p>
* @return <p>A list of portal names</p>
*/
public static List<String> getNetwork(String network) {
return allPortalNetworks.get(network.toLowerCase());
}
/**
* 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.getPortalActivator().deactivate();
portal.getPortalOpener().closePortal(true);
String portalName = portal.getCleanName();
String networkName = portal.getCleanNetwork();
//Remove portal from lookup blocks
for (BlockLocation block : portal.getStructure().getFrame()) {
lookupBlocks.remove(block);
}
//Remove registered info about the lookup controls and blocks
lookupBlocks.remove(portal.getSignLocation());
lookupControls.remove(portal.getSignLocation());
BlockLocation button = portal.getStructure().getButton();
if (button != null) {
lookupBlocks.remove(button);
lookupControls.remove(button);
}
//Remove entrances
for (BlockLocation entrance : portal.getStructure().getEntrances()) {
lookupEntrances.remove(entrance);
}
//Remove the portal from the list of all portals
if (removeAll) {
allPortals.remove(portal);
}
if (portal.getOptions().isBungee()) {
//Remove the bungee listing
bungeePortals.remove(portalName);
} else {
//Remove from network lists
portalLookupByNetwork.get(networkName).remove(portalName);
allPortalNetworks.get(networkName).remove(portalName);
//Update all portals in the same network with this portal as its destination
for (String originName : allPortalNetworks.get(networkName)) {
Portal origin = PortalHandler.getByName(originName, portal.getCleanNetwork());
if (origin == null || !origin.getDestinationName().equalsIgnoreCase(portalName) ||
!origin.getStructure().isVerified()) {
continue;
}
//Update the portal's sign
if (origin.getOptions().isFixed()) {
origin.drawSign();
}
//Close portal without destination
if (origin.getOptions().isAlwaysOn()) {
origin.getPortalOpener().closePortal(true);
}
}
}
//Mark the portal's sign as unregistered
new PortalSignDrawer(portal).drawUnregisteredSign();
PortalFileHelper.saveAllPortals(portal.getWorld());
portal.setRegistered(false);
DynmapManager.removePortalMarker(portal);
}
/**
* Registers a portal
*
* @param portal <p>The portal to register</p>
*/
static void registerPortal(Portal portal) {
portal.getOptions().setFixed(portal.getDestinationName().length() > 0 || portal.getOptions().isRandom() ||
portal.getOptions().isBungee());
String portalName = portal.getCleanName();
String networkName = portal.getCleanNetwork();
//Bungee portals are stored in their own list
if (portal.getOptions().isBungee()) {
bungeePortals.put(portalName, portal);
} else {
//Check if network exists in the lookup list. If not, register the new network
if (!portalLookupByNetwork.containsKey(networkName)) {
Stargate.debug("register", String.format("Network %s not in lookupNamesNet, adding",
portal.getNetwork()));
portalLookupByNetwork.put(networkName, new HashMap<>());
}
//Check if this network exists in the network list. If not, register the network
if (!allPortalNetworks.containsKey(networkName)) {
Stargate.debug("register", String.format("Network %s not in allPortalsNet, adding",
portal.getNetwork()));
allPortalNetworks.put(networkName, new ArrayList<>());
}
//Register the portal
portalLookupByNetwork.get(networkName).put(portalName, portal);
if (!allPortalNetworks.get(networkName).contains(portalName)) {
allPortalNetworks.get(networkName).add(portalName);
} else {
Stargate.logSevere(String.format("Portal %s on network %s was registered twice. Check your portal " +
"database for duplicates.", portal.getName(), portal.getNetwork()));
}
}
//Register all frame blocks to the lookup list
for (BlockLocation block : portal.getStructure().getFrame()) {
lookupBlocks.put(block, portal);
}
//Register the sign and button to the lookup lists
if (!portal.getOptions().hasNoSign()) {
lookupBlocks.put(portal.getSignLocation(), portal);
lookupControls.put(portal.getSignLocation(), portal);
}
BlockLocation button = portal.getStructure().getButton();
if (button != null) {
lookupBlocks.put(button, portal);
lookupControls.put(button, portal);
}
//Register entrances to the lookup list
for (BlockLocation entrance : portal.getStructure().getEntrances()) {
lookupEntrances.put(entrance, portal);
}
allPortals.add(portal);
portal.setRegistered(true);
DynmapManager.addPortalMarker(portal);
}
}

View File

@@ -0,0 +1,406 @@
package net.knarcraft.stargate.portal;
import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.knarlib.util.ColorHelper;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.SignData;
import net.knarcraft.stargate.portal.property.PortalLocation;
import net.knarcraft.stargate.utility.PermissionHelper;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.block.Sign;
import java.util.Map;
/**
* The portal sign drawer draws the sing of a given portal
*/
public class PortalSignDrawer {
private final Portal portal;
private final static ChatColor errorColor = ChatColor.DARK_RED;
private static ChatColor freeColor;
private static ChatColor mainColor;
private static ChatColor highlightColor;
private static Map<Material, ChatColor> perSignMainColors;
private static Map<Material, ChatColor> perSignHighlightColors;
/**
* Instantiates a new portal sign drawer
*
* @param portal <p>The portal whose sign this portal sign drawer is responsible for drawing</p>
*/
public PortalSignDrawer(Portal portal) {
this.portal = portal;
}
/**
* Sets the highlighting sign color
*
* <p>The highlighting color is used for the markings around portal names and network names ('>','<','-',')','(').</p>
*
* @param newHighlightColor <p>The new highlight color</p>
*/
public static void setHighlightColor(ChatColor newHighlightColor) {
highlightColor = newHighlightColor;
}
/**
* Sets the main sign color
*
* <p>The main sign color is used for most text on the sign.</p>
*
* @param newMainColor <p>The new main sign color</p>
*/
public static void setMainColor(ChatColor newMainColor) {
mainColor = newMainColor;
}
/**
* Sets the color to use for marking free stargates
*
* @param freeColor <p>The new color to use for marking free stargates</p>
*/
public static void setFreeColor(ChatColor freeColor) {
PortalSignDrawer.freeColor = freeColor;
}
/**
* Sets the per-sign main colors
*
* @param signMainColors <p>The per-sign main colors</p>
*/
public static void setPerSignMainColors(Map<Material, ChatColor> signMainColors) {
PortalSignDrawer.perSignMainColors = signMainColors;
}
/**
* Sets the per-sign highlight colors
*
* @param signHighlightColors <p>The per-sign highlight colors</p>
*/
public static void setPerSignHighlightColors(Map<Material, ChatColor> signHighlightColors) {
PortalSignDrawer.perSignHighlightColors = signHighlightColors;
}
/**
* Gets the currently used main sign color
*
* @return <p>The currently used main sign color</p>
*/
public static ChatColor getMainColor() {
return mainColor;
}
/**
* Gets the currently used highlighting sign color
*
* @return <p>The currently used highlighting sign color</p>
*/
public static ChatColor getHighlightColor() {
return highlightColor;
}
/**
* Draws the sign of the portal this sign drawer is responsible for
*/
public void drawSign() {
Sign sign = getSign();
if (sign == null) {
return;
}
SignData signData = new SignData(sign, getMainColor(sign.getType()), getHighlightColor(sign.getType()));
drawSign(signData);
}
/**
* Gets the sign for this sign drawer's portal
*
* @return <p>The sign of this sign drawer's portal</p>
*/
private Sign getSign() {
Block signBlock = portal.getSignLocation().getBlock();
BlockState state = signBlock.getState();
if (!(state instanceof Sign sign)) {
if (!portal.getOptions().hasNoSign()) {
Stargate.logWarning("Sign block is not a Sign object");
Stargate.debug("Portal::drawSign", String.format("Block: %s @ %s", signBlock.getType(),
signBlock.getLocation()));
}
return null;
}
return sign;
}
/**
* Draws the sign of the portal this sign drawer is responsible for
*
* @param signData <p>All necessary sign information</p>
*/
private void drawSign(SignData signData) {
Sign sign = signData.getSign();
ChatColor highlightColor = signData.getHighlightSignColor();
ChatColor mainColor = signData.getMainSignColor();
//Clear sign
clearSign(sign);
setLine(signData, 0, highlightColor + "-" + mainColor + translateAllColorCodes(portal.getName()) +
highlightColor + "-");
if (!portal.getPortalActivator().isActive()) {
//Default sign text
drawInactiveSign(signData);
} else {
if (portal.getOptions().isBungee()) {
//Bungee sign
drawBungeeSign(signData);
} else if (portal.getOptions().isFixed()) {
//Sign pointing at one other portal
drawFixedSign(signData);
} else {
//Networking stuff
drawNetworkSign(signData);
}
}
sign.update();
}
/**
* Clears all lines of a sign, but does not update the sign
*
* @param sign <p>The sign to clear</p>
*/
private void clearSign(Sign sign) {
for (int index = 0; index <= 3; index++) {
sign.setLine(index, "");
}
}
/**
* Marks this sign drawer's portal as unregistered
*/
public void drawUnregisteredSign() {
Sign sign = getSign();
if (sign == null) {
return;
}
clearSign(sign);
sign.setLine(0, translateAllColorCodes(portal.getName()));
sign.update();
}
/**
* Draws a sign with choose-able network locations
*
* @param signData <p>All necessary sign information</p>
*/
private void drawNetworkSign(SignData signData) {
PortalActivator destinations = portal.getPortalActivator();
int maxIndex = destinations.getDestinations().size() - 1;
int signLineIndex = 0;
int destinationIndex = destinations.getDestinations().indexOf(portal.getDestinationName());
boolean freeGatesColored = Stargate.getEconomyConfig().useEconomy() &&
Stargate.getEconomyConfig().drawFreePortalsColored();
//Last, and not only entry. Draw the entry two back
if ((destinationIndex == maxIndex) && (maxIndex > 1)) {
drawNetworkSignLine(signData, freeGatesColored, ++signLineIndex, destinationIndex - 2);
}
//Not first entry. Draw the previous entry
if (destinationIndex > 0) {
drawNetworkSignLine(signData, freeGatesColored, ++signLineIndex, destinationIndex - 1);
}
//Draw the chosen entry (line 2 or 3)
drawNetworkSignChosenLine(signData, freeGatesColored, ++signLineIndex);
//Has another entry and space on the sign
if ((maxIndex >= destinationIndex + 1)) {
drawNetworkSignLine(signData, freeGatesColored, ++signLineIndex, destinationIndex + 1);
}
//Has another entry and space on the sign
if ((maxIndex >= destinationIndex + 2) && (++signLineIndex <= 3)) {
drawNetworkSignLine(signData, freeGatesColored, signLineIndex, destinationIndex + 2);
}
}
/**
* Draws the chosen destination on one sign line
*
* @param signData <p>All necessary sign information</p>
* @param freeGatesColored <p>Whether to display free gates in a different color</p>
* @param signLineIndex <p>The line to draw on</p>
*/
private void drawNetworkSignChosenLine(SignData signData, boolean freeGatesColored, int signLineIndex) {
ChatColor highlightColor = signData.getHighlightSignColor();
ChatColor mainColor = signData.getMainSignColor();
if (freeGatesColored) {
Portal destination = PortalHandler.getByName(portal.getDestinationName(), portal.getNetwork());
boolean free = PermissionHelper.isFree(portal.getActivePlayer(), portal, destination);
ChatColor nameColor = (free ? freeColor : highlightColor);
setLine(signData, signLineIndex, nameColor + ">" + (free ? freeColor : mainColor) +
translateAllColorCodes(portal.getDestinationName()) + nameColor + "<");
} else {
setLine(signData, signLineIndex, highlightColor + ">" + mainColor +
translateAllColorCodes(portal.getDestinationName()) + highlightColor + "<");
}
}
/**
* Sets a line on a sign, adding the chosen sign color
*
* @param signData <p>All necessary sign information</p>
* @param index <p>The index of the sign line to change</p>
* @param text <p>The new text on the sign</p>
*/
public void setLine(SignData signData, int index, String text) {
ChatColor mainColor = signData.getMainSignColor();
signData.getSign().setLine(index, mainColor + text);
}
/**
* Draws one network destination on one sign line
*
* @param signData <p>All necessary sign information</p>
* @param freeGatesColored <p>Whether to display free gates in a different color</p>
* @param signLineIndex <p>The line to draw on</p>
* @param destinationIndex <p>The index of the destination to draw</p>
*/
private void drawNetworkSignLine(SignData signData, boolean freeGatesColored, int signLineIndex, int destinationIndex) {
ChatColor mainColor = signData.getMainSignColor();
PortalActivator destinations = portal.getPortalActivator();
String destinationName = destinations.getDestinations().get(destinationIndex);
if (freeGatesColored) {
Portal destination = PortalHandler.getByName(destinationName, portal.getNetwork());
boolean free = PermissionHelper.isFree(portal.getActivePlayer(), portal, destination);
setLine(signData, signLineIndex, (free ? freeColor : mainColor) + translateAllColorCodes(destinationName));
} else {
setLine(signData, signLineIndex, mainColor + translateAllColorCodes(destinationName));
}
}
/**
* Draws the sign of a BungeeCord portal
*
* @param signData <p>All necessary sign information</p>
*/
private void drawBungeeSign(SignData signData) {
ChatColor highlightColor = signData.getHighlightSignColor();
ChatColor mainColor = signData.getMainSignColor();
setLine(signData, 1, Stargate.getString("bungeeSign"));
setLine(signData, 2, highlightColor + ">" + mainColor + translateAllColorCodes(portal.getDestinationName()) +
highlightColor + "<");
setLine(signData, 3, highlightColor + "[" + mainColor + translateAllColorCodes(portal.getNetwork()) +
highlightColor + "]");
}
/**
* Draws the sign of an in-active portal
*
* <p>The sign for an in-active portal should display the right-click prompt and the network.</p>
*
* @param signData <p>All necessary sign information</p>
*/
private void drawInactiveSign(SignData signData) {
ChatColor highlightColor = signData.getHighlightSignColor();
ChatColor mainColor = signData.getMainSignColor();
setLine(signData, 1, Stargate.getString("signRightClick"));
setLine(signData, 2, Stargate.getString("signToUse"));
if (!portal.getOptions().isNoNetwork()) {
setLine(signData, 3, highlightColor + "(" + mainColor + translateAllColorCodes(portal.getNetwork()) +
highlightColor + ")");
} else {
setLine(signData, 3, "");
}
}
/**
* Draws a sign pointing to a fixed location
*
* @param signData <p>All necessary sign information</p>
*/
private void drawFixedSign(SignData signData) {
ChatColor highlightColor = signData.getHighlightSignColor();
ChatColor mainColor = signData.getMainSignColor();
Portal destinationPortal = PortalHandler.getByName(Portal.cleanString(portal.getDestinationName()),
portal.getCleanNetwork());
String destinationName = portal.getOptions().isRandom() ? Stargate.getString("signRandom") :
(destinationPortal != null ? destinationPortal.getName() : portal.getDestinationName());
setLine(signData, 1, highlightColor + ">" + mainColor + translateAllColorCodes(destinationName) +
highlightColor + "<");
if (portal.getOptions().isNoNetwork()) {
setLine(signData, 2, "");
} else {
setLine(signData, 2, highlightColor + "(" + mainColor +
translateAllColorCodes(portal.getNetwork()) + highlightColor + ")");
}
Portal destination = PortalHandler.getByName(Portal.cleanString(portal.getDestinationName()),
portal.getNetwork());
if (destination == null && !portal.getOptions().isRandom()) {
setLine(signData, 3, errorColor + Stargate.getString("signDisconnected"));
} else {
setLine(signData, 3, "");
}
}
/**
* Marks a portal with an invalid gate by changing its sign and writing to the console
*
* @param portalLocation <p>The location of the portal with an invalid gate</p>
* @param gateName <p>The name of the invalid gate type</p>
* @param lineIndex <p>The index of the line the invalid portal was found at</p>
*/
public static void markPortalWithInvalidGate(PortalLocation portalLocation, String gateName, int lineIndex) {
BlockState blockState = portalLocation.getSignLocation().getBlock().getState();
if (!(blockState instanceof Sign sign)) {
return;
}
sign.setLine(3, errorColor + Stargate.getString("signInvalidGate"));
sign.update();
Stargate.logInfo(String.format("Gate layout on line %d does not exist [%s]", lineIndex, gateName));
}
/**
* Gets the main color to use for the given sign type
*
* @param signType <p>The sign type to get the main color for</p>
* @return <p>The main color for the given sign type</p>
*/
private ChatColor getMainColor(Material signType) {
ChatColor signColor = perSignMainColors.get(signType);
if (signColor == null) {
return mainColor;
} else {
return signColor;
}
}
/**
* Gets the highlight color to use for the given sign type
*
* @param signType <p>The sign type to get the highlight color for</p>
* @return <p>The highlight color for the given sign type</p>
*/
private ChatColor getHighlightColor(Material signType) {
ChatColor signColor = perSignHighlightColors.get(signType);
if (signColor == null) {
return highlightColor;
} else {
return signColor;
}
}
/**
* Translates all normal and RGB color codes in the given input
*
* @param input <p>The input to translate color codes for</p>
* @return <p>The input with color codes converted translated from & to §</p>
*/
private String translateAllColorCodes(String input) {
return ColorHelper.translateColorCodes(input, ColorConversion.RGB);
}
}

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,106 @@
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),
/**
* This option allows a portal which does not display a teleportation message, for better immersion
*/
SILENT('i', "stargate.option.silent", 21),
/**
* This option causes a fixed portal's sign to be removed after creation
*/
NO_SIGN('e', "stargate.option.nosign", 22);
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,196 @@
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("PortalOptions", "Can not create a non-fixed always-on gate. Setting AlwaysOn = false");
}
if ((this.isRandom() || this.isBungee()) && !this.isAlwaysOn()) {
this.options.put(PortalOption.ALWAYS_ON, true);
Stargate.debug("PortalOptions", "Gate marked as random or bungee, set to always-on");
}
if (this.hasNoSign() && !this.isFixed) {
this.options.put(PortalOption.NO_SIGN, false);
Stargate.debug("PortalOptions", "Gate marked with no sign, but not fixed. Setting NoSign = false");
}
}
/**
* 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);
}
/**
* Gets whether this portal is silent
*
* <p>A silent portal does not output anything to the chat when teleporting. This option is mainly useful to keep
* the immersion during teleportation (for role-playing servers or similar).</p>
*
* @return <p>Whether this portal is silent</p>
*/
public boolean isSilent() {
return this.options.get(PortalOption.SILENT);
}
/**
* Gets whether this portal has no sign
*
* <p>An always-on portal is allowed to not have a sign as it will never be interacted with anyway.</p>
*
* @return <p>Whether this portal has no sign</p>
*/
public boolean hasNoSign() {
return this.options.get(PortalOption.NO_SIGN);
}
}

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,356 @@
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 a copy of the character to material mapping for this gate
*
* @return <p>The character to material map</p>
*/
public Map<Character, Material> getCharacterMaterialMap() {
return new HashMap<>(characterMaterialMap);
}
/**
* 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.isAir() || 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,347 @@
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) {
String failString = String.format("Could not load Gate %s", fileName) + " - %s";
if (gate.getLayout().getControls().length != 2) {
Stargate.logSevere(String.format(failString, "Gates must have exactly 2 control points."));
return false;
}
if (!MaterialHelper.isButtonCompatible(gate.getPortalButton())) {
Stargate.logSevere(String.format(failString, "Gate button must be a type of button."));
return false;
}
if (!gate.getPortalOpenBlock().isBlock()) {
Stargate.logSevere(String.format(failString, "Gate open block must be a type of block."));
return false;
}
if (!gate.getPortalClosedBlock().isBlock()) {
Stargate.logSevere(String.format(failString, "Gate closed block must be a type of block."));
return false;
}
for (Material material : gate.getCharacterMaterialMap().values()) {
if (!material.isBlock()) {
Stargate.logSevere(String.format(failString, "Every gate border block must be a type of block."));
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);
loadGateFromJar("squarenetherglowstonegate.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());
}
}

View File

@@ -0,0 +1,34 @@
package net.knarcraft.stargate.portal.teleporter;
import net.knarcraft.stargate.event.StargateEntityPortalEvent;
import net.knarcraft.stargate.portal.Portal;
import org.bukkit.entity.Entity;
/**
* The portal teleporter takes care of the actual portal teleportation for any entities
*/
public class EntityTeleporter extends Teleporter {
private final Entity teleportingEntity;
/**
* Instantiates a new portal teleporter
*
* @param targetPortal <p>The portal which is the target of the teleportation</p>
*/
public EntityTeleporter(Portal targetPortal, Entity teleportingEntity) {
super(targetPortal, teleportingEntity);
this.teleportingEntity = teleportingEntity;
}
/**
* Teleports an entity to this teleporter's portal
*
* @param origin <p>The portal the entity is teleporting from</p>
* @return <p>True if the entity was teleported. False otherwise</p>
*/
public boolean teleportEntity(Portal origin) {
return teleport(origin, new StargateEntityPortalEvent(teleportingEntity, origin, portal, exit));
}
}

View File

@@ -0,0 +1,78 @@
package net.knarcraft.stargate.portal.teleporter;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.event.StargatePlayerPortalEvent;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.utility.DirectionHelper;
import net.knarcraft.stargate.utility.TeleportHelper;
import org.bukkit.Bukkit;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.util.Vector;
import java.util.List;
/**
* The portal teleporter takes care of the actual portal teleportation for any players
*/
public class PlayerTeleporter extends Teleporter {
private final Player player;
/**
* Instantiates a new player teleporter
*
* @param targetPortal <p>The portal which is the target of the teleportation</p>
* @param player <p>The teleporting player</p>
*/
public PlayerTeleporter(Portal targetPortal, Player player) {
super(targetPortal, player);
this.player = player;
}
/**
* Teleports a player to this teleporter's portal
*
* @param origin <p>The portal the player teleports from</p>
* @param event <p>The player move event triggering the event</p>
*/
public void teleportPlayer(Portal origin, PlayerMoveEvent event) {
double velocity = player.getVelocity().length();
List<Entity> passengers = player.getPassengers();
//Call the StargatePlayerPortalEvent to allow plugins to change destination
if (!origin.equals(portal)) {
exit = triggerPortalEvent(origin, new StargatePlayerPortalEvent(player, origin, portal, exit));
if (exit == null) {
return;
}
}
//Calculate the exit velocity of the player
Vector newVelocityDirection = DirectionHelper.getDirectionVectorFromYaw(portal.getYaw());
Vector newVelocity = newVelocityDirection.multiply(velocity * Stargate.getGateConfig().getExitVelocity());
//Load chunks to make sure not to teleport to the void
loadChunks();
//Teleport any creatures leashed by the player in a 15-block range
TeleportHelper.teleportLeashedCreatures(player, origin, portal);
if (player.eject()) {
TeleportHelper.handleEntityPassengers(passengers, player, origin, portal, exit.getDirection(), newVelocity);
}
//If no event is passed in, assume it's a teleport, and act as such
if (event == null) {
player.teleport(exit);
} else {
//Set the exit location of the event
event.setTo(exit);
}
//Set the velocity of the teleported player after the teleportation is finished
Bukkit.getScheduler().scheduleSyncDelayedTask(Stargate.getInstance(), () -> player.setVelocity(newVelocity), 1);
}
}

View File

@@ -0,0 +1,319 @@
package net.knarcraft.stargate.portal.teleporter;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.BlockLocation;
import net.knarcraft.stargate.container.ChunkUnloadRequest;
import net.knarcraft.stargate.container.RelativeBlockVector;
import net.knarcraft.stargate.event.StargateTeleportEvent;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.utility.DirectionHelper;
import net.knarcraft.stargate.utility.EntityHelper;
import net.knarcraft.stargate.utility.TeleportHelper;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.data.Bisected;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.Slab;
import org.bukkit.entity.AbstractHorse;
import org.bukkit.entity.Entity;
import org.bukkit.event.Event;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.util.Vector;
import java.util.ArrayList;
import java.util.List;
/**
* The portal teleporter takes care of common teleportation logic
*/
public abstract class Teleporter {
/**
* The portal the entity is teleporting to
*/
protected final Portal portal;
/**
* The scheduler to use for delaying tasks
*/
protected final BukkitScheduler scheduler;
/**
* The exit location any entities will be teleported to
*/
protected Location exit;
/**
* The entity being teleported by this teleporter
*/
protected final Entity teleportedEntity;
/**
* Instantiates a new portal teleporter
*
* @param portal <p>The portal which is the target of the teleportation</p>
* @param teleportedEntity <p>The entity teleported by this teleporter</p>
*/
public Teleporter(Portal portal, Entity teleportedEntity) {
this.portal = portal;
this.scheduler = Stargate.getInstance().getServer().getScheduler();
this.teleportedEntity = teleportedEntity;
this.exit = getExit(teleportedEntity);
}
/**
* Teleports an entity
*
* @param origin <p>The portal the entity teleported from</p>
* @param stargateTeleportEvent <p>The event to call to make sure the teleportation is valid</p>
* @return <p>True if the teleportation was successfully performed</p>
*/
public boolean teleport(Portal origin, StargateTeleportEvent stargateTeleportEvent) {
List<Entity> passengers = teleportedEntity.getPassengers();
//Call the StargateEntityPortalEvent to allow plugins to change destination
if (!origin.equals(portal)) {
exit = triggerPortalEvent(origin, stargateTeleportEvent);
if (exit == null) {
return false;
}
}
//Load chunks to make sure not to teleport to the void
loadChunks();
if (teleportedEntity.eject()) {
TeleportHelper.handleEntityPassengers(passengers, teleportedEntity, origin, portal, exit.getDirection(),
new Vector());
}
teleportedEntity.teleport(exit);
return true;
}
/**
* Gets the exit location of this teleporter
*
* @return <p>The exit location of this teleporter</p>
*/
public Location getExit() {
return exit.clone();
}
/**
* Triggers the entity portal event to allow plugins to change the exit location
*
* @param origin <p>The origin portal teleported from</p>
* @param stargateTeleportEvent <p>The exit location to teleport the entity to</p>
* @return <p>The location the entity should be teleported to, or null if the event was cancelled</p>
*/
protected Location triggerPortalEvent(Portal origin, StargateTeleportEvent stargateTeleportEvent) {
Stargate.getInstance().getServer().getPluginManager().callEvent((Event) stargateTeleportEvent);
//Teleport is cancelled. Teleport the entity back to where it came from just for sanity's sake
if (stargateTeleportEvent.isCancelled()) {
new EntityTeleporter(origin, teleportedEntity).teleportEntity(origin);
return null;
}
return stargateTeleportEvent.getExit();
}
/**
* Adjusts the rotation of the exit to make the teleporting entity face directly out from the portal
*
* @param exit <p>The location the entity will exit from</p>
*/
protected void adjustExitLocationRotation(Location exit) {
int adjust = 0;
if (portal.getOptions().isBackwards()) {
adjust = 180;
}
float newYaw = (portal.getYaw() + adjust) % 360;
Stargate.debug("Portal::adjustRotation", "Setting exit yaw to " + newYaw);
exit.setDirection(DirectionHelper.getDirectionVectorFromYaw(newYaw));
}
/**
* Loads the chunks outside the portal's entrance
*/
protected void loadChunks() {
for (Chunk chunk : getChunksToLoad()) {
chunk.addPluginChunkTicket(Stargate.getInstance());
//Allow the chunk to unload after 10 seconds
Stargate.addChunkUnloadRequest(new ChunkUnloadRequest(chunk, 10000L));
}
}
/**
* Adjusts the positioning of the portal exit to prevent the given entity from suffocating
*
* @param relativeExit <p>The relative exit defined as the portal's exit</p>
* @param exitLocation <p>The currently calculated portal exit</p>
* @param entity <p>The travelling entity</p>
* @return <p>A location which won't suffocate the entity inside the portal</p>
*/
private Location preventExitSuffocation(RelativeBlockVector relativeExit, Location exitLocation, Entity entity) {
//Go left to find start of opening
RelativeBlockVector openingLeft = getPortalExitEdge(relativeExit, -1);
//Go right to find the end of the opening
RelativeBlockVector openingRight = getPortalExitEdge(relativeExit, 1);
//Get the width to check if the entity fits
int openingWidth = openingRight.getRight() - openingLeft.getRight() + 1;
int existingOffset = relativeExit.getRight() - openingLeft.getRight();
double newOffset = (openingWidth - existingOffset) / 2D;
//Remove the half offset for better centering
if (openingWidth > 1) {
newOffset -= 0.5;
}
exitLocation = DirectionHelper.moveLocation(exitLocation, newOffset, 0, 0, portal.getYaw());
//Move large entities further from the portal
return moveExitLocationOutwards(exitLocation, entity);
}
/**
* Moves the exit location out from the portal to prevent the entity from entering a teleportation loop
*
* @param exitLocation <p>The current exit location to adjust</p>
* @param entity <p>The entity to adjust the exit location for</p>
* @return <p>The adjusted exit location</p>
*/
private Location moveExitLocationOutwards(Location exitLocation, Entity entity) {
double entitySize = EntityHelper.getEntityMaxSize(entity);
int entityBoxSize = EntityHelper.getEntityMaxSizeInt(entity);
if (entitySize > 1) {
double entityOffset;
if (portal.getOptions().isAlwaysOn()) {
entityOffset = (entityBoxSize / 2D);
} else {
entityOffset = (entitySize / 2D) - 1;
}
//If a horse has a player riding it, the player will spawn inside the roof of a standard portal unless it's
// moved one block out.
if (entity instanceof AbstractHorse) {
entityOffset += 1;
}
exitLocation = DirectionHelper.moveLocation(exitLocation, 0, 0, entityOffset, portal.getYaw());
}
return exitLocation;
}
/**
* Gets one of the edges of a portal's opening/exit
*
* @param relativeExit <p>The known exit to start from</p>
* @param direction <p>The direction to move (+1 for right, -1 for left)</p>
* @return <p>The right or left edge of the opening</p>
*/
private RelativeBlockVector getPortalExitEdge(RelativeBlockVector relativeExit, int direction) {
RelativeBlockVector openingEdge = relativeExit;
do {
RelativeBlockVector possibleOpening = new RelativeBlockVector(openingEdge.getRight() + direction,
openingEdge.getDown(), openingEdge.getOut());
if (portal.getGate().getLayout().getExits().contains(possibleOpening)) {
openingEdge = possibleOpening;
} else {
break;
}
} while (true);
return openingEdge;
}
/**
* Adjusts an exit location by setting pitch and adjusting height
*
* <p>If the exit location is a slab or water, the exit location will be changed to arrive one block above. The
* slab check is necessary to prevent the player from clipping through the slab and spawning beneath it. The water
* check is necessary when teleporting boats to prevent it from becoming a submarine.</p>
*
* @param entity <p>The travelling entity</p>
* @param exitLocation <p>The exit location generated</p>
* @return <p>The location the travelling entity should be teleported to</p>
*/
private Location adjustExitLocationHeight(Entity entity, Location exitLocation) {
if (exitLocation != null) {
BlockData blockData = exitLocation.getBlock().getBlockData();
if ((blockData instanceof Bisected bisected && bisected.getHalf() == Bisected.Half.BOTTOM) ||
(blockData instanceof Slab slab && slab.getType() == Slab.Type.BOTTOM) ||
blockData.getMaterial() == Material.WATER) {
//Prevent traveller from spawning inside a slab, or a boat from spawning inside water
Stargate.debug("adjustExitLocation", "Added a block to get above a slab or a block of water");
exitLocation.add(0, 1, 0);
}
return exitLocation;
} else {
Stargate.logWarning("Unable to generate exit location");
return entity.getLocation();
}
}
/**
* 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>
* @return <p>The location the entity should be teleported to.</p>
*/
private Location getExit(Entity entity) {
Location exitLocation = null;
RelativeBlockVector relativeExit = portal.getGate().getLayout().getExit();
if (relativeExit != null) {
BlockLocation exit = portal.getBlockAt(relativeExit);
//Move one block out to prevent exiting inside the portal
float portalYaw = portal.getYaw();
if (portal.getOptions().isBackwards()) {
portalYaw += 180;
}
exitLocation = exit.getRelativeLocation(0D, 0D, 1, portalYaw);
if (entity != null) {
double entitySize = EntityHelper.getEntityMaxSize(entity);
//Prevent exit suffocation for players riding horses or similar
if (entitySize > 1) {
exitLocation = preventExitSuffocation(relativeExit, exitLocation, entity);
}
}
} else {
Stargate.logWarning(String.format("Missing destination point in .gate file %s",
portal.getGate().getFilename()));
}
//Adjust height and rotation
Location adjusted = adjustExitLocationHeight(entity, exitLocation);
adjustExitLocationRotation(adjusted);
return adjusted;
}
/**
* Gets all relevant chunks near this teleporter's portal's entrance which need to be loaded before teleportation
*
* @return <p>A list of chunks to load</p>
*/
private List<Chunk> getChunksToLoad() {
List<Chunk> chunksToLoad = new ArrayList<>();
for (RelativeBlockVector vector : portal.getGate().getLayout().getEntrances()) {
BlockLocation entranceLocation = portal.getBlockAt(vector);
Chunk chunk = entranceLocation.getChunk();
//Make sure not to load chunks twice
if (!chunksToLoad.contains(chunk)) {
chunksToLoad.add(chunk);
}
//Get the chunk in front of the gate entrance
int blockOffset = portal.getOptions().isBackwards() ? -5 : 5;
Location fiveBlocksForward = DirectionHelper.moveLocation(entranceLocation, 0, 0, blockOffset,
portal.getYaw());
//Load the chunk five blocks forward to make sure the teleported entity will never spawn in unloaded chunks
Chunk forwardChunk = fiveBlocksForward.getChunk();
if (!chunksToLoad.contains(forwardChunk)) {
chunksToLoad.add(forwardChunk);
}
}
return chunksToLoad;
}
}

View File

@@ -0,0 +1,183 @@
package net.knarcraft.stargate.portal.teleporter;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.config.StargateGateConfig;
import net.knarcraft.stargate.event.StargateEntityPortalEvent;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.utility.DirectionHelper;
import net.knarcraft.stargate.utility.TeleportHelper;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Boat;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Vehicle;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.util.Vector;
import java.util.List;
/**
* The portal teleporter takes care of the actual portal teleportation for any vehicles
*/
public class VehicleTeleporter extends EntityTeleporter {
private final Vehicle teleportingVehicle;
/**
* Instantiates a new vehicle teleporter
*
* @param targetPortal <p>The targetPortal which is the target of the teleportation</p>
* @param teleportingVehicle <p>The teleporting vehicle</p>
*/
public VehicleTeleporter(Portal targetPortal, Vehicle teleportingVehicle) {
super(targetPortal, teleportingVehicle);
this.teleportingVehicle = teleportingVehicle;
}
/**
* Teleports a vehicle to this teleporter's portal
*
* <p>It is assumed that if a vehicle contains any players, their permissions have already been validated before
* calling this method.</p>
*
* @param origin <p>The portal the vehicle is teleporting from</p>
* @return <p>True if the vehicle was teleported. False otherwise</p>
*/
@Override
public boolean teleportEntity(Portal origin) {
Stargate.debug("VehicleTeleporter::teleport", "Preparing to teleport: " + teleportingVehicle);
double velocity = teleportingVehicle.getVelocity().length();
//Stop the vehicle before teleporting
teleportingVehicle.setVelocity(new Vector());
//Get new velocity
Vector newVelocityDirection = DirectionHelper.getDirectionVectorFromYaw(portal.getYaw());
Vector newVelocity = newVelocityDirection.multiply(velocity);
//Call the StargateEntityPortalEvent to allow plugins to change destination
exit = triggerPortalEvent(origin, new StargateEntityPortalEvent(teleportingVehicle, origin, portal, exit));
if (exit == null) {
return false;
}
//Teleport the vehicle
return teleportVehicle(exit, newVelocity, origin);
}
/**
* Teleports a vehicle with any passengers to the given location
*
* @param exit <p>The location the vehicle should be teleported to</p>
* @param newVelocity <p>The velocity to give the vehicle right after teleportation</p>
* @param origin <p>The portal the vehicle teleported from</p>
* @return <p>True if the vehicle was teleported. False otherwise</p>
*/
private boolean teleportVehicle(Location exit, Vector newVelocity, Portal origin) {
//Load chunks to make sure not to teleport to the void
loadChunks();
List<Entity> passengers = teleportingVehicle.getPassengers();
if (!passengers.isEmpty()) {
//Check if the passengers are allowed according to current config settings
if (!vehiclePassengersAllowed(passengers)) {
return false;
}
if (!(teleportingVehicle instanceof LivingEntity) &&
Stargate.getGateConfig().enableCraftBookRemoveOnEjectFix()) {
//Teleport a normal vehicle with passengers (minecart or boat)
putPassengersInNewVehicle(passengers, exit, newVelocity, origin);
} else {
//Teleport a living vehicle with passengers (pig, horse, donkey, strider)
teleportVehicle(passengers, exit, newVelocity, origin);
}
} else {
//Check if teleportation of empty vehicles is enabled
if (!Stargate.getGateConfig().handleEmptyVehicles()) {
return false;
}
//Teleport an empty vehicle
teleportingVehicle.teleport(exit);
scheduler.scheduleSyncDelayedTask(Stargate.getInstance(),
() -> teleportingVehicle.setVelocity(newVelocity), 1);
}
return true;
}
/**
* Checks whether current config values allow the teleportation of the given passengers
*
* @param passengers <p>The passengers to teleport</p>
* @return <p>True if the passengers are allowed to teleport</p>
*/
private boolean vehiclePassengersAllowed(List<Entity> passengers) {
StargateGateConfig config = Stargate.getGateConfig();
//Don't teleport if the vehicle contains a creature and creature transportation is disabled
if (TeleportHelper.containsNonPlayer(passengers) && !config.handleCreatureTransportation()) {
return false;
}
//Don't teleport if the player does not contain a player and non-player vehicles is disabled
return TeleportHelper.containsPlayer(passengers) || config.handleNonPlayerVehicles();
}
/**
* Teleport a vehicle which is not a minecart or a boat
*
* @param passengers <p>The passengers of the vehicle</p>
* @param exit <p>The location the vehicle will exit</p>
* @param newVelocity <p>The new velocity of the teleported vehicle</p>
* @param origin <p>The portal the vehicle teleported from</p>
*/
private void teleportVehicle(List<Entity> passengers, Location exit, Vector newVelocity, Portal origin) {
if (teleportingVehicle.eject()) {
TeleportHelper.handleEntityPassengers(passengers, teleportingVehicle, origin, portal, exit.getDirection(),
newVelocity);
}
Stargate.debug("VehicleTeleporter::teleportVehicle", "Teleporting " + teleportingVehicle +
" to final location " + exit + " with direction " + exit.getDirection());
teleportingVehicle.teleport(exit, PlayerTeleportEvent.TeleportCause.PLUGIN);
scheduler.scheduleSyncDelayedTask(Stargate.getInstance(),
() -> {
Stargate.debug("VehicleTeleporter::teleportVehicle", "Setting velocity " + newVelocity +
" for vehicle " + teleportingVehicle);
teleportingVehicle.setVelocity(newVelocity);
}, 1);
}
/**
* Creates a new vehicle equal to the player's previous vehicle and puts any passengers inside
*
* <p>While it is possible to teleport boats and minecarts using the same methods as "teleportLivingVehicle", this
* method works better with CraftBook with minecart options enabled. Using normal teleportation, CraftBook destroys
* the minecart once the player is ejected, causing the minecart to disappear and the player to teleport without it.</p>
*
* @param passengers <p>A list of all passengers in the vehicle</p>
* @param exit <p>The exit location to spawn the new vehicle on</p>
* @param newVelocity <p>The new velocity of the new vehicle</p>
* @param origin <p>The portal the vehicle teleported from</p>
*/
private void putPassengersInNewVehicle(List<Entity> passengers, Location exit,
Vector newVelocity, Portal origin) {
World vehicleWorld = exit.getWorld();
if (vehicleWorld == null) {
Stargate.logWarning("Unable to get the world to teleport the vehicle to");
return;
}
//Spawn a new vehicle
Vehicle newVehicle = vehicleWorld.spawn(exit, teleportingVehicle.getClass());
if (teleportingVehicle instanceof Boat boat) {
((Boat) newVehicle).setBoatType(boat.getBoatType());
}
//Remove the old vehicle
if (teleportingVehicle.eject()) {
TeleportHelper.handleEntityPassengers(passengers, newVehicle, origin, portal, exit.getDirection(),
newVelocity);
}
teleportingVehicle.remove();
scheduler.scheduleSyncDelayedTask(Stargate.getInstance(), () -> newVehicle.setVelocity(newVelocity), 1);
}
}

View File

@@ -0,0 +1,83 @@
package net.knarcraft.stargate.thread;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.BlockChangeRequest;
import org.bukkit.Axis;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.EndGateway;
import org.bukkit.block.data.Orientable;
/**
* This thread changes gate blocks to display a gate as open or closed
*
* <p>This thread fetches some entries from blockPopulateQueue each time it's called.</p>
*/
public class BlockChangeThread implements Runnable {
@Override
public void run() {
long sTime = System.nanoTime();
//Repeat for at most 0.025 seconds
while (System.nanoTime() - sTime < 25000000) {
if (pollQueue()) {
break;
}
}
}
/**
* Polls the block change request queue for any waiting requests
*
* @return <p>True if the queue is empty and it's safe to quit</p>
*/
public static boolean pollQueue() {
//Abort if there's no work to be done
BlockChangeRequest blockChangeRequest = Stargate.getBlockChangeRequestQueue().poll();
if (blockChangeRequest == null) {
return true;
}
//Change the material of the pulled block
Block block = blockChangeRequest.getBlockLocation().getBlock();
block.setType(blockChangeRequest.getMaterial(), false);
if (blockChangeRequest.getMaterial() == Material.END_GATEWAY) {
//Force a specific location to prevent exit gateway generation
fixEndGatewayGate(block);
} else if (blockChangeRequest.getAxis() != null) {
//If orientation is relevant, adjust the block's orientation
orientBlock(block, blockChangeRequest.getAxis());
}
return false;
}
/**
* Prevents end gateway portal from behaving strangely
*
* @param block <p>The block to fix</p>
*/
private static void fixEndGatewayGate(Block block) {
EndGateway gateway = (EndGateway) block.getState();
gateway.setAge(Long.MIN_VALUE);
if (block.getWorld().getEnvironment() == World.Environment.THE_END) {
gateway.setExitLocation(block.getLocation());
gateway.setExactTeleport(true);
}
gateway.update(false, false);
}
/**
* Sets the orientation axis of the placed block
*
* @param block <p>The block to orient</p>
* @param axis <p>The axis to use for orienting the block</p>
*/
private static void orientBlock(Block block, Axis axis) {
Orientable orientable = (Orientable) block.getBlockData();
orientable.setAxis(axis);
block.setBlockData(orientable);
}
}

View File

@@ -0,0 +1,31 @@
package net.knarcraft.stargate.thread;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.ChunkUnloadRequest;
import org.bukkit.Chunk;
import java.util.Queue;
/**
* Unloads chunks which should no longer be forced to stay loaded
*/
public class ChunkUnloadThread implements Runnable {
@Override
public void run() {
long systemNanoTime = System.nanoTime();
Queue<ChunkUnloadRequest> unloadQueue = Stargate.getChunkUnloadQueue();
//Peek at the first element to check if the chunk should be unloaded
ChunkUnloadRequest firstElement = unloadQueue.peek();
//Repeat until all un-loadable chunks have been processed
while (firstElement != null && firstElement.getUnloadNanoTime() < systemNanoTime) {
unloadQueue.remove();
Chunk chunkToUnload = firstElement.getChunkToUnload();
//Allow the chunk to be unloaded
chunkToUnload.removePluginChunkTicket(Stargate.getInstance());
firstElement = unloadQueue.peek();
}
}
}

View File

@@ -0,0 +1,66 @@
package net.knarcraft.stargate.thread;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.portal.Portal;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
/**
* This class contains the function used to close servers which should no longer be open/active
*/
public class StarGateThread implements Runnable {
@Override
public void run() {
long time = System.currentTimeMillis() / 1000;
closeOpenPortals(time);
deactivateActivePortals(time);
}
/**
* Closes portals which are open and have timed out
*
* @param time <p>The current time</p>
*/
private void closeOpenPortals(long time) {
List<Portal> closedPortals = new ArrayList<>();
Queue<Portal> openPortalsQueue = Stargate.getStargateConfig().getOpenPortalsQueue();
for (Portal portal : openPortalsQueue) {
//Skip always open and non-open gates
if (portal.getOptions().isAlwaysOn() || !portal.isOpen()) {
continue;
}
if (time > portal.getTriggeredTime() + Stargate.getGateConfig().getOpenTime()) {
portal.getPortalOpener().closePortal(false);
closedPortals.add(portal);
}
}
openPortalsQueue.removeAll(closedPortals);
}
/**
* De-activates portals which are active and have timed out
*
* @param time <p>The current time</p>
*/
private void deactivateActivePortals(long time) {
List<Portal> deactivatedPortals = new ArrayList<>();
Queue<Portal> activePortalsQueue = Stargate.getStargateConfig().getActivePortalsQueue();
for (Portal portal : activePortalsQueue) {
//Skip portals which aren't active
if (!portal.getPortalActivator().isActive()) {
continue;
}
if (time > portal.getTriggeredTime() + Stargate.getGateConfig().getActiveTime()) {
portal.getPortalActivator().deactivate();
deactivatedPortals.add(portal);
}
}
activePortalsQueue.removeAll(deactivatedPortals);
}
}

View File

@@ -0,0 +1,221 @@
package net.knarcraft.stargate.utility;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalHandler;
import net.knarcraft.stargate.portal.teleporter.PlayerTeleporter;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerMoveEvent;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* This class contains helpful functions to help with sending and receiving BungeeCord plugin messages
*/
public final class BungeeHelper {
private final static String bungeeSubChannel = "SGBungee";
private final static String bungeeChannel = "BungeeCord";
private final static String teleportMessageDelimiter = "#@#";
private final static Map<UUID, String> bungeeQueue = new HashMap<>();
private BungeeHelper() {
}
/**
* Get the plugin message channel use for BungeeCord messages
*
* @return <p>The bungee plugin channel</p>
*/
public static String getBungeeChannel() {
return bungeeChannel;
}
/**
* Removes a player from the queue of players teleporting through BungeeCord
*
* <p>Whenever a BungeeCord teleportation message is received and the player is not currently connected to this
* server, it'll be added to this queue. Once the player joins this server, the player should be removed from the
* queue and teleported to the destination.</p>
*
* @param playerUUID <p>The UUID of the player to remove</p>
* @return <p>The name of the destination portal the player should be teleported to</p>
*/
public static String removeFromQueue(UUID playerUUID) {
return bungeeQueue.remove(playerUUID);
}
/**
* Sends a plugin message to BungeeCord allowing the target server to catch it
*
* @param player <p>The teleporting player</p>
* @param entrancePortal <p>The portal the player is teleporting from</p>
* @return <p>True if the message was successfully sent</p>
*/
public static boolean sendTeleportationMessage(Player player, Portal entrancePortal) {
try {
//Build the teleportation message, format is <player identifier>delimiter<destination>
String message = player.getUniqueId() + teleportMessageDelimiter + entrancePortal.getDestinationName();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream);
//Build the message data and send it over the SGBungee BungeeCord channel
dataOutputStream.writeUTF("Forward");
//Send the message to the server defined in the entrance portal's network line
dataOutputStream.writeUTF(stripColor(entrancePortal.getNetwork()));
//Specify the sub-channel/tag to make it recognizable on arrival
dataOutputStream.writeUTF(bungeeSubChannel);
//Write the length of the message
dataOutputStream.writeShort(message.length());
//Write the actual message
dataOutputStream.writeBytes(message);
//Send the plugin message
player.sendPluginMessage(Stargate.getInstance(), bungeeChannel, byteArrayOutputStream.toByteArray());
} catch (IOException ex) {
Stargate.logSevere("Error sending BungeeCord teleport packet");
ex.printStackTrace();
return false;
}
return true;
}
/**
* Sends the bungee message necessary to make a player connect to another server
*
* @param player <p>The player to teleport</p>
* @param entrancePortal <p>The bungee portal the player is teleporting from</p>
* @return <p>True if the plugin message was sent successfully</p>
*/
public static boolean changeServer(Player player, Portal entrancePortal) {
try {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream);
//Send a connect-message to connect the player to the server defined in the entrance portal's network line
dataOutputStream.writeUTF("Connect");
dataOutputStream.writeUTF(stripColor(entrancePortal.getNetwork()));
//Send the plugin message
player.sendPluginMessage(Stargate.getInstance(), bungeeChannel, byteArrayOutputStream.toByteArray());
} catch (IOException ex) {
Stargate.logSevere("Error sending BungeeCord connect packet");
ex.printStackTrace();
return false;
}
return true;
}
/**
* Reads a plugin message byte array to a string if it's sent from another stargate plugin
*
* @param message <p>The byte array to read</p>
* @return <p>The message contained in the byte array, or null on failure</p>
*/
public static String readPluginMessage(byte[] message) {
byte[] data;
try {
DataInputStream dataInputStream = new DataInputStream(new ByteArrayInputStream(message));
String subChannel = dataInputStream.readUTF();
//Only listen for the SGBungee channel
if (!subChannel.equals(bungeeSubChannel)) {
return null;
}
//Get the length of the contained message
short dataLength = dataInputStream.readShort();
//Prepare a byte array for the sent message
data = new byte[dataLength];
//Read the message to the prepared array
dataInputStream.readFully(data);
} catch (IOException ex) {
Stargate.logSevere("Error receiving BungeeCord message");
ex.printStackTrace();
return null;
}
return new String(data);
}
/**
* Handles the receival of a teleport message
*
* @param receivedMessage <p>The received teleport message</p>
*/
public static void handleTeleportMessage(String receivedMessage) {
//Get the player id and destination from the message
String[] messageParts = receivedMessage.split(teleportMessageDelimiter);
UUID playerUUID = UUID.fromString(messageParts[0]);
String destination = messageParts[1];
//Check if the player is online, if so, teleport, otherwise, queue
Player player = Stargate.getInstance().getServer().getPlayer(playerUUID);
if (player == null) {
bungeeQueue.put(playerUUID, destination);
} else {
Portal destinationPortal = PortalHandler.getBungeePortal(destination);
//If teleporting to an invalid portal, let the server decide where the player arrives
if (destinationPortal == null) {
Stargate.logInfo(String.format("Bungee portal %s does not exist", destination));
return;
}
new PlayerTeleporter(destinationPortal, player).teleport(destinationPortal, null);
}
}
/**
* Teleports a player to a bungee gate
*
* @param player <p>The player to teleport</p>
* @param entrancePortal <p>The gate the player is entering from</p>
* @param event <p>The event causing the teleportation</p>
* @return <p>True if the teleportation was successful</p>
*/
public static boolean bungeeTeleport(Player player, Portal entrancePortal, PlayerMoveEvent event) {
//Check if bungee is actually enabled
if (!Stargate.getGateConfig().enableBungee()) {
if (!entrancePortal.getOptions().isSilent()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("bungeeDisabled"));
}
entrancePortal.getPortalOpener().closePortal(false);
return false;
}
//Teleport the player back to this gate, for sanity's sake
new PlayerTeleporter(entrancePortal, player).teleportPlayer(entrancePortal, event);
//Send the SGBungee packet first, it will be queued by BC if required
if (!BungeeHelper.sendTeleportationMessage(player, entrancePortal)) {
Stargate.debug("bungeeTeleport", "Unable to send teleportation message");
return false;
}
//Send the connect-message to make the player change server
if (!BungeeHelper.changeServer(player, entrancePortal)) {
Stargate.debug("bungeeTeleport", "Unable to change server");
return false;
}
Stargate.debug("bungeeTeleport", "Teleported player to another server");
return true;
}
/**
* Strips all color tags from a string
*
* @param string <p>The string to strip color from</p>
* @return <p>The string without color codes</p>
*/
private static String stripColor(String string) {
return ChatColor.stripColor(ChatColor.translateAlternateColorCodes('&', string));
}
}

View File

@@ -0,0 +1,147 @@
package net.knarcraft.stargate.utility;
import org.bukkit.Location;
import org.bukkit.block.BlockFace;
import org.bukkit.util.Vector;
/**
* This class helps with direction-related calculations
*/
public final class DirectionHelper {
private DirectionHelper() {
}
/**
* Gets a yaw by comparing two locations
*
* <p>The yaw here is the direction an observer a the first location has to look to face the second location.
* The yaw is only meant to be calculated for locations where both have either the same x value or the same z value.
* Equal locations, or locations with equal x and equal z will throw an exception.</p>
*
* @param location1 <p>The first location, which works as the origin</p>
* @param location2 <p>The second location, which the yaw will point towards</p>
* @return <p>The yaw pointing from the first location to the second location</p>
*/
public static float getYawFromLocationDifference(Location location1, Location location2) {
Location difference = location1.clone().subtract(location2.clone());
if (difference.getX() > 0) {
return 90;
} else if (difference.getX() < 0) {
return 270;
} else if (difference.getZ() > 0) {
return 180;
} else if (difference.getZ() < 0) {
return 0;
}
throw new IllegalArgumentException("Locations given are equal or at the same x and y axis");
}
/**
* Gets a block face given a yaw value
*
* <p>The supplied yaw must be a value such that (yaw mod 90) = 0. If not, an exception is thrown.</p>
*
* @param yaw <p>The yaw value to convert</p>
* @return <p>The block face the yaw corresponds to</p>
*/
public static BlockFace getBlockFaceFromYaw(double yaw) {
//Make sure the yaw is between 0 and 360
yaw = normalizeYaw(yaw);
if (yaw == 0) {
return BlockFace.SOUTH;
} else if (yaw == 90) {
return BlockFace.WEST;
} else if (yaw == 180) {
return BlockFace.NORTH;
} else if (yaw == 270) {
return BlockFace.EAST;
} else {
throw new IllegalArgumentException("Invalid yaw given. Yaw must be divisible by 90.");
}
}
/**
* Gets a direction vector given a yaw
*
* @param yaw <p>The yaw to convert to a direction vector</p>
* @return <p>The direction vector pointing in the same direction as the yaw</p>
*/
public static Vector getDirectionVectorFromYaw(double yaw) {
//Make sure the yaw is between 0 and 360
yaw = normalizeYaw(yaw);
if (yaw == 0) {
return new Vector(0, 0, 1);
} else if (yaw == 90) {
return new Vector(-1, 0, 0);
} else if (yaw == 180) {
return new Vector(0, 0, -1);
} else if (yaw == 270) {
return new Vector(1, 0, 0);
} else {
throw new IllegalArgumentException(String.format("Invalid yaw %f given", yaw));
}
}
/**
* Moves a location by the given amounts
*
* <p>The right, down and out work the same as for the relative block vector. Looking a the front of a portal,
* right goes rightwards, down goes downwards and out goes towards the observer.</p>
*
* @param location <p>The location to start at</p>
* @param right <p>The amount to go right</p>
* @param down <p>The amount to go downward</p>
* @param out <p>The amount to go outward</p>
* @param yaw <p>The yaw when looking directly outwards from a portal</p>
* @return <p>A location relative to the given location</p>
*/
public static Location moveLocation(Location location, double right, double down, double out, double yaw) {
return location.add(getCoordinateVectorFromRelativeVector(right, down, out, yaw));
}
/**
* Gets a vector in Minecraft's normal X,Y,Z-space from a relative block vector
*
* @param right <p>The amount of rightward steps from the top-left origin</p>
* @param down <p>The amount of downward steps from the top-left origin</p>
* @param out <p>The distance outward from the top-left origin</p>
* @param yaw <p>The yaw when looking directly outwards from a portal</p>
* @return <p>A normal vector</p>
*/
public static Vector getCoordinateVectorFromRelativeVector(double right, double down, double out, double yaw) {
if (yaw == 0) {
//South
return new Vector(right, -down, out);
} else if (yaw == 90) {
//West
return new Vector(-out, -down, right);
} else if (yaw == 180) {
//North
return new Vector(-right, -down, -out);
} else if (yaw == 270) {
//East
return new Vector(out, -down, -right);
} else {
throw new IllegalArgumentException(String.format("Invalid yaw %f given", yaw));
}
}
/**
* Normalizes a yaw to make it positive and no larger than 360 degrees
*
* @param yaw <p>The yaw to normalize</p>
* @return <p>The normalized yaw</p>
*/
private static double normalizeYaw(double yaw) {
while (yaw < 0) {
yaw += 360;
}
yaw = yaw % 360;
return yaw;
}
}

View File

@@ -0,0 +1,248 @@
package net.knarcraft.stargate.utility;
import net.knarcraft.knarlib.formatting.StringFormatter;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.config.EconomyConfig;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.property.PortalOwner;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.UUID;
/**
* The economy helper class has helper functions for player payment
*/
public final class EconomyHelper {
private EconomyHelper() {
}
/**
* Tries to make the given user pay the teleport fee
*
* @param entrancePortal <p>The portal the player is entering</p>
* @param player <p>The player wishing to teleport</p>
* @param cost <p>The cost of teleportation</p>
* @return <p>False if payment was successful. True if the payment was unsuccessful</p>
*/
public static boolean cannotPayTeleportFee(Portal entrancePortal, Player player, int cost) {
boolean success;
//Try to charge the player. Paying the portal owner is only possible if a UUID is available
UUID ownerUUID = entrancePortal.getOwner().getUUID();
if (ownerUUID == null) {
Stargate.logWarning(String.format("The owner of the portal %s does not have a UUID and payment to owner " +
"was therefore not possible. Make the owner re-create the portal to fix this.", entrancePortal));
}
if (entrancePortal.getGate().getToOwner() && ownerUUID != null) {
success = chargePlayerIfNecessary(player, ownerUUID, cost);
} else {
success = chargePlayerIfNecessary(player, cost);
}
//Send the insufficient funds message
if (!success) {
sendInsufficientFundsMessage(entrancePortal.getName(), player, cost);
entrancePortal.getPortalOpener().closePortal(false);
return true;
}
//Send the deduct-message to the player
sendDeductMessage(entrancePortal.getName(), player, cost);
if (entrancePortal.getGate().getToOwner()) {
PortalOwner owner = entrancePortal.getOwner();
Player portalOwner;
if (owner.getUUID() != null) {
portalOwner = Stargate.getInstance().getServer().getPlayer(owner.getUUID());
} else {
portalOwner = Stargate.getInstance().getServer().getPlayer(owner.getName());
}
//Notify the gate owner of received payment
if (portalOwner != null) {
sendObtainMessage(entrancePortal.getName(), portalOwner, cost);
}
}
return false;
}
/**
* Sends a message to the gate owner telling him/her how much he/she earned from a player using his/her gate
*
* @param portalName <p>The name of the used portal</p>
* @param portalOwner <p>The owner of the portal</p>
* @param earnings <p>The amount the owner earned</p>
*/
public static void sendObtainMessage(String portalName, Player portalOwner, int earnings) {
String obtainedMsg = Stargate.getString("ecoObtain");
obtainedMsg = replacePlaceholders(obtainedMsg, portalName, earnings);
Stargate.getMessageSender().sendSuccessMessage(portalOwner, obtainedMsg);
}
/**
* Sends a message telling the user how much they paid for interacting with a portal
*
* @param portalName <p>The name of the portal interacted with</p>
* @param player <p>The interacting player</p>
* @param cost <p>The cost of the interaction</p>
*/
public static void sendDeductMessage(String portalName, Player player, int cost) {
String deductMsg = Stargate.getString("ecoDeduct");
deductMsg = replacePlaceholders(deductMsg, portalName, cost);
Stargate.getMessageSender().sendSuccessMessage(player, deductMsg);
}
/**
* Sends a message telling the user they don't have enough funds to do a portal interaction
*
* @param portalName <p>The name of the portal interacted with</p>
* @param player <p>The interacting player</p>
* @param cost <p>The cost of the interaction</p>
*/
public static void sendInsufficientFundsMessage(String portalName, Player player, int cost) {
String inFundMsg = Stargate.getString("ecoInFunds");
inFundMsg = replacePlaceholders(inFundMsg, portalName, cost);
Stargate.getMessageSender().sendErrorMessage(player, inFundMsg);
}
/**
* Sends a message telling the user how much they are refunded for breaking their portal
*
* @param portalName <p>The name of the broken portal</p>
* @param player <p>The player breaking the portal</p>
* @param cost <p>The amount the user has to pay for destroying the portal. (expects a negative value)</p>
*/
public static void sendRefundMessage(String portalName, Player player, int cost) {
String refundMsg = Stargate.getString("ecoRefund");
refundMsg = replacePlaceholders(refundMsg, portalName, -cost);
Stargate.getMessageSender().sendSuccessMessage(player, refundMsg);
}
/**
* Determines the cost of using a gate
*
* @param player <p>The player trying to use the gate</p>
* @param source <p>The source/entry portal</p>
* @param destination <p>The destination portal</p>
* @return <p>The cost of using the portal</p>
*/
public static int getUseCost(Player player, Portal source, Portal destination) {
EconomyConfig config = Stargate.getEconomyConfig();
//No payment required
if (!config.useEconomy() || source.getOptions().isFree()) {
return 0;
}
//Not charging for free destinations
if (destination != null && config.freeIfFreeDestination() && destination.getOptions().isFree()) {
return 0;
}
//Cost is 0 if the player owns this gate and funds go to the owner
if (source.getGate().getToOwner() && source.isOwner(player)) {
return 0;
}
//Player gets free gate use
if (PermissionHelper.hasPermission(player, "stargate.free.use")) {
return 0;
}
return source.getGate().getUseCost();
}
/**
* Charges the player for an action, if required
*
* @param player <p>The player to take money from</p>
* @param target <p>The target to pay</p>
* @param cost <p>The cost of the transaction</p>
* @return <p>True if the player was charged successfully</p>
*/
public static boolean chargePlayerIfNecessary(Player player, UUID target, int cost) {
if (skipPayment(cost)) {
return true;
}
//Charge player
return chargePlayer(player, target, cost);
}
/**
* Charges a player
*
* @param player <p>The player to charge</p>
* @param amount <p>The amount to charge</p>
* @return <p>True if the payment succeeded, or if no payment was necessary</p>
*/
private static boolean chargePlayer(Player player, double amount) {
Economy economy = Stargate.getEconomyConfig().getEconomy();
if (Stargate.getEconomyConfig().isEconomyEnabled() && economy != null) {
if (!economy.has(player, amount)) {
return false;
}
economy.withdrawPlayer(player, amount);
}
return true;
}
/**
* Charges the player for an action, if required
*
* @param player <p>The player to take money from</p>
* @param cost <p>The cost of the transaction</p>
* @return <p>True if the player was charged successfully</p>
*/
public static boolean chargePlayerIfNecessary(Player player, int cost) {
if (skipPayment(cost)) {
return true;
}
//Charge player
return chargePlayer(player, cost);
}
/**
* Checks whether a payment transaction should be skipped
*
* @param cost <p>The cost of the transaction</p>
* @return <p>True if the transaction should be skipped</p>
*/
private static boolean skipPayment(int cost) {
return cost == 0 || !Stargate.getEconomyConfig().useEconomy();
}
/**
* Charges a player, giving the charge to a target
*
* @param player <p>The player to charge</p>
* @param target <p>The UUID of the player to pay</p>
* @param amount <p>The amount to charge</p>
* @return <p>True if the payment succeeded, or if no payment was necessary</p>
*/
private static boolean chargePlayer(Player player, UUID target, double amount) {
Economy economy = Stargate.getEconomyConfig().getEconomy();
if (Stargate.getEconomyConfig().isEconomyEnabled() && player.getUniqueId().compareTo(target) != 0 && economy != null) {
if (!economy.has(player, amount)) {
return false;
}
//Take money from the user and give to the owner
economy.withdrawPlayer(player, amount);
economy.depositPlayer(Bukkit.getOfflinePlayer(target), amount);
}
return true;
}
/**
* Replaces the cost and portal variables in a string
*
* @param message <p>The message to replace variables in</p>
* @param portalName <p>The name of the relevant portal</p>
* @param cost <p>The cost for a given interaction</p>
* @return <p>The same string with cost and portal variables replaced</p>
*/
private static String replacePlaceholders(String message, String portalName, int cost) {
return StringFormatter.replacePlaceholders(message, new String[]{"%cost%", "%portal%"},
new String[]{Stargate.getEconomyConfig().format(cost), portalName});
}
}

View File

@@ -0,0 +1,38 @@
package net.knarcraft.stargate.utility;
import org.bukkit.entity.Entity;
/**
* This helper class helps with entity properties not immediately available
*/
public final class EntityHelper {
private EntityHelper() {
}
/**
* Gets the max size of an entity along its x and z axis
*
* <p>This function gets the ceiling of the max size of an entity, thus calculating the smallest box, using whole
* blocks as unit, needed to contain the entity. Assuming n is returned, an (n x n) box is needed to contain the
* entity.</p>
*
* @param entity <p>The entity to get max size for</p>
* @return <p>The max size of the entity</p>
*/
public static int getEntityMaxSizeInt(Entity entity) {
return (int) Math.ceil((float) getEntityMaxSize(entity));
}
/**
* Gets the max size of an entity along its x and z axis
*
* @param entity <p>The entity to get max size for</p>
* @return <p>The max size of the entity</p>
*/
public static double getEntityMaxSize(Entity entity) {
return Math.max(entity.getBoundingBox().getWidthX(), entity.getBoundingBox().getWidthZ());
}
}

View File

@@ -0,0 +1,210 @@
package net.knarcraft.stargate.utility;
import net.knarcraft.stargate.Stargate;
import org.bukkit.Material;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
/**
* Helper class for reading gate files
*/
public final class GateReader {
private GateReader() {
}
/**
* Reads a gate file
*
* @param scanner <p>The scanner to read from</p>
* @param characterMaterialMap <p>The map of characters to store valid symbols in</p>
* @param fileName <p>The filename of the loaded gate config file</p>
* @param design <p>The list to store the loaded design/layout to</p>
* @param frameTypes <p>The set to store frame/border materials to</p>
* @param config <p>The map of config values to store to</p>
* @return <p>The column count/width of the loaded gate</p>
*/
public static int readGateFile(Scanner scanner, Map<Character, Material> characterMaterialMap, String fileName,
List<List<Character>> design, Set<Material> frameTypes, Map<String, String> config) {
boolean designing = false;
int columns = 0;
try {
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
if (designing) {
//If we have reached the gate's layout/design, read it
columns = readGateDesignLine(line, columns, characterMaterialMap, fileName, design);
if (columns < 0) {
return -1;
}
} else {
if (!line.isEmpty() && !line.startsWith("#")) {
//Read a normal config value
readGateConfigValue(line, characterMaterialMap, frameTypes, config);
} else if ((line.isEmpty()) || (!line.contains("=") && !line.startsWith("#"))) {
//An empty line marks the start of the gate's layout/design
designing = true;
}
}
}
} catch (Exception exception) {
Stargate.logSevere(String.format("Could not load Gate %s - %s", fileName, exception.getMessage()));
return -1;
} finally {
if (scanner != null) {
scanner.close();
}
}
return columns;
}
/**
* Reads one design line of the gate layout file
*
* <p>The max columns value is sent through this method in such a way that when the last gate design line is read,
* the max columns value contains the largest amount of columns (character) found in any of the design's lines.</p>
*
* @param line <p>The line to read</p>
* @param maxColumns <p>The current max columns value of the design</p>
* @param characterMaterialMap <p>The map between characters and the corresponding materials to use</p>
* @param fileName <p>The filename of the loaded gate config file</p>
* @param design <p>The two-dimensional list to store the loaded design to</p>
* @return <p>The new max columns value of the design</p>
*/
private static int readGateDesignLine(String line, int maxColumns, Map<Character, Material> characterMaterialMap,
String fileName, List<List<Character>> design) {
List<Character> row = new ArrayList<>();
//Update the max columns number if this line has more columns
if (line.length() > maxColumns) {
maxColumns = line.length();
}
for (Character symbol : line.toCharArray()) {
//Refuse read gate designs with unknown characters
if (symbol.equals('?') || (!characterMaterialMap.containsKey(symbol))) {
Stargate.logSevere(String.format("Could not load Gate %s - Unknown symbol '%s' in diagram", fileName,
symbol));
return -1;
}
//Add the read character to the row
row.add(symbol);
}
//Add this row of the gate's design to the two-dimensional design list
design.add(row);
return maxColumns;
}
/**
* Reads one config value from the gate layout file
*
* @param line <p>The line to read</p>
* @param characterMaterialMap <p>The character to material map to store to</p>
* @param frameTypes <p>The set to store gate frame/border types to</p>
* @param config <p>The config value map to store to</p>
* @throws Exception <p>If an invalid material is encountered</p>
*/
private static void readGateConfigValue(String line, Map<Character, Material> characterMaterialMap,
Set<Material> frameTypes, Map<String, String> config) throws Exception {
String[] split = line.split("=");
String key = split[0].trim();
String value = split[1].trim();
if (key.length() == 1) {
//Read a gate frame material
Character symbol = key.charAt(0);
Material material = Material.getMaterial(value);
if (material == null) {
throw new Exception("Invalid material in line: " + line);
}
//Register the map between the read symbol and the corresponding material
characterMaterialMap.put(symbol, material);
//Save the material as one of the frame materials used for this kind of gate
frameTypes.add(material);
} else {
//Read a normal config value
config.put(key, value);
}
}
/**
* Reads an integer configuration value
*
* @param config <p>The configuration to read</p>
* @param fileName <p>The filename of the config file</p>
* @param key <p>The config key to read</p>
* @return <p>The read value, or -1 if it could not be read</p>
*/
public static int readGateConfig(Map<String, String> config, String fileName, String key) {
if (config.containsKey(key)) {
try {
return Integer.parseInt(config.get(key));
} catch (NumberFormatException ex) {
Stargate.logWarning(String.format("%s reading %s: %s is not numeric", ex.getClass().getName(),
fileName, key));
}
}
return -1;
}
/**
* Reads a material configuration value
*
* @param config <p>The configuration to read</p>
* @param fileName <p>The filename of the config file</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 specified in the config, or the default material if it could not be read</p>
*/
public static Material readGateConfig(Map<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.logWarning(String.format("Error reading %s: %s is not a material", fileName, key));
}
}
return defaultMaterial;
}
/**
* Generates a matrix containing the gate layout
*
* <p>This basically changes the list of lists into a primitive matrix. Additionally, spaces are added to the end of
* each row which to too short relative to the longest row.</p>
*
* @param design <p>The design of the gate layout</p>
* @param columns <p>The largest amount of columns in the design</p>
* @return <p>A matrix containing the gate's layout</p>
*/
public static Character[][] generateLayoutMatrix(List<List<Character>> design, int columns) {
Character[][] layout = new Character[design.size()][columns];
for (int lineIndex = 0; lineIndex < design.size(); lineIndex++) {
List<Character> row = design.get(lineIndex);
Character[] result = new Character[columns];
for (int rowIndex = 0; rowIndex < columns; rowIndex++) {
if (rowIndex < row.size()) {
result[rowIndex] = row.get(rowIndex);
} else {
//Add spaces to all lines which are too short
result[rowIndex] = ' ';
}
}
layout[lineIndex] = result;
}
return layout;
}
}

View File

@@ -0,0 +1,52 @@
package net.knarcraft.stargate.utility;
import org.bukkit.Material;
import org.bukkit.Tag;
/**
* This class helps decide properties of materials not already present in the Spigot API
*/
public final class MaterialHelper {
private MaterialHelper() {
}
/**
* Checks whether the given material is a dead or alive wall coral
*
* @param material <p>The material to check</p>
* @return <p>True if the material is a wall coral</p>
*/
public static boolean isWallCoral(Material material) {
//Unfortunately, there is no tag for dead wall corals, so they need to be checked manually
return Tag.WALL_CORALS.isTagged(material) ||
material.equals(Material.DEAD_BRAIN_CORAL_WALL_FAN) ||
material.equals(Material.DEAD_BUBBLE_CORAL_WALL_FAN) ||
material.equals(Material.DEAD_FIRE_CORAL_WALL_FAN) ||
material.equals(Material.DEAD_HORN_CORAL_WALL_FAN) ||
material.equals(Material.DEAD_TUBE_CORAL_WALL_FAN);
}
/**
* Checks whether the given material is a container
*
* @param material <p>The material to check</p>
* @return <p>True if the material is a container</p>
*/
public static boolean isContainer(Material material) {
return Tag.SHULKER_BOXES.isTagged(material) || material == Material.CHEST ||
material == Material.TRAPPED_CHEST || material == Material.ENDER_CHEST;
}
/**
* Checks whether the given material can be used as a button
*
* @param material <p>The material to check</p>
* @return <p>True if the material can be used as a button</p>
*/
public static boolean isButtonCompatible(Material material) {
return Tag.BUTTONS.isTagged(material) || isWallCoral(material) || isContainer(material);
}
}

View File

@@ -0,0 +1,417 @@
package net.knarcraft.stargate.utility;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.event.StargateAccessEvent;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.property.PortalOption;
import net.knarcraft.stargate.portal.teleporter.PlayerTeleporter;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerMoveEvent;
import static net.knarcraft.stargate.Stargate.getMaxNameNetworkLength;
/**
* Helper class for deciding which actions a player is allowed to perform
*/
public final class PermissionHelper {
private PermissionHelper() {
}
/**
* Opens a portal if the given player is allowed to, and if the portal is not already open
*
* @param player <p>The player opening the portal</p>
* @param portal <p>The portal to open</p>
*/
public static void openPortal(Player player, Portal portal) {
Portal destination = portal.getPortalActivator().getDestination();
//For an always open portal, no action is necessary
if (portal.getOptions().isAlwaysOn()) {
return;
}
//Destination is invalid or the same portal. Send an error message
if (destination == null || destination == portal) {
if (!portal.getOptions().isSilent()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("invalidMsg"));
}
return;
}
//Portal is already open
if (portal.isOpen()) {
//Close the portal if this player opened the portal
if (portal.getActivePlayer() == player) {
portal.getPortalOpener().closePortal(false);
}
return;
}
//Deny access if another player has activated the portal, and it's still in use
if (!portal.getOptions().isFixed() && portal.getPortalActivator().isActive() &&
portal.getActivePlayer() != player) {
if (!portal.getOptions().isSilent()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("denyMsg"));
}
return;
}
//Check if the player can use the private gate
if (portal.getOptions().isPrivate() && !PermissionHelper.canUsePrivatePortal(player, portal)) {
if (!portal.getOptions().isSilent()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("denyMsg"));
}
return;
}
//Destination is currently in use by another player, blocking teleportation
if (destination.isOpen() && !destination.getOptions().isAlwaysOn()) {
if (!portal.getOptions().isSilent()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("blockMsg"));
}
return;
}
//Open the portal
portal.getPortalOpener().openPortal(player, false);
}
/**
* Creates a StargateAccessEvent and gets the updated deny value
*
* <p>The event is used for other plugins to bypass the permission checks.</p>
*
* @param player <p>The player trying to use the portal</p>
* @param portal <p>The portal the player is trying to use</p>
* @param deny <p>Whether the player's access has already been denied by a previous check</p>
* @return <p>False if the player should be allowed through the portal</p>
*/
public static boolean portalAccessDenied(Player player, Portal portal, boolean deny) {
StargateAccessEvent event = new StargateAccessEvent(player, portal, deny);
Stargate.getInstance().getServer().getPluginManager().callEvent(event);
return event.getDeny();
}
/**
* Checks whether a given user cannot travel between two portals
*
* @param player <p>The player to check</p>
* @param entrancePortal <p>The portal the user wants to enter</p>
* @param destination <p>The portal the user wants to exit from</p>
* @return <p>False if the user is allowed to access the portal</p>
*/
public static boolean cannotAccessPortal(Player player, Portal entrancePortal, Portal destination) {
boolean deny = false;
if (entrancePortal.getOptions().isBungee()) {
if (!PermissionHelper.canAccessServer(player, entrancePortal.getCleanNetwork())) {
//If the portal is a bungee portal, and the player cannot access the server, deny
Stargate.debug("cannotAccessPortal", "Cannot access server");
deny = true;
}
} else if (PermissionHelper.cannotAccessNetwork(player, entrancePortal.getCleanNetwork())) {
//If the player does not have access to the network, deny
Stargate.debug("cannotAccessPortal", "Cannot access network");
deny = true;
} else if (PermissionHelper.cannotAccessWorld(player, destination.getWorld().getName())) {
//If the player does not have access to the portal's world, deny
Stargate.debug("cannotAccessPortal", "Cannot access world");
deny = true;
}
//Allow other plugins to override whether the player can access the portal
return portalAccessDenied(player, entrancePortal, deny);
}
/**
* Checks whether a player has the given permission
*
* <p>This is the same as player.hasPermission(), but this function allows for printing permission debugging info.</p>
*
* @param player <p>The player to check</p>
* @param permission <p>The permission to check</p>
* @return <p>True if the player has the permission</p>
*/
public static boolean hasPermission(Player player, String permission) {
if (Stargate.getStargateConfig().isPermissionDebuggingEnabled()) {
Stargate.debug("hasPerm::Permission(" + player.getName() + ")", permission + " => " +
player.hasPermission(permission));
}
return player.hasPermission(permission);
}
/**
* Check if a player has been given a permission implicitly
*
* <p>This should be run if a player has a parent permission to check for the child permission. It is assumed the
* player has the child permission unless it's explicitly set to false.</p>
*
* @param player <p>The player to check</p>
* @param permission <p>The permission to check</p>
* @return <p>True if the player has the permission implicitly or explicitly</p>
*/
public static boolean hasPermissionImplicit(Player player, String permission) {
if (!player.isPermissionSet(permission)) {
if (Stargate.getStargateConfig().isPermissionDebuggingEnabled()) {
Stargate.debug("hasPermissionImplicit::Permission", permission + " => implicitly true");
}
return true;
}
if (Stargate.getStargateConfig().isPermissionDebuggingEnabled()) {
Stargate.debug("hasPermissionImplicit::Permission", permission + " => " +
player.hasPermission(permission));
}
return player.hasPermission(permission);
}
/**
* Checks whether a player can access the given world
*
* @param player <p>The player trying to access the world</p>
* @param world <p>The world the player is trying to access</p>
* @return <p>False if the player should be allowed to access the world</p>
*/
public static boolean cannotAccessWorld(Player player, String world) {
//The player can access all worlds
if (hasPermission(player, "stargate.world")) {
//Check if the world permission has been explicitly denied
return !hasPermissionImplicit(player, "stargate.world." + world);
}
//The player can access the destination world
return !hasPermission(player, "stargate.world." + world);
}
/**
* Checks whether a player can access the given network
*
* @param player <p>The player to check</p>
* @param network <p>The network to check</p>
* @return <p>True if the player is denied from accessing the network</p>
*/
public static boolean cannotAccessNetwork(Player player, String network) {
//The player can access all networks
if (hasPermission(player, "stargate.network")) {
//Check if the world permission has been explicitly denied
return !hasPermissionImplicit(player, "stargate.network." + network);
}
//Check if the player can access this network
if (hasPermission(player, "stargate.network." + network)) {
return false;
}
//Is able to create personal gates (Assumption is made they can also access them)
String playerName = player.getName();
if (playerName.length() > getMaxNameNetworkLength()) {
playerName = playerName.substring(0, getMaxNameNetworkLength());
}
return !network.equals(playerName) || !hasPermission(player, "stargate.create.personal");
}
/**
* Checks whether a player can access the given bungee server
*
* @param player <p>The player trying to teleport</p>
* @param server <p>The server the player is trying to connect to</p>
* @return <p>True if the player is allowed to access the given server</p>
*/
public static boolean canAccessServer(Player player, String server) {
//The player can access all servers
if (hasPermission(player, "stargate.server")) {
//Check if the server permission has been explicitly denied
return hasPermissionImplicit(player, "stargate.server." + server);
}
//The player can access the destination server
return hasPermission(player, "stargate.server." + server);
}
/**
* Checks whether the given player can teleport the given stretch for free
*
* @param player <p>The player trying to teleport</p>
* @param src <p>The portal the player is entering</p>
* @param dest <p>The portal the player wants to teleport to</p>
* @return <p>True if the player can travel for free</p>
*/
public static boolean isFree(Player player, Portal src, Portal dest) {
//This portal is free
if (src.getOptions().isFree()) {
return true;
}
//Player can use this portal for free
if (hasPermission(player, "stargate.free.use")) {
return true;
}
//Don't charge for free destinations unless specified in the config
return dest != null && Stargate.getEconomyConfig().freeIfFreeDestination() && dest.getOptions().isFree();
}
/**
* Checks whether the player can see this gate (Hidden property check)
*
* <p>This decides if the player can see the gate on the network selection screen</p>
*
* @param player <p>The player to check</p>
* @param portal <p>The portal to check</p>
* @return <p>True if the given player can see the given portal</p>
*/
public static boolean canSeePortal(Player player, Portal portal) {
//The portal is not hidden
if (!portal.getOptions().isHidden()) {
return true;
}
//The player can see all hidden portals
if (hasPermission(player, "stargate.admin.hidden")) {
return true;
}
//The player is the owner of the portal
return portal.isOwner(player);
}
/**
* Checks if the given player is allowed to use the given private portal
*
* @param player <p>The player trying to use the portal</p>
* @param portal <p>The private portal used</p>
* @return <p>True if the player is allowed to use the portal</p>
*/
public static boolean canUsePrivatePortal(Player player, Portal portal) {
//Check if the player is the owner of the gate
if (portal.isOwner(player)) {
return true;
}
//The player is an admin with the ability to use private gates
return hasPermission(player, "stargate.admin.private");
}
/**
* Checks if the given player has access to the given portal option
*
* @param player <p>The player trying to use the option</p>
* @param option <p>The option the player is trying to use</p>
* @return <p>True if the player is allowed to create a portal with the given option</p>
*/
public static boolean canUseOption(Player player, PortalOption option) {
return hasPermission(player, option.getPermissionString());
}
/**
* Checks if the given player is allowed to create gates on the given network
*
* @param player <p>The player trying to create a new gate</p>
* @param network <p>The network the player is trying to create a gate on</p>
* @return <p>True if the player is allowed to create the new gate</p>
*/
public static boolean canCreateNetworkGate(Player player, String network) {
//Check if the player is allowed to create a portal on any network
if (hasPermission(player, "stargate.create.network")) {
//Check if the network has been explicitly denied
return hasPermissionImplicit(player, "stargate.create.network." + network);
}
//Check if the player is allowed to create on this specific network
return hasPermission(player, "stargate.create.network." + network);
}
/**
* Checks whether the given player is allowed to create a personal gate
*
* @param player <p>The player trying to create the new gate</p>
* @return <p>True if the player is allowed</p>
*/
public static boolean canCreatePersonalPortal(Player player) {
return hasPermission(player, "stargate.create.personal");
}
/**
* Checks if the given player can create a portal with the given gate layout
*
* @param player <p>The player trying to create a portal</p>
* @param gate <p>The gate type of the new portal</p>
* @return <p>True if the player is allowed to create a portal with the given gate layout</p>
*/
public static boolean canCreatePortal(Player player, String gate) {
//Check if the player is allowed to create all gates
if (hasPermission(player, "stargate.create.gate")) {
//Check if the gate type has been explicitly denied
return hasPermissionImplicit(player, "stargate.create.gate." + gate);
}
//Check if the player can create the specific gate type
return hasPermission(player, "stargate.create.gate." + gate);
}
/**
* Checks if the given player can destroy the given portal
*
* @param player <p>The player trying to destroy the portal</p>
* @param portal <p>The portal to destroy</p>
* @return <p>True if the player is allowed to destroy the portal</p>
*/
public static boolean canDestroyPortal(Player player, Portal portal) {
String network = portal.getCleanNetwork();
//Use a special check for bungee portals
if (portal.getOptions().isBungee()) {
return hasPermission(player, "stargate.admin.bungee");
}
//Check if the player is allowed to destroy on all networks
if (hasPermission(player, "stargate.destroy.network")) {
//Check if the network has been explicitly denied
return hasPermissionImplicit(player, "stargate.destroy.network." + network);
}
//Check if the player is allowed to destroy on the network
if (hasPermission(player, "stargate.destroy.network." + network)) {
return true;
}
//Check if personal portal and if the player is allowed to destroy it
return portal.isOwner(player) && hasPermission(player, "stargate.destroy.personal");
}
/**
* Decide of the player can teleport through a portal
*
* @param entrancePortal <p>The portal the player is entering from</p>
* @param destination <p>The destination of the portal the player is inside</p>
* @param player <p>The player wanting to teleport</p>
* @param event <p>The move event causing the teleportation</p>
* @return <p>True if the player cannot teleport. False otherwise</p>
*/
public static boolean playerCannotTeleport(Portal entrancePortal, Portal destination, Player player, PlayerMoveEvent event) {
//No portal or not open
if (entrancePortal == null || !entrancePortal.isOpen()) {
return true;
}
//Not open for this player
if (!entrancePortal.getPortalOpener().isOpenFor(player)) {
if (!entrancePortal.getOptions().isSilent()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("denyMsg"));
}
new PlayerTeleporter(entrancePortal, player).teleportPlayer(entrancePortal, event);
return true;
}
//No destination
if (!entrancePortal.getOptions().isBungee() && destination == null) {
return true;
}
//Player cannot access portal
if (PermissionHelper.cannotAccessPortal(player, entrancePortal, destination)) {
if (!entrancePortal.getOptions().isSilent()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("denyMsg"));
}
new PlayerTeleporter(entrancePortal, player).teleportPlayer(entrancePortal, event);
entrancePortal.getPortalOpener().closePortal(false);
return true;
}
//Player cannot pay for teleportation
int cost = EconomyHelper.getUseCost(player, entrancePortal, destination);
if (cost > 0) {
return EconomyHelper.cannotPayTeleportFee(entrancePortal, player, cost);
}
return false;
}
}

View File

@@ -0,0 +1,401 @@
package net.knarcraft.stargate.utility;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.container.BlockChangeRequest;
import net.knarcraft.stargate.container.BlockLocation;
import net.knarcraft.stargate.container.RelativeBlockVector;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalHandler;
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.gate.Gate;
import net.knarcraft.stargate.portal.property.gate.GateHandler;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Directional;
import org.bukkit.block.data.Waterlogged;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
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(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.getWorld().getName();
if (!worldName.equalsIgnoreCase(world.getName())) {
continue;
}
//Save the portal
savePortal(bufferedWriter, portal);
}
bufferedWriter.close();
} catch (Exception e) {
Stargate.logSevere(String.format("Exception while writing stargates to %s: %s", saveFileLocation, e));
}
}
/**
* 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(BufferedWriter bufferedWriter, Portal portal) throws IOException {
StringBuilder builder = new StringBuilder();
BlockLocation 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.getSignLocation().toString()).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(portal.getYaw()).append(':');
builder.append(portal.getTopLeft().toString()).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(Portal portal, 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.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.isSilent()).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(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(World world, 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 e) {
Stargate.logSevere(String.format("Exception while reading stargates from %s: %d", database.getName(),
lineIndex));
e.printStackTrace();
}
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(Scanner scanner, int lineIndex, 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(World world, boolean needsToSaveDatabase) {
//Open any always-on portals. Do this here as it should be more efficient than in the loop.
PortalHandler.verifyAllPortals();
int portalCount = PortalRegistry.getAllPortals().size();
int openCount = PortalHandler.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
for (Portal portal : PortalRegistry.getAllPortals()) {
if (portal.isRegistered()) {
portal.drawSign();
updatePortalButton(portal);
}
}
//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(String[] portalData, World world, int lineIndex) {
//Load min. required portal data
String name = portalData[0];
BlockLocation button = (portalData[2].length() > 0) ? new BlockLocation(world, portalData[2]) : null;
//Load the portal's location
PortalLocation portalLocation = new PortalLocation();
portalLocation.setSignLocation(new BlockLocation(world, portalData[1]));
portalLocation.setYaw(Float.parseFloat(portalData[5]));
portalLocation.setTopLeft(new BlockLocation(world, portalData[6]));
//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
Portal portal = new Portal(portalLocation, button, destination, name, network, gate, owner,
PortalHandler.getPortalOptions(portalData));
//Register the portal, and close it in case it wasn't properly closed when the server stopped
boolean buttonLocationChanged = updateButtonVector(portal);
PortalHandler.registerPortal(portal);
portal.getPortalOpener().closePortal(true);
return buttonLocationChanged;
}
/**
* Updates a portal's button if it does not match the correct material
*
* @param portal <p>The portal update the button of</p>
*/
private static void updatePortalButton(Portal portal) {
BlockLocation buttonLocation = getButtonLocation(portal);
if (portal.getOptions().isAlwaysOn()) {
//Clear button if not already air or water
if (MaterialHelper.isButtonCompatible(buttonLocation.getType())) {
Material newMaterial = decideRemovalMaterial(buttonLocation, portal);
Stargate.addBlockChangeRequest(new BlockChangeRequest(buttonLocation, newMaterial, null));
}
} else {
//Replace button if the material does not match
if (buttonLocation.getType() != portal.getGate().getPortalButton()) {
generatePortalButton(portal, DirectionHelper.getBlockFaceFromYaw(portal.getYaw()));
}
}
}
/**
* Decides the material to use for removing a portal's button/sign
*
* @param location <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>
*/
public static Material decideRemovalMaterial(BlockLocation location, Portal portal) {
//Get the blocks to each side of the location
Location leftLocation = location.getRelativeLocation(-1, 0, 0, portal.getYaw());
Location rightLocation = location.getRelativeLocation(1, 0, 0, portal.getYaw());
//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(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(Portal portal) {
for (RelativeBlockVector control : portal.getGate().getLayout().getControls()) {
BlockLocation controlLocation = portal.getLocation().getTopLeft().getRelativeLocation(control,
portal.getYaw());
BlockLocation buttonLocation = controlLocation.getRelativeLocation(
new RelativeBlockVector(0, 0, 1), portal.getYaw());
if (!buttonLocation.equals(portal.getLocation().getSignLocation())) {
portal.getLocation().setButtonVector(control);
BlockLocation oldButtonLocation = portal.getStructure().getButton();
if (oldButtonLocation != null && !oldButtonLocation.equals(buttonLocation)) {
Stargate.addBlockChangeRequest(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(Portal portal, BlockFace buttonFacing) {
//Go one block outwards to find the button's location rather than the control block's location
BlockLocation button = getButtonLocation(portal);
Directional buttonData = (Directional) Bukkit.createBlockData(portal.getGate().getPortalButton());
buttonData.setFacing(buttonFacing);
button.getBlock().setBlockData(buttonData);
portal.getStructure().setButton(button);
}
/**
* Gets the location of a portal's button
*
* @param portal <p>The portal to find the button for</p>
* @return <p>The location of the portal's button</p>
*/
private static BlockLocation getButtonLocation(Portal portal) {
BlockLocation topLeft = portal.getTopLeft();
RelativeBlockVector buttonVector = portal.getLocation().getButtonVector();
return topLeft.getRelativeLocation(buttonVector.addToVector(RelativeBlockVector.Property.OUT, 1),
portal.getYaw());
}
}

View File

@@ -0,0 +1,247 @@
package net.knarcraft.stargate.utility;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.teleporter.EntityTeleporter;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Creature;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.util.Vector;
import java.util.ArrayList;
import java.util.List;
/**
* A helper class with methods for various teleportation tasks
*
* <p>The teleport helper mainly helps with passengers and leashed creatures</p>
*/
public final class TeleportHelper {
private TeleportHelper() {
}
/**
* Checks whether a player has leashed creatures that block the teleportation
*
* @param player <p>The player trying to teleport</p>
* @return <p>False if the player has leashed any creatures that cannot go through the portal</p>
*/
public static boolean noLeashedCreaturesPreventTeleportation(Player player) {
//Find any nearby leashed entities to teleport with the player
List<Creature> nearbyCreatures = getLeashedCreatures(player);
//Disallow creatures with passengers to prevent smuggling
for (Creature creature : nearbyCreatures) {
if (!creature.getPassengers().isEmpty()) {
return false;
}
}
//TODO: Improve this to account for any players sitting on any of the lead creatures
//If it's enabled, there is no problem
if (Stargate.getGateConfig().handleLeashedCreatures()) {
return true;
} else {
return nearbyCreatures.isEmpty();
}
}
/**
* Gets all creatures leashed by a player within the given range
*
* @param player <p>The player to check</p>
* @return <p>A list of all creatures the player is holding in a leash (lead)</p>
*/
public static List<Creature> getLeashedCreatures(Player player) {
List<Creature> leashedCreatures = new ArrayList<>();
//Find any nearby leashed entities to teleport with the player
List<Entity> nearbyEntities = player.getNearbyEntities(15, 15, 15);
//Teleport all creatures leashed by the player to the portal the player is to exit from
for (Entity entity : nearbyEntities) {
if (entity instanceof Creature creature && creature.isLeashed() && creature.getLeashHolder() == player) {
leashedCreatures.add(creature);
}
}
return leashedCreatures;
}
/**
* Teleports and adds a passenger to an entity
*
* <p>Teleportation of living vehicles is really buggy if you wait between the teleportation and passenger adding,
* but there needs to be a delay between teleporting the vehicle and teleporting and adding the passenger.</p>
*
* @param targetVehicle <p>The entity to add the passenger to</p>
* @param passenger <p>The passenger to teleport and add</p>
* @param exitDirection <p>The direction of any passengers exiting the stargate</p>
* @param newVelocity <p>The new velocity of the teleported passenger</p>
*/
public static void teleportAndAddPassenger(Entity targetVehicle, Entity passenger, Vector exitDirection,
Vector newVelocity) {
Location passengerExit = targetVehicle.getLocation().clone().setDirection(exitDirection);
if (!passenger.teleport(passengerExit)) {
Stargate.debug("TeleportHelper::handleVehiclePassengers", "Failed to teleport passenger" +
passenger);
} else {
Stargate.debug("TeleportHelper::handleVehiclePassengers", "Teleported " + passenger +
" to " + passengerExit);
}
if (!targetVehicle.addPassenger(passenger)) {
Stargate.debug("TeleportHelper::handleVehiclePassengers", "Failed to add passenger" +
passenger);
} else {
Stargate.debug("TeleportHelper::handleVehiclePassengers", "Added passenger " + passenger +
" to " + targetVehicle);
}
Stargate.debug("VehicleTeleporter::teleportVehicle", "Setting velocity " + newVelocity +
" for passenger " + passenger);
passenger.setVelocity(newVelocity);
}
/**
* Ejects, teleports and adds all passengers to the target entity
*
* @param passengers <p>The passengers to handle</p>
* @param entity <p>The entity the passengers should be put into</p
* @param origin <p>The portal the entity teleported from</p>
* @param target <p>The portal the entity is teleporting to</p>
* @param exitRotation <p>The rotation of any passengers exiting the stargate</p>
* @param newVelocity <p>The new velocity of the teleported passengers</p>
*/
public static void handleEntityPassengers(List<Entity> passengers, Entity entity, Portal origin, Portal target,
Vector exitRotation, Vector newVelocity) {
for (Entity passenger : passengers) {
List<Entity> passengerPassengers = passenger.getPassengers();
if (!passengerPassengers.isEmpty()) {
Stargate.debug("Teleporter::handleEntityPassengers", "Found the entities: " +
passengerPassengers + " as passengers of " + entity);
}
if (passenger.eject()) {
//Teleport any passengers of the passenger
handleEntityPassengers(passengerPassengers, passenger, origin, target, exitRotation, newVelocity);
}
Bukkit.getScheduler().scheduleSyncDelayedTask(Stargate.getInstance(), () -> {
if (passenger instanceof Player player) {
//Teleport any creatures leashed by the player in a 15-block range
teleportLeashedCreatures(player, origin, target);
}
teleportAndAddPassenger(entity, passenger, exitRotation, newVelocity);
}, passenger instanceof Player ? Stargate.getGateConfig().waitForPlayerAfterTeleportDelay() : 0);
}
}
/**
* Teleports any creatures leashed by the player
*
* <p>Will return false if the teleportation should be aborted because the player has leashed creatures that
* aren't allowed to be teleported with the player.</p>
*
* @param player <p>The player which is teleported</p>
* @param origin <p>The portal the player is teleporting from</p>
* @param target <p>The portal the player is teleporting to</p>
*/
public static void teleportLeashedCreatures(Player player, Portal origin, Portal target) {
//If this feature is disabled, just return
if (!Stargate.getGateConfig().handleLeashedCreatures()) {
return;
}
BukkitScheduler scheduler = Bukkit.getScheduler();
//Find any nearby leashed entities to teleport with the player
List<Creature> nearbyEntities = TeleportHelper.getLeashedCreatures(player);
//Teleport all creatures leashed by the player to the portal the player is to exit from
for (Creature creature : nearbyEntities) {
creature.setLeashHolder(null);
scheduler.scheduleSyncDelayedTask(Stargate.getInstance(), () -> {
new EntityTeleporter(target, creature).teleportEntity(origin);
scheduler.scheduleSyncDelayedTask(Stargate.getInstance(), () -> creature.setLeashHolder(player),
Stargate.getGateConfig().waitForPlayerAfterTeleportDelay());
}, 2);
}
}
/**
* Checks whether a list of entities or any of their passengers contains any non-players
*
* @param entities <p>The list of entities to check</p>
* @return <p>True if at least one entity is not a player</p>
*/
public static boolean containsNonPlayer(List<Entity> entities) {
for (Entity entity : entities) {
if (!(entity instanceof Player) || containsNonPlayer(entity.getPassengers())) {
return true;
}
}
return false;
}
/**
* Checks whether a list of entities of their passengers contains at least one player
*
* @param entities <p>The list of entities to check</p>
* @return <p>True if at least one player is present among the passengers</p>
*/
public static boolean containsPlayer(List<Entity> entities) {
for (Entity entity : entities) {
if (entity instanceof Player || containsPlayer(entity.getPassengers())) {
return true;
}
}
return false;
}
/**
* Gets all players recursively from a list of entities
*
* @param entities <p>The entities to check for players</p>
* @return <p>The found players</p>
*/
public static List<Player> getPlayers(List<Entity> entities) {
List<Player> players = new ArrayList<>(5);
for (Entity entity : entities) {
if (entity instanceof Player) {
players.add((Player) entity);
}
players.addAll(getPlayers(entity.getPassengers()));
}
return players;
}
/**
* Checks whether the given player is allowed to and can afford to teleport
*
* @param player <p>The player trying to teleport</p>
* @param entrancePortal <p>The portal the player is entering</p>
* @param destinationPortal <p>The portal the player is to exit from</p>
* @return <p>True if the player is allowed to teleport and is able to pay necessary fees</p>
*/
public static boolean playerCanTeleport(Player player, Portal entrancePortal, Portal destinationPortal) {
//Make sure the user can access the portal
if (PermissionHelper.cannotAccessPortal(player, entrancePortal, destinationPortal)) {
if (!entrancePortal.getOptions().isSilent()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("denyMsg"));
}
entrancePortal.getPortalOpener().closePortal(false);
return false;
}
//Check if the player is able to afford the teleport fee
int cost = EconomyHelper.getUseCost(player, entrancePortal, destinationPortal);
boolean canAffordFee = cost <= 0 || Stargate.getEconomyConfig().canAffordFee(player, cost);
if (!canAffordFee) {
if (!entrancePortal.getOptions().isSilent()) {
Stargate.getMessageSender().sendErrorMessage(player, Stargate.getString("ecoInFunds"));
}
return false;
}
return TeleportHelper.noLeashedCreaturesPreventTeleportation(player);
}
}

View File

@@ -0,0 +1,115 @@
package net.knarcraft.stargate.utility;
import net.knarcraft.stargate.Stargate;
import net.knarcraft.stargate.portal.Portal;
import net.knarcraft.stargate.portal.PortalHandler;
import net.knarcraft.stargate.portal.PortalRegistry;
import net.knarcraft.stargate.portal.property.PortalOwner;
import org.bukkit.OfflinePlayer;
import org.bukkit.World;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
/**
* Helps migrate player names to UUID where necessary
*/
public final class UUIDMigrationHelper {
private UUIDMigrationHelper() {
}
private static Map<String, List<Portal>> playerNamesToMigrate;
/**
* Migrates the player's name to a UUID
*
* <p>If any portals are missing a UUID for their owner, and the given player is the owner of those portals, the
* given player's UUID will be used as UUID for the portals' owner.</p>
*
* @param player <p>The player to migrate</p>
*/
public static void migrateUUID(OfflinePlayer player) {
Map<String, List<Portal>> playersToMigrate = getPlayersToMigrate();
String playerName = player.getName();
//Nothing to do
if (!playersToMigrate.containsKey(playerName)) {
return;
}
Stargate.debug("UUIDMigrationHelper::migrateUUID", String.format("Migrating name to UUID for player %s",
playerName));
List<Portal> portalsOwned = playersToMigrate.get(playerName);
if (portalsOwned == null) {
return;
}
migratePortalsToUUID(portalsOwned, player.getUniqueId());
//Remove the player to prevent the migration to happen every time the player joins
playersToMigrate.remove(playerName);
}
/**
* Migrates a list of portals to use UUID instead of only player name
*
* @param portals <p>The portals to migrate</p>
* @param uniqueId <p>The unique ID of the portals' owner</p>
*/
private static void migratePortalsToUUID(List<Portal> portals, UUID uniqueId) {
Set<World> worldsToSave = new HashSet<>();
//Get the real portal from the copy and set UUID
for (Portal portalCopy : portals) {
Portal portal = PortalHandler.getByName(portalCopy.getCleanName(), portalCopy.getCleanNetwork());
if (portal != null) {
portal.getOwner().setUUID(uniqueId);
worldsToSave.add(portal.getWorld());
}
}
//Need to make sure the changes are saved
for (World world : worldsToSave) {
PortalFileHelper.saveAllPortals(world);
}
}
/**
* Gets all player names which need to be migrated to UUIDs
*
* @return <p>The player names to migrate</p>
*/
private static Map<String, List<Portal>> getPlayersToMigrate() {
//Make sure to only go through portals once
if (playerNamesToMigrate != null) {
return playerNamesToMigrate;
}
playerNamesToMigrate = new HashMap<>();
for (Portal portal : PortalRegistry.getAllPortals()) {
PortalOwner owner = portal.getOwner();
String ownerName = owner.getName();
//If a UUID is missing, add the portal to the list owned by the player
if (owner.getUUID() == null) {
List<Portal> portalList = playerNamesToMigrate.get(ownerName);
if (portalList == null) {
List<Portal> newList = new ArrayList<>();
newList.add(portal);
playerNamesToMigrate.put(ownerName, newList);
} else {
portalList.add(portal);
}
}
}
return playerNamesToMigrate;
}
}

View File

@@ -0,0 +1,49 @@
lang=language
defaultNetwork=defaultGateNetwork
use-mysql=
ignoreEntrance=
portal-save-location=folders.portalFolder
portal-folder=folders.portalFolder
gate-folder=folders.gateFolder
default-gate-network=gates.defaultGateNetwork
destroyexplosion=gates.integrity.destroyedByExplosion
maxgates=gates.maxGatesEachNetwork
destMemory=gates.cosmetic.rememberDestination
ignoreEntrance=
gates.integrity.ignoreEntrance=
handleVehicles=gates.functionality.handleVehicles
sortLists=gates.cosmetic.sortNetworkDestinations
protectEntrance=gates.integrity.protectEntrance
enableBungee=gates.functionality.enableBungee
verifyPortals=gates.integrity.verifyPortals
signColor=gates.cosmetic.signColor
gates.cosmetic.signColor=gates.cosmetic.mainSignColor
debug=debugging.debug
permdebug=debugging.permissionDebug
useiconomy=economy.useEconomy
useeconomy=economy.useEconomy
cost-to-use=economy.useCost
cost-to-create=economy.createCost
createcost=economy.createCost
destroycost=economy.destroyCost
usecost=economy.useCost
toowner=economy.toOwner
cost-destination=economy.chargeFreeDestination
chargefreedestination=economy.chargeFreeDestination
freegatesgreen=economy.freeGatesGreen
CheckUpdates=
economy.freeGatesGreen=economy.freeGatesColored
teleportMessage=
registerMessage=
destroyzMessage=
noownersMessage=
unselectMessage=
collisinMessage=
cantAffordToUse=
cantAffordToNew=
portal-open=
portal-closed=
cost-type=
cost-to-activate=
taxaccount=taxAccount
usevault=

View File

@@ -0,0 +1,107 @@
# stargate Configuration File
# Main stargate config
# language - The language file to load for messages (de,en,es,fr,hu,it,ja,nb-no,nl,nn-no,pt-br,ru,zh_cn)
language: en
# adminUpdateAlert - Whether to alert admins about new plugin updates
adminUpdateAlert: true
folders:
# portalFolder - The folder for storing portals
portalFolder: plugins/Stargate/portals/
# gateFolder - The folder for storing gate layouts
gateFolder: plugins/Stargate/gates/
gates:
# maxGatesEachNetwork - The maximum number of gates allowed on a network - 0 for unlimited
maxGatesEachNetwork: 0
# defaultGateNetwork - The default gate network
defaultGateNetwork: central
# exitVelocity - The velocity to give players exiting stargates, relative to the entry velocity
exitVelocity: 0.1
cosmetic:
# rememberDestination - Whether to remember the cursor location between uses
rememberDestination: false
# sortNetworkDestinations - Whether to sort network lists alphabetically
sortNetworkDestinations: false
# mainSignColor - The color used for drawing signs (Default: BLACK).
mainSignColor: BLACK
# highlightSignColor - The color used for sign markings (Default: WHITE)
highlightSignColor: WHITE
perSignColors:
- 'ACACIA:default,default'
- 'BIRCH:default,default'
- 'CRIMSON:inverted,inverted'
- 'DARK_OAK:inverted,inverted'
- 'JUNGLE:default,default'
- 'OAK:default,default'
- 'SPRUCE:inverted,inverted'
- 'WARPED:inverted,inverted'
integrity:
# destroyedByExplosion - Whether to destroy gates with explosions (Creeper, TNT, etc.)
destroyedByExplosion: false
# verifyPortals - Whether all the non-sign blocks are checked to match the gate layout when a stargate is loaded.
verifyPortals: false
# protectEntrance - Whether to protect gate entrance material (More resource intensive. Only enable if using
# destroyable open/closed material)
protectEntrance: false
functionality:
enableBungee: false
# handleVehicles - Whether to allow vehicles through gates. This overrides other vehicle settings
handleVehicles: true
# handleEmptyVehicles - Whether to allow empty vehicles through gates (chest/hopper/tnt/furnace minecarts included)
handleEmptyVehicles: true
# handleCreatureTransportation - Whether to allow players to transport creatures by sending vehicles (minecarts,
# boats) through gates
handleCreatureTransportation: true
# handleNonPlayerVehicles - Whether to allow vehicles with a passenger which is not a player through gates.
# handleCreatureTransportation must be enabled
handleNonPlayerVehicles: true
# handleLeashedCreatures - Whether to allow creatures lead by a player to teleport with the player
handleLeashedCreatures: true
# enableCraftBookRemoveOnEjectFix - Whether to enable a fix that causes loss of NBT data, but allows vehicle
# teleportation to work when CraftBook's remove minecart/boat on eject setting is enabled
enableCraftBookRemoveOnEjectFix: false
# ######################## #
# stargate economy options #
# ######################## #
economy:
# useEconomy - Whether to use an economy plugin
useEconomy: false
# createCost - The cost to create a gate
createCost: 0
# destroyCost - The cost to destroy a gate
destroyCost: 0
# useCost - The cost to use a gate
useCost: 0
# toOwner - Whether the charge for using a gate goes to the gate's owner
toOwner: false
# chargeFreeDestination - Whether a gate whose destination is a free gate is still charged
chargeFreeDestination: true
# freeGatesColored - Whether a free gate in the destination list is marked with a color
freeGatesColored: false
# freeGatesColor - The color to use for marking free gates
freeGatesColor: DARK_GREEN
# ############# #
# Debug options #
# ############# #
debugging:
# debug - Debug -- Only enable if you have issues, massive console output
debug: false
# permissionDebug - This will output any and all Permissions checks to console, used for permissions debugging
# (Requires debug: true)
permissionDebug: false
advanced:
# waitForPlayerAfterTeleportDelay - The amount of ticks to wait before adding a player as passenger of a vehicle.
# On slow servers, a value of 6 is required to avoid client glitches after teleporting on a vehicle.
waitForPlayerAfterTeleportDelay: 6
# ############## #
# Dynmap options #
# ############## #
dynmap:
# enableDynmap - Whether to display Stargates in Dynmap's map
enableDynmap: true
# dynmapIconsHiddenByDefault - Whether to hide the set of Stargate icons by default, requiring users to
# manually enable them with a checkbox.
dynmapIconsHiddenByDefault: true

View File

@@ -0,0 +1,12 @@
portal-open=END_GATEWAY
portal-closed=AIR
button=BIRCH_BUTTON
toowner=false
X=END_STONE_BRICKS
-=END_STONE_BRICKS
XX
X..X
-..-
X*.X
XX

View File

@@ -0,0 +1,24 @@
#This is the default gate type. You can copy this and make as many .gate files as you need.
#The portal-open block can be most blocks which do not fill the entire block or otherwise prevent the player from
#entering the portal, but NETHER_PORTAL, AIR, WATER, LAVA, KELP_PLANT, OAK_FENCE, IRON_BARS, CHAIN, BAMBOO, SUGAR_CANE,
#COBWEB and VINE gives an impression of which blocks will work.
portal-open=NETHER_PORTAL
#The portal-closed block can be any of the blocks used for portal-open, but also any solid, full-size block such as DIRT.
portal-closed=AIR
#The button can be the following: A chest (CHEST), any type of button (STONE_BUTTON, OAK_BUTTON), any type of shulker
#box (LIME_SHULKER_BOX), or any wall coral (DEAD_TUBE_CORAL_WALL_FAN, TUBE_CORAL_WALL_FAN, DEAD_BRAIN_CORAL_WALL_FAN,
#BRAIN_CORAL_WALL_FAN, etc.)
button=STONE_BUTTON
#Whether payment for entry should go to this gate's owner
toowner=false
#The material to use for the normal frame
X=OBSIDIAN
#The material to use for the sign and button blocks of the frame
-=OBSIDIAN
#The description of the required portal blocks. X = Frame block. - = Sign/button position. . = Empty blocks. * = Exit
XX
X..X
-..-
X*.X
XX

View File

@@ -0,0 +1,13 @@
portal-open=NETHER_PORTAL
portal-closed=AIR
button=OAK_BUTTON
toowner=false
X=OBSIDIAN
-=GLOWSTONE
A=GLOWSTONE
XAX
X...X
-...-
X.*.X
XAX

View File

@@ -0,0 +1,12 @@
portal-open=KELP_PLANT
portal-closed=WATER
button=BRAIN_CORAL_WALL_FAN
toowner=false
X=SEA_LANTERN
-=SEA_LANTERN
XX
X..X
-..-
X*.X
XX

View File

@@ -1,21 +1,21 @@
author=EduardBaer
prefix=[Stargate]
teleportMsg=Du wurdest Teleportiert.
destroyMsg=Gate zerstört
invalidMsg=Ungültiges Ziel
destroyMsg=Gate zerstört
invalidMsg=Ungültiges Ziel
blockMsg=Ziel blockiert
destEmpty=Zielliste leer
denyMsg=Zugriff verweigert
ecoDeduct=%cost% abgezogen
ecoRefund=%cost% zurückerstattet
ecoRefund=%cost% zurückerstattet
ecoObtain=%cost% von Stargate %portal% erhalten
ecoInFunds=Das kannst du dir nicht leisten.
createMsg=Gate erstellt.
createNetDeny=Du hast keinen Zugriff auf dieses Netzwerk.
createGateDeny=Du hast keinen Zugriff auf dieses Gate-Layout.
createPersonal=Gate im persönlichen Netzwerk erstellt.
createPersonal=Gate im persönlichen Netzwerk erstellt.
createNameLength=Name zu kurz oder zu lang.
createExists=Ein Gate mit diesem Name existiert bereits.
createFull=Dieses Netzwerk ist voll.

View File

@@ -5,11 +5,15 @@ invalidMsg=Invalid Destination
blockMsg=Destination Blocked
destEmpty=Destination List Empty
denyMsg=Access Denied
reloaded=Stargate Reloaded
ecoDeduct=Deducted %cost%
ecoRefund=Refunded %cost%
ecoObtain=Obtained %cost% from Stargate %portal%
ecoInFunds=Insufficient Funds
ecoLoadError=Vault was loaded, but no economy plugin could be hooked into
vaultLoadError=Economy is enabled but Vault could not be loaded. Economy disabled
vaultLoaded=Vault v%version% found
createMsg=Gate Created
createNetDeny=You do not have access to that network
@@ -25,8 +29,15 @@ signRightClick=Right click
signToUse=to use gate
signRandom=Random
signDisconnected=Disconnected
signInvalidGate=Invalid gate
bungeeDisabled=BungeeCord support is disabled.
bungeeDeny=You do not have permission to create BungeeCord gates.
bungeeEmpty=BungeeCord gates require both a destination and network.
bungeeSign=Teleport to
portalInfoTitle=[STARGATE INFO]
portalInfoName=Name: %name%
portalInfoDestination=Destination: %destination%
portalInfoNetwork=Network: %network%
portalInfoServer=Server: %server%

View File

@@ -1,10 +1,10 @@
author=Manuestaire
prefix=[Stargate]
teleportMsg=Teletransportado
destroyMsg=Portal Destruído
destroyMsg=Portal Destruído
invalidMsg=Elige Destino
blockMsg=Destino Bloqueado
destEmpty=La lista de destinos está vacía
destEmpty=La lista de destinos está vacía
denyMsg=Acceso denegado
ecoDeduct=Pagaste %cost%
@@ -14,11 +14,11 @@ ecoInFunds=No tienes suficiente dinero
createMsg=Portal creado
createNetDeny=No tienes acceso a esta red
createGateDeny=No tienes acceso a este diseño de portal
createGateDeny=No tienes acceso a este diseño de portal
createPersonal=Creando el portal en una red personal
createNameLength=Nombre demasiado largo o demasiado corto
createExists=Ya existe una puerta con este nombre
createFull=Esta red está llena
createFull=Esta red está llena
createWorldDeny=No tienes permisos para acceder a ese mundo
createConflict=El portal entra en conflicto con un portal ya existente

View File

@@ -1,4 +1,4 @@
author=Dauphin14
author=Dauphin14
prefix=[Stargate]
teleportMsg=Téléportation Réussie.
destroyMsg=Portail detruit.

View File

@@ -0,0 +1,44 @@
author=furplag
prefix=[Stargate]
teleportMsg=テレポート
destroyMsg=ゲートが破壊されました
invalidMsg=無効な行き先
blockMsg=ブロックされた行き先
destEmpty=行き先リストが空です
denyMsg=アクセスが拒否されました
reloaded= Stargate をリロードしました
ecoDeduct=cost の値引き
ecoRefund=cost の返金
ecoObtain= Stargate portal から cost を得ました
ecoInFunds=資金の不足
ecoLoadError= Vault が読み込まれましたが、Economy プラグインをフックできませんでした
vaultLoadError=Economy は有効になっていますが、Vault をロードできないため Economy は無効化されました
vaultLoaded= Vault vversion が見つかりました
createMsg=ゲートが作成されました
createNetDeny=対象のネットワークにアクセスできません
createGateDeny=対象のゲートレイアウトにアクセスできません
createPersonal=パーソナルネットワーク上にゲートを作成する
createNameLength=ゲート名が短すぎるか長すぎます
createExists=すでに存在するゲート名です
createFull=対象のネットワークはいっぱいです
createWorldDeny=あなたはその世界にアクセスできません
createConflict=ゲートが既存のゲートと競合しています
signRightClick=右クリック
signToUse=ゲートを使用する
signRandom=ランダム
signDisconnected=切断
signInvalidGate=無効なゲート
bungeeDisabled=BungeeCord サポートは無効になっています
bungeeDeny=BungeeCord ゲートを作成する権限がありません
bungeeEmpty=BungeeCord ゲートには、行き先とネットワークの両方が必要です
bungeeSign=テレポート先:
portalInfoTitle=[STARGATE INFO]
portalInfoName=ゲート名: name
portalInfoDestination=行き先: destination
portalInfoNetwork=ネットワーク: network
portalInfoServer=サーバー: server

View File

@@ -0,0 +1,43 @@
author=EpicKnarvik97
prefix=[Stjerneport]
teleportMsg=Teleporterte
destroyMsg=Port Ødelagt
invalidMsg=Ugyldig Destinasjon
blockMsg=Destinasjon Blokkert
destEmpty=Destinasjonslisten Er Tom
denyMsg=Tilgang Avslått
reloaded=Stjerneport Ble Lastet Inn På Nytt
ecoDeduct=Fratrekk %cost%
ecoRefund=Refundert %cost%
ecoObtain=Fikk %cost% fra Stjerneport %portal%
ecoInFunds=Manglende Midler
ecoLoadError=Vault ble lastet inn men ingen brukbar økonomi-utvidelse ble funnet
vaultLoadError=Økonomi er skrudd på, men Vault kunne ikke lastes inn. Økonomi er skrudd av
vaultLoaded=Vault v%version% funnet
createMsg=Port opprettet
createNetDeny=Du har ikke tilgang til det nettverket
createGateDeny=Du har ikke tilgang til den portutformingen
createPersonal=Oppretter port på personlig nettverk
createNameLength=Navnet er for kort eller for langt.
createExists=En port ved det navnet eksisterer allerede
createFull=Dette nettverket er fullt
createWorldDeny=Du har ikke tilgang til den verdenen
createConflict=Port er i konflikt med en eksisterende port
signRightClick=Høyreklikk
signToUse=for å bruke port
signRandom=Tilfeldig
signDisconnected=Koblet fra
bungeeDisabled=BungeeCord støtte er slått av.
bungeeDeny=Du har ikke tillatelse til å opprette BungeeCord porter.
bungeeEmpty=BungeeCord porter behøver bade en destinasjon og et nettverk.
bungeeSign=Teleporter til
portalInfoTitle=[STJERNEPORT INFO]
portalInfoName=Navn: %name%
portalInfoDestination=Destinasjon: %destination%
portalInfoNetwork=Nettverk: %network%
portalInfoServer=Server: %server%

View File

@@ -16,7 +16,7 @@ createMsg=Gate gemaakt
createNetDeny=Je hebt geen toegang tot dat netwerk.
createGateDeny=Je mag die Gate-Layout niet gebruiken
createPersonal=Gate op persoonlijk netwerk gemaakt.
createNameLength=Naam te lang of te kort.
createNameLength=Naam te chosenLanguage of te kort.
createExists=Er bestaat al een gate met die naam
createFull=Dit netwerk is vol.
createWorldDeny=Je mag niet in die wereld komen.

View File

@@ -0,0 +1,43 @@
author=EpicKnarvik97
prefix=[Stjerneport]
teleportMsg=Teleporterte
destroyMsg=Port Øydelagd
invalidMsg=Ugyldig Destinasjon
blockMsg=Destinasjon Blokkert
destEmpty=Destinasjonslista Er Tom
denyMsg=Tilgang Avslått
reloaded=Stjerneport Vart Lasta Inn På Nytt
ecoDeduct=Fråtrekk %cost%
ecoRefund=Refundert %cost%
ecoObtain=Mottok %cost% frå Stjerneport %portal%
ecoInFunds=Manglande Midlar
ecoLoadError=Vault vart lasta inn men inga brukbar økonomi-utviding vart funnen
vaultLoadError=Økonomi er skrudd på, men Vault kunne ikkje lastas inn. Økonomi er skrudd av
vaultLoaded=Vault v%version% funnen
createMsg=Port oppretta
createNetDeny=Du har ikkje tilgang til det nettverket
createGateDeny=Du har ikkje tilgang til den portutforminga
createPersonal=Opprettar port på personleg nettverk
createNameLength=Namnet er for kort eller for langt.
createExists=Ein port med det namnet eksisterar allereie
createFull=Dette nettverket er fullt
createWorldDeny=Du har ikkje tilgang til den verda
createConflict=Port er i konflikt med ein eksisterande port
signRightClick=Høgreklikk
signToUse=for å bruke port
signRandom=Tilfeldig
signDisconnected=Kopla frå
bungeeDisabled=BungeeCord støtte er slått av.
bungeeDeny=Du har ikkje løyve til å opprette BungeeCord portar.
bungeeEmpty=BungeeCord portar treng bade ein destinasjon og eit nettverk.
bungeeSign=Teleporter til
portalInfoTitle=[STJERNEPORT INFO]
portalInfoName=Namn: %name%
portalInfoDestination=Destinasjon: %destination%
portalInfoNetwork=Nettverk: %network%
portalInfoServer=Server: %server%

View File

@@ -17,7 +17,7 @@ createNetDeny=Voce nao tem acesso a essa rede.
createGateDeny=Voce nao tem acesso a esse portal layout.
createPersonal=Criando portal em rede pessoal.
createNameLength=Nome muito curto ou muito grande.
createExists=Já existe um portal com esse nome.
createExists=Já existe um portal com esse nome.
createFull=Esta rede esta cheia.
createWorldDeny=Voce nao tem acesso a esse mundo.
createConflict=Portal em conflito com um portal ja existente.

Some files were not shown because too many files have changed in this diff Show More