From 3470810acf1350bad17b0b9d3d2a1fda41cb8533 Mon Sep 17 00:00:00 2001 From: Okx Date: Sun, 16 Jun 2019 10:48:27 +0100 Subject: [PATCH 01/78] Initial commit --- plugins/railswitch-paper/.gitignore | 8 ++++++++ plugins/railswitch-paper/LICENSE.md | 22 ++++++++++++++++++++++ plugins/railswitch-paper/README.md | 1 + 3 files changed, 31 insertions(+) create mode 100644 plugins/railswitch-paper/.gitignore create mode 100644 plugins/railswitch-paper/LICENSE.md create mode 100644 plugins/railswitch-paper/README.md diff --git a/plugins/railswitch-paper/.gitignore b/plugins/railswitch-paper/.gitignore new file mode 100644 index 000000000..302a5f2ff --- /dev/null +++ b/plugins/railswitch-paper/.gitignore @@ -0,0 +1,8 @@ +.gradle +build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar diff --git a/plugins/railswitch-paper/LICENSE.md b/plugins/railswitch-paper/LICENSE.md new file mode 100644 index 000000000..48b0ec4e2 --- /dev/null +++ b/plugins/railswitch-paper/LICENSE.md @@ -0,0 +1,22 @@ + +The MIT License (MIT) + +Copyright (c) 2019 okx-code + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/railswitch-paper/README.md b/plugins/railswitch-paper/README.md new file mode 100644 index 000000000..945a40824 --- /dev/null +++ b/plugins/railswitch-paper/README.md @@ -0,0 +1 @@ +RailSwitch From db3314a6e6ce6a9dcbf0d59642f66f9adb448930 Mon Sep 17 00:00:00 2001 From: Okx Date: Sun, 16 Jun 2019 10:49:32 +0100 Subject: [PATCH 02/78] add code --- plugins/railswitch-paper/.gitignore | 1 + plugins/railswitch-paper/build.gradle | 26 +++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 56177 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + plugins/railswitch-paper/gradlew | 172 +++++++++++++++++ plugins/railswitch-paper/gradlew.bat | 84 +++++++++ plugins/railswitch-paper/settings.gradle | 2 + .../java/sh/okx/railswitch/RailSwitch.java | 80 ++++++++ .../okx/railswitch/SetDestinationCommand.java | 33 ++++ .../database/RailSwitchDatabase.java | 178 ++++++++++++++++++ .../listener/DectorRailActivateListener.java | 121 ++++++++++++ .../listener/DetectorRailUseListener.java | 99 ++++++++++ .../src/main/resources/config.yml | 9 + .../src/main/resources/plugin.yml | 12 ++ 14 files changed, 823 insertions(+) create mode 100644 plugins/railswitch-paper/build.gradle create mode 100644 plugins/railswitch-paper/gradle/wrapper/gradle-wrapper.jar create mode 100644 plugins/railswitch-paper/gradle/wrapper/gradle-wrapper.properties create mode 100644 plugins/railswitch-paper/gradlew create mode 100644 plugins/railswitch-paper/gradlew.bat create mode 100644 plugins/railswitch-paper/settings.gradle create mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java create mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java create mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java create mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DectorRailActivateListener.java create mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailUseListener.java create mode 100644 plugins/railswitch-paper/src/main/resources/config.yml create mode 100644 plugins/railswitch-paper/src/main/resources/plugin.yml diff --git a/plugins/railswitch-paper/.gitignore b/plugins/railswitch-paper/.gitignore index 302a5f2ff..0f56cbe93 100644 --- a/plugins/railswitch-paper/.gitignore +++ b/plugins/railswitch-paper/.gitignore @@ -6,3 +6,4 @@ gradle-app.setting # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) !gradle-wrapper.jar +\.idea \ No newline at end of file diff --git a/plugins/railswitch-paper/build.gradle b/plugins/railswitch-paper/build.gradle new file mode 100644 index 000000000..3e5af3540 --- /dev/null +++ b/plugins/railswitch-paper/build.gradle @@ -0,0 +1,26 @@ +plugins { + id 'java' +} + +group 'sh.okx' +version '1.0-SNAPSHOT' + +sourceCompatibility = 1.8 + +repositories { + mavenCentral() + maven { + url 'https://hub.spigotmc.org/nexus/content/groups/public/' + } + maven { + url 'http://build.devotedmc.com/plugin/repository/everything/' + } +} + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.12' + compile 'vg.civcraft.mc.citadel:Citadel:3.9.0' + compile 'vg.civcraft.mc.civmodcore:CivModCore:1.6.1' + compile 'vg.civcraft.mc.namelayer:NameLayer:2.11.0' + compile 'org.spigotmc:spigot-api:1.12-R0.1-SNAPSHOT' +} diff --git a/plugins/railswitch-paper/gradle/wrapper/gradle-wrapper.jar b/plugins/railswitch-paper/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..94336fcae912db8a11d55634156fa011f4686124 GIT binary patch literal 56177 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 i;x|p)^~-yfPOPa3(|vBayXKz \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/plugins/railswitch-paper/gradlew.bat b/plugins/railswitch-paper/gradlew.bat new file mode 100644 index 000000000..f9553162f --- /dev/null +++ b/plugins/railswitch-paper/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/plugins/railswitch-paper/settings.gradle b/plugins/railswitch-paper/settings.gradle new file mode 100644 index 000000000..f33bf5dcf --- /dev/null +++ b/plugins/railswitch-paper/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'RailSwitch' + diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java new file mode 100644 index 000000000..46bd7dd98 --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java @@ -0,0 +1,80 @@ +package sh.okx.railswitch; + +import com.google.common.base.CharMatcher; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.plugin.PluginManager; +import sh.okx.railswitch.database.RailSwitchDatabase; +import sh.okx.railswitch.listener.DectorRailActivateListener; +import sh.okx.railswitch.listener.DetectorRailUseListener; +import vg.civcraft.mc.civmodcore.ACivMod; +import vg.civcraft.mc.namelayer.GroupManager; +import vg.civcraft.mc.namelayer.permission.PermissionType; + +import java.util.Arrays; +import java.util.List; + +public class RailSwitch extends ACivMod { + private boolean timings; + private RailSwitchDatabase database; + + @Override + public void onEnable() { + super.onEnable(); + saveDefaultConfig(); + loadDatabase(); + + if (getConfig().getBoolean("timings")) { + timings = true; + } + + PluginManager pm = getServer().getPluginManager(); + if (pm.isPluginEnabled("NameLayer")) { + List modAndAbove = Arrays.asList( + GroupManager.PlayerType.MODS, + GroupManager.PlayerType.ADMINS, + GroupManager.PlayerType.OWNER); + PermissionType.registerPermission("CREATE_RAIL_SWITCH", modAndAbove, "Create a rail switch by right-clicking a detector rail"); + } + + pm.registerEvents(new DectorRailActivateListener(this), this); + //pm.registerEvents(new DetectorRailUseListener(this), this); + getCommand("setdestination").setExecutor(new SetDestinationCommand(this)); + } + + public RailSwitchDatabase getDatabase() { + return database; + } + + private void loadDatabase() { + ConfigurationSection config = getConfig(); + + String username = config.getString("mysql.username"); + String host = config.getString("mysql.host"); + String password = config.getString("mysql.password"); + String database = config.getString("mysql.database"); + String prefix = config.getString("mysql.prefix"); + int port = config.getInt("mysql.port"); + + this.database = new RailSwitchDatabase(host, port, database, username, password, prefix, getLogger()); + } + + /** + * make sure the message doesn't have any weirdness + */ + public boolean isValidDestination(String message) { + return message.length() <= 40 + && CharMatcher.inRange('0', '9') + .or(CharMatcher.inRange('a', 'z')) + .or(CharMatcher.inRange('A', 'Z')) + .or(CharMatcher.anyOf("!\"#$%&'()*+,-./;:<=>?@[]\\^_`{|}~")).matchesAllOf(message); + } + + public boolean isTimings() { + return timings; + } + + @Override + protected String getPluginName() { + return "RailSwitch"; + } +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java new file mode 100644 index 000000000..95556fdef --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java @@ -0,0 +1,33 @@ +package sh.okx.railswitch; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class SetDestinationCommand implements CommandExecutor { + private final RailSwitch plugin; + + public SetDestinationCommand(RailSwitch plugin) { + this.plugin = plugin; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player) || args.length < 1) { + return false; + } + + Player player = (Player) sender; + String dest = args[0]; + if (!plugin.isValidDestination(dest)) { + player.sendMessage(ChatColor.RED + "Destinations can not be more than 40 characters and may only use alphanumerical characters and ASCII symbols."); + return true; + } + + plugin.getDatabase().setPlayerDestination(player, dest); + player.sendMessage(ChatColor.GREEN + "Set your rail destination to: " + dest); + return true; + } +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java new file mode 100644 index 000000000..31b5d32cd --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java @@ -0,0 +1,178 @@ +package sh.okx.railswitch.database; + +import org.bukkit.entity.Player; +import vg.civcraft.mc.civmodcore.dao.ConnectionPool; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class RailSwitchDatabase { + //private String switchTable; + private String destTable; + + private ConnectionPool pool; + private Logger log; + + // takes a uuid and location and returns a row if and only if that uuid + // has their destination set to the same destination as that location + //private String checkDestination; + //private String getDestination; + //private String setSwitchDestination; + private String setPlayerDestination; + //private String removeSwitchDestination; + private String getPlayerDestination; + + public RailSwitchDatabase(String host, int port, String db, String user, String password, String prefix, Logger logger) { + log = logger; + pool = new ConnectionPool(logger, user, password, host, port, db, 10, 1000, 600000, 7200000); + + //switchTable = prefix + "switch"; + destTable = prefix + "dest"; + createTable(); + //loadSwitches(); + loadStatements(); + } + + private void createTable() { + try (Connection connection = pool.getConnection()) { + /*connection.createStatement().executeUpdate( + "CREATE TABLE IF NOT EXISTS `" + switchTable + "` (" + + "`world` varchar(36) NOT NULL," + + "`x` int(10) NOT NULL," + + "`y` int(10) NOT NULL," + + "`z` int(10) NOT NULL," + + "`dest` varchar(40) NOT NULL," + + "PRIMARY KEY (`world`, `x`, `y`, `z`))");*/ + connection.createStatement().executeUpdate( + "CREATE TABLE IF NOT EXISTS `" + destTable + "` (" + + "`uuid` VARCHAR(36) NOT NULL," + + "`dest` VARCHAR(40) NOT NULL," + + "PRIMARY KEY (`uuid`))"); + } catch (SQLException e) { + log.log(Level.SEVERE, "Could not create tables", e); + } + } + + /*private void loadSwitches() { + ResultSet resultSet = getResultSet("SELECT * FROM `" + switchTable + "`"); + try { + while (resultSet.next()) { + String world = resultSet.getString("world"); + int x = resultSet.getInt("x"); + int y = resultSet.getInt("y"); + int z = resultSet.getInt("z"); + String dest = resultSet.getString("dest"); + switches.put(new Location(Bukkit.getWorld(world), x, y, z), dest); + } + } catch (SQLException e) { + logger.log(Level.SEVERE, "Could not get result set from SQL statement!", e); + } + }*/ + + private void loadStatements() { + /*checkDestination = String.format( + "SELECT `%1$s`.`uuid` FROM `%2$s` JOIN `%1$s` ON `%2$s`.`dest`=`%1$s`.`dest` WHERE `x`=? AND `y`=? AND `z`=? AND `world`=? AND `uuid`=? LIMIT 1", + destTable, switchTable);*/ + /*getDestination = String.format( + "SELECT `dest` FROM `%1$s` WHERE `x`=? AND `y`=? AND `z`=? AND `world`=? LIMIT 1", + switchTable); + setSwitchDestination = String.format( + "REPLACE INTO `%1$s` VALUES (?, ?, ?, ?, ?)", + switchTable);*/ + setPlayerDestination = String.format( + "REPLACE INTO `%1$s` VALUES (?, ?)", + destTable); + /*removeSwitchDestination = String.format( + "DELETE FROM `%s` WHERE `world`=? AND `x`=? AND `y`=? AND `z`=?", + switchTable);*/ + getPlayerDestination = String.format( + "SELECT `dest` FROM `%1$s` WHERE `uuid`=?", + destTable); + } + + /*public boolean isActivateRail(Player player, Location location) { + try (Connection connection = pool.getConnection()) { + PreparedStatement statement = connection.prepareStatement(checkDestination); + statement.setInt(1, location.getBlockX()); + statement.setInt(2, location.getBlockY()); + statement.setInt(3, location.getBlockZ()); + statement.setString(4, location.getWorld().getUID().toString()); + statement.setString(5, player.getUniqueId().toString()); + + ResultSet resultSet = statement.executeQuery(); + return resultSet.next(); + } catch (SQLException e) { + log.log(Level.SEVERE, "Could not check activate rail", e); + return false; + } + } + + public String getDestination(Location location) { + try (Connection connection = pool.getConnection()) { + PreparedStatement statement = connection.prepareStatement(getDestination); + statement.setInt(1, location.getBlockX()); + statement.setInt(2, location.getBlockY()); + statement.setInt(3, location.getBlockZ()); + statement.setString(4, location.getWorld().getUID().toString()); + + ResultSet resultSet = statement.executeQuery(); + if (!resultSet.next()) { + return null; + } + return resultSet.getString("dest"); + } catch (SQLException e) { + log.log(Level.SEVERE, "Could not get destination", e); + return null; + } + } + + public void setDestination(Location location, String destination) { + try (Connection connection = pool.getConnection()) { + boolean none = destination.equalsIgnoreCase("none"); + PreparedStatement statement = connection.prepareStatement(none ? removeSwitchDestination : setSwitchDestination); + statement.setString(1, location.getWorld().getUID().toString()); + statement.setInt(2, location.getBlockX()); + statement.setInt(3, location.getBlockY()); + statement.setInt(4, location.getBlockZ()); + if (!none) { + statement.setString(5, destination); + } + + statement.executeUpdate(); + } catch (SQLException e) { + log.log(Level.SEVERE, "Could not set destination", e); + } + }*/ + + public void setPlayerDestination(Player player, String destination) { + try (Connection connection = pool.getConnection()) { + PreparedStatement statement = connection.prepareStatement(setPlayerDestination); + statement.setString(1, player.getUniqueId().toString()); + statement.setString(2, destination); + + statement.executeUpdate(); + } catch (SQLException e) { + log.log(Level.SEVERE, "Could not set destination", e); + } + } + + public String getPlayerDestination(Player player) { + try (Connection connection = pool.getConnection()) { + PreparedStatement statement = connection.prepareStatement(getPlayerDestination); + statement.setString(1, player.getUniqueId().toString()); + + ResultSet resultSet = statement.executeQuery(); + if (!resultSet.next()) { + return null; + } + return resultSet.getString("dest"); + } catch (SQLException e) { + log.log(Level.SEVERE, "Could not get destination", e); + return null; + } + } +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DectorRailActivateListener.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DectorRailActivateListener.java new file mode 100644 index 000000000..af2658577 --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DectorRailActivateListener.java @@ -0,0 +1,121 @@ +package sh.okx.railswitch.listener; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.Sign; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Minecart; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockRedstoneEvent; +import sh.okx.railswitch.RailSwitch; +import vg.civcraft.mc.citadel.Citadel; +import vg.civcraft.mc.citadel.ReinforcementManager; +import vg.civcraft.mc.citadel.reinforcement.PlayerReinforcement; +import vg.civcraft.mc.citadel.reinforcement.Reinforcement; + +public class DectorRailActivateListener implements Listener { + private final RailSwitch plugin; + + public DectorRailActivateListener(RailSwitch plugin) { + this.plugin = plugin; + } + + @EventHandler + public void on(BlockRedstoneEvent e) { + Block block = e.getBlock(); + // check if block is a detector rail + if (block == null || block.getType() != Material.DETECTOR_RAIL || e.getNewCurrent() != 15) { + return; + } + + Player nearestPlayer = findNearestPlayerInMinecart(block.getLocation()); + if (nearestPlayer == null) { + // vanilla behaviour if no player + return; + } + + // check if the player has the same destination as + // the destination on the detector rail + long start = System.currentTimeMillis(); + boolean activate = isActivateRail(nearestPlayer, block.getLocation()); + if (plugin.isTimings()) { + plugin.getLogger().info("Took " + (System.currentTimeMillis() - start) + "ms"); + } + if (!activate) { + e.setNewCurrent(0); + } + } + + private boolean isActivateRail(Player player, Location location) { + Block above = location.getBlock().getRelative(BlockFace.UP); + if (!(above.getState() instanceof Sign)) { + // true = don't change vanilla behaviour + return true; + } + Sign sign = (Sign) above.getState(); + String[] lines = sign.getLines(); + if (!"[destination]".equalsIgnoreCase(lines[0])) { + return true; + } + + // sign and rail must be on the same reinforcement group + // to prevent abuse + if (Bukkit.getPluginManager().isPluginEnabled("Citadel") + && !isSameReinforcementGroup(above.getLocation(), location)) { + return true; + } + + String destination = plugin.getDatabase().getPlayerDestination(player); + if (destination == null) { + return true; + } + + // only activate redstone if the sign has a destination the player is set to + for (int i = 1; i < 4; i++) { + if (destination.equalsIgnoreCase(lines[i])) { + return true; + } + } + return false; + } + + private boolean isSameReinforcementGroup(Location a, Location b) { + ReinforcementManager rein = Citadel.getReinforcementManager(); + Reinforcement reinA = rein.getReinforcement(a); + Reinforcement reinB = rein.getReinforcement(b); + if (!(reinA instanceof PlayerReinforcement) || !(reinB instanceof PlayerReinforcement)) { + return false; + } + + PlayerReinforcement playerReinA = (PlayerReinforcement) reinA; + PlayerReinforcement playerReinB = (PlayerReinforcement) reinB; + + return playerReinA.getGroup().equals(playerReinB.getGroup()); + } + + /** + * @param location the centre + * @return the nearest player riding a minecart in a 3 block radius of the centre + */ + private Player findNearestPlayerInMinecart(Location location) { + World world = location.getWorld(); + Player nearestPlayer = null; + double distance = Double.MAX_VALUE; + for (Entity entity : world.getNearbyEntities(location, 3, 3, 3)) { + if (entity instanceof Player && entity.getVehicle() instanceof Minecart) { + double entityDistance = location.distanceSquared(entity.getLocation()); + if (entityDistance < distance) { + distance = entityDistance; + nearestPlayer = (Player) entity; + } + } + } + return nearestPlayer; + } +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailUseListener.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailUseListener.java new file mode 100644 index 000000000..70c70b5a6 --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailUseListener.java @@ -0,0 +1,99 @@ +package sh.okx.railswitch.listener; + +import com.google.common.base.CharMatcher; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import sh.okx.railswitch.RailSwitch; +import vg.civcraft.mc.citadel.Citadel; +import vg.civcraft.mc.citadel.reinforcement.PlayerReinforcement; +import vg.civcraft.mc.citadel.reinforcement.Reinforcement; +import vg.civcraft.mc.namelayer.NameAPI; +import vg.civcraft.mc.namelayer.permission.PermissionType; + +import java.util.Collections; +import java.util.Map; +import java.util.WeakHashMap; + +public class DetectorRailUseListener implements Listener { + /*private Map creatingSwitch = Collections.synchronizedMap(new WeakHashMap<>()); + private final RailSwitch plugin; + + public DetectorRailUseListener(RailSwitch plugin) { + this.plugin = plugin; + } + + @EventHandler + public void on(PlayerInteractEvent e) { + if (e.getAction() != Action.RIGHT_CLICK_BLOCK || e.getHand() != EquipmentSlot.HAND) { + return; + } + Block block = e.getClickedBlock(); + if (block.getType() != Material.DETECTOR_RAIL) { + return; + } + + Player player = e.getPlayer(); + if (Bukkit.getPluginManager().isPluginEnabled("Citadel")) { + Reinforcement reinforcement = Citadel.getReinforcementManager().getReinforcement(block); + if (!(reinforcement instanceof PlayerReinforcement)) { + return; + } + PlayerReinforcement playerReinforcement = (PlayerReinforcement) reinforcement; + + // check CREATE_RAIL_SWITCH permission + if (!NameAPI.getGroupManager().hasAccess(playerReinforcement.getGroup(), player.getUniqueId(), + PermissionType.getPermission("CREATE_RAIL_SWITCH"))) { + return; + } + } + + player.sendMessage(ChatColor.GOLD + "Type a destination name or 'none' for no destination"); + long start = System.currentTimeMillis(); + String destination = plugin.getDatabase().getDestination(block.getLocation()); + if (plugin.isTimings()) { + plugin.getLogger().info("Get rail destination took" + (System.currentTimeMillis()-start) + "ms"); + } + if (destination != null) { + player.sendMessage(ChatColor.GOLD + "The current destination is: " + destination); + } + creatingSwitch.put(player, block.getLocation()); + } + + @EventHandler + public void on(AsyncPlayerChatEvent e) { + Player player = e.getPlayer(); + Location location = creatingSwitch.remove(player); + if (location == null) { + return; + } + e.setCancelled(true); + + String message = e.getMessage(); + if (!plugin.isValidDestination(message)) { + player.sendMessage(ChatColor.RED + "Destination names cannot be more than 40 characters and may only use alphanumerical characters and ASCII symbols"); + player.sendMessage(ChatColor.RED + "Rail switch creation cancelled."); + return; + } + + long start = System.currentTimeMillis(); + plugin.getDatabase().setDestination(location, message); + if (plugin.isTimings()) { + plugin.getLogger().info("Set rail destination took " + (System.currentTimeMillis()-start) + "ms"); + } + if (message.equalsIgnoreCase("none")) { + player.sendMessage(ChatColor.GREEN + "Unset rail destination"); + } else { + player.sendMessage(ChatColor.GREEN + "Set detector rail destination to: " + message); + } + }*/ +} diff --git a/plugins/railswitch-paper/src/main/resources/config.yml b/plugins/railswitch-paper/src/main/resources/config.yml new file mode 100644 index 000000000..7570bcb26 --- /dev/null +++ b/plugins/railswitch-paper/src/main/resources/config.yml @@ -0,0 +1,9 @@ +mysql: + username: '' + host: localhost + password: '' + database: minecraft + port: 3306 + prefix: '' + +timings: false \ No newline at end of file diff --git a/plugins/railswitch-paper/src/main/resources/plugin.yml b/plugins/railswitch-paper/src/main/resources/plugin.yml new file mode 100644 index 000000000..fdbb81379 --- /dev/null +++ b/plugins/railswitch-paper/src/main/resources/plugin.yml @@ -0,0 +1,12 @@ +name: RailSwitch +version: 1.0 +main: sh.okx.railswitch.RailSwitch +author: Okx +description: Easily make rail switches for automated destination selection +depend: [CivModCore] +softdepend: [Citadel, NameLayer] +commands: + setdestination: + usage: / + description: Set your rail destination + aliases: [dest, destination, switch, setswitch, setsw] \ No newline at end of file From 85ebdb441203c6bedf474d810ff0965be884c2a0 Mon Sep 17 00:00:00 2001 From: okx-code Date: Sun, 16 Jun 2019 11:52:42 +0200 Subject: [PATCH 03/78] Update README.md --- plugins/railswitch-paper/README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/plugins/railswitch-paper/README.md b/plugins/railswitch-paper/README.md index 945a40824..c941143a0 100644 --- a/plugins/railswitch-paper/README.md +++ b/plugins/railswitch-paper/README.md @@ -1 +1,16 @@ -RailSwitch +# RailSwitch + +## How to use + +### Setup + +1. Place a sign in the block above a detector rail. +2. The first line must be `[destination]` (case insentitive) +3. The second, third and fourth lines can be a destination name +4. When a player passes over the detector rail, it will only activate and emit redstone if a player set their destination as any of the three on the sign. + +### Using + +1. Type `/dest ` +2. Go AFK +3. Arrive From 612da8d5936e55c3a00e3235f7bb584899377753 Mon Sep 17 00:00:00 2001 From: okx-code Date: Sun, 16 Jun 2019 11:54:25 +0200 Subject: [PATCH 04/78] Update README.md --- plugins/railswitch-paper/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/railswitch-paper/README.md b/plugins/railswitch-paper/README.md index c941143a0..43985273e 100644 --- a/plugins/railswitch-paper/README.md +++ b/plugins/railswitch-paper/README.md @@ -14,3 +14,7 @@ 1. Type `/dest ` 2. Go AFK 3. Arrive + +### Example Video + +[![RailSwitch Demo Video](https://img.youtube.com/vi/GKku2fcB-wY/0.jpg)](https://www.youtube.com/watch?v=GKku2fcB-wY) From dfdacbf20bcf038cf4e4dd10d8c2daa99b07e5d7 Mon Sep 17 00:00:00 2001 From: Okx Date: Sun, 16 Jun 2019 11:06:17 +0100 Subject: [PATCH 05/78] remove unused code --- .../java/sh/okx/railswitch/RailSwitch.java | 13 --- .../database/RailSwitchDatabase.java | 98 ------------------ .../listener/DetectorRailUseListener.java | 99 ------------------- 3 files changed, 210 deletions(-) delete mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailUseListener.java diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java index 46bd7dd98..d521fb15a 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java @@ -5,13 +5,7 @@ import org.bukkit.configuration.ConfigurationSection; import org.bukkit.plugin.PluginManager; import sh.okx.railswitch.database.RailSwitchDatabase; import sh.okx.railswitch.listener.DectorRailActivateListener; -import sh.okx.railswitch.listener.DetectorRailUseListener; import vg.civcraft.mc.civmodcore.ACivMod; -import vg.civcraft.mc.namelayer.GroupManager; -import vg.civcraft.mc.namelayer.permission.PermissionType; - -import java.util.Arrays; -import java.util.List; public class RailSwitch extends ACivMod { private boolean timings; @@ -28,13 +22,6 @@ public class RailSwitch extends ACivMod { } PluginManager pm = getServer().getPluginManager(); - if (pm.isPluginEnabled("NameLayer")) { - List modAndAbove = Arrays.asList( - GroupManager.PlayerType.MODS, - GroupManager.PlayerType.ADMINS, - GroupManager.PlayerType.OWNER); - PermissionType.registerPermission("CREATE_RAIL_SWITCH", modAndAbove, "Create a rail switch by right-clicking a detector rail"); - } pm.registerEvents(new DectorRailActivateListener(this), this); //pm.registerEvents(new DetectorRailUseListener(this), this); diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java index 31b5d32cd..fdee9654b 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java @@ -17,36 +17,20 @@ public class RailSwitchDatabase { private ConnectionPool pool; private Logger log; - // takes a uuid and location and returns a row if and only if that uuid - // has their destination set to the same destination as that location - //private String checkDestination; - //private String getDestination; - //private String setSwitchDestination; private String setPlayerDestination; - //private String removeSwitchDestination; private String getPlayerDestination; public RailSwitchDatabase(String host, int port, String db, String user, String password, String prefix, Logger logger) { log = logger; pool = new ConnectionPool(logger, user, password, host, port, db, 10, 1000, 600000, 7200000); - //switchTable = prefix + "switch"; destTable = prefix + "dest"; createTable(); - //loadSwitches(); loadStatements(); } private void createTable() { try (Connection connection = pool.getConnection()) { - /*connection.createStatement().executeUpdate( - "CREATE TABLE IF NOT EXISTS `" + switchTable + "` (" + - "`world` varchar(36) NOT NULL," + - "`x` int(10) NOT NULL," + - "`y` int(10) NOT NULL," + - "`z` int(10) NOT NULL," + - "`dest` varchar(40) NOT NULL," + - "PRIMARY KEY (`world`, `x`, `y`, `z`))");*/ connection.createStatement().executeUpdate( "CREATE TABLE IF NOT EXISTS `" + destTable + "` (" + "`uuid` VARCHAR(36) NOT NULL," + @@ -57,97 +41,15 @@ public class RailSwitchDatabase { } } - /*private void loadSwitches() { - ResultSet resultSet = getResultSet("SELECT * FROM `" + switchTable + "`"); - try { - while (resultSet.next()) { - String world = resultSet.getString("world"); - int x = resultSet.getInt("x"); - int y = resultSet.getInt("y"); - int z = resultSet.getInt("z"); - String dest = resultSet.getString("dest"); - switches.put(new Location(Bukkit.getWorld(world), x, y, z), dest); - } - } catch (SQLException e) { - logger.log(Level.SEVERE, "Could not get result set from SQL statement!", e); - } - }*/ - private void loadStatements() { - /*checkDestination = String.format( - "SELECT `%1$s`.`uuid` FROM `%2$s` JOIN `%1$s` ON `%2$s`.`dest`=`%1$s`.`dest` WHERE `x`=? AND `y`=? AND `z`=? AND `world`=? AND `uuid`=? LIMIT 1", - destTable, switchTable);*/ - /*getDestination = String.format( - "SELECT `dest` FROM `%1$s` WHERE `x`=? AND `y`=? AND `z`=? AND `world`=? LIMIT 1", - switchTable); - setSwitchDestination = String.format( - "REPLACE INTO `%1$s` VALUES (?, ?, ?, ?, ?)", - switchTable);*/ setPlayerDestination = String.format( "REPLACE INTO `%1$s` VALUES (?, ?)", destTable); - /*removeSwitchDestination = String.format( - "DELETE FROM `%s` WHERE `world`=? AND `x`=? AND `y`=? AND `z`=?", - switchTable);*/ getPlayerDestination = String.format( "SELECT `dest` FROM `%1$s` WHERE `uuid`=?", destTable); } - /*public boolean isActivateRail(Player player, Location location) { - try (Connection connection = pool.getConnection()) { - PreparedStatement statement = connection.prepareStatement(checkDestination); - statement.setInt(1, location.getBlockX()); - statement.setInt(2, location.getBlockY()); - statement.setInt(3, location.getBlockZ()); - statement.setString(4, location.getWorld().getUID().toString()); - statement.setString(5, player.getUniqueId().toString()); - - ResultSet resultSet = statement.executeQuery(); - return resultSet.next(); - } catch (SQLException e) { - log.log(Level.SEVERE, "Could not check activate rail", e); - return false; - } - } - - public String getDestination(Location location) { - try (Connection connection = pool.getConnection()) { - PreparedStatement statement = connection.prepareStatement(getDestination); - statement.setInt(1, location.getBlockX()); - statement.setInt(2, location.getBlockY()); - statement.setInt(3, location.getBlockZ()); - statement.setString(4, location.getWorld().getUID().toString()); - - ResultSet resultSet = statement.executeQuery(); - if (!resultSet.next()) { - return null; - } - return resultSet.getString("dest"); - } catch (SQLException e) { - log.log(Level.SEVERE, "Could not get destination", e); - return null; - } - } - - public void setDestination(Location location, String destination) { - try (Connection connection = pool.getConnection()) { - boolean none = destination.equalsIgnoreCase("none"); - PreparedStatement statement = connection.prepareStatement(none ? removeSwitchDestination : setSwitchDestination); - statement.setString(1, location.getWorld().getUID().toString()); - statement.setInt(2, location.getBlockX()); - statement.setInt(3, location.getBlockY()); - statement.setInt(4, location.getBlockZ()); - if (!none) { - statement.setString(5, destination); - } - - statement.executeUpdate(); - } catch (SQLException e) { - log.log(Level.SEVERE, "Could not set destination", e); - } - }*/ - public void setPlayerDestination(Player player, String destination) { try (Connection connection = pool.getConnection()) { PreparedStatement statement = connection.prepareStatement(setPlayerDestination); diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailUseListener.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailUseListener.java deleted file mode 100644 index 70c70b5a6..000000000 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailUseListener.java +++ /dev/null @@ -1,99 +0,0 @@ -package sh.okx.railswitch.listener; - -import com.google.common.base.CharMatcher; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.block.Action; -import org.bukkit.event.player.AsyncPlayerChatEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.EquipmentSlot; -import sh.okx.railswitch.RailSwitch; -import vg.civcraft.mc.citadel.Citadel; -import vg.civcraft.mc.citadel.reinforcement.PlayerReinforcement; -import vg.civcraft.mc.citadel.reinforcement.Reinforcement; -import vg.civcraft.mc.namelayer.NameAPI; -import vg.civcraft.mc.namelayer.permission.PermissionType; - -import java.util.Collections; -import java.util.Map; -import java.util.WeakHashMap; - -public class DetectorRailUseListener implements Listener { - /*private Map creatingSwitch = Collections.synchronizedMap(new WeakHashMap<>()); - private final RailSwitch plugin; - - public DetectorRailUseListener(RailSwitch plugin) { - this.plugin = plugin; - } - - @EventHandler - public void on(PlayerInteractEvent e) { - if (e.getAction() != Action.RIGHT_CLICK_BLOCK || e.getHand() != EquipmentSlot.HAND) { - return; - } - Block block = e.getClickedBlock(); - if (block.getType() != Material.DETECTOR_RAIL) { - return; - } - - Player player = e.getPlayer(); - if (Bukkit.getPluginManager().isPluginEnabled("Citadel")) { - Reinforcement reinforcement = Citadel.getReinforcementManager().getReinforcement(block); - if (!(reinforcement instanceof PlayerReinforcement)) { - return; - } - PlayerReinforcement playerReinforcement = (PlayerReinforcement) reinforcement; - - // check CREATE_RAIL_SWITCH permission - if (!NameAPI.getGroupManager().hasAccess(playerReinforcement.getGroup(), player.getUniqueId(), - PermissionType.getPermission("CREATE_RAIL_SWITCH"))) { - return; - } - } - - player.sendMessage(ChatColor.GOLD + "Type a destination name or 'none' for no destination"); - long start = System.currentTimeMillis(); - String destination = plugin.getDatabase().getDestination(block.getLocation()); - if (plugin.isTimings()) { - plugin.getLogger().info("Get rail destination took" + (System.currentTimeMillis()-start) + "ms"); - } - if (destination != null) { - player.sendMessage(ChatColor.GOLD + "The current destination is: " + destination); - } - creatingSwitch.put(player, block.getLocation()); - } - - @EventHandler - public void on(AsyncPlayerChatEvent e) { - Player player = e.getPlayer(); - Location location = creatingSwitch.remove(player); - if (location == null) { - return; - } - e.setCancelled(true); - - String message = e.getMessage(); - if (!plugin.isValidDestination(message)) { - player.sendMessage(ChatColor.RED + "Destination names cannot be more than 40 characters and may only use alphanumerical characters and ASCII symbols"); - player.sendMessage(ChatColor.RED + "Rail switch creation cancelled."); - return; - } - - long start = System.currentTimeMillis(); - plugin.getDatabase().setDestination(location, message); - if (plugin.isTimings()) { - plugin.getLogger().info("Set rail destination took " + (System.currentTimeMillis()-start) + "ms"); - } - if (message.equalsIgnoreCase("none")) { - player.sendMessage(ChatColor.GREEN + "Unset rail destination"); - } else { - player.sendMessage(ChatColor.GREEN + "Set detector rail destination to: " + message); - } - }*/ -} From 99061e575cee429d13a2331cf17e137a8320d5d9 Mon Sep 17 00:00:00 2001 From: Okx Date: Sun, 16 Jun 2019 11:56:50 +0100 Subject: [PATCH 06/78] remove civmodcore dependency, extend sql capabilities, add caching --- plugins/railswitch-paper/build.gradle | 16 ++++-- .../java/sh/okx/railswitch/RailSwitch.java | 36 +++++++----- .../database/AbstractConnectionPool.java | 17 ++++++ .../railswitch/database/ConnectionPool.java | 8 +++ .../database/MySQLConnectionPool.java | 22 +++++++ .../database/RailSwitchDatabase.java | 57 ++++++++++++------- .../database/SQLiteConnectionPool.java | 20 +++++++ .../src/main/resources/config.yml | 6 +- .../src/main/resources/plugin.yml | 3 +- 9 files changed, 142 insertions(+), 43 deletions(-) create mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/AbstractConnectionPool.java create mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/ConnectionPool.java create mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/MySQLConnectionPool.java create mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/SQLiteConnectionPool.java diff --git a/plugins/railswitch-paper/build.gradle b/plugins/railswitch-paper/build.gradle index 3e5af3540..05de8402c 100644 --- a/plugins/railswitch-paper/build.gradle +++ b/plugins/railswitch-paper/build.gradle @@ -1,12 +1,17 @@ plugins { id 'java' + id 'com.github.johnrengelman.shadow' version '5.0.0' } group 'sh.okx' -version '1.0-SNAPSHOT' +version '1.1' sourceCompatibility = 1.8 +shadowJar { + classifier = null +} + repositories { mavenCentral() maven { @@ -19,8 +24,9 @@ repositories { dependencies { testCompile group: 'junit', name: 'junit', version: '4.12' - compile 'vg.civcraft.mc.citadel:Citadel:3.9.0' - compile 'vg.civcraft.mc.civmodcore:CivModCore:1.6.1' - compile 'vg.civcraft.mc.namelayer:NameLayer:2.11.0' - compile 'org.spigotmc:spigot-api:1.12-R0.1-SNAPSHOT' + shadow 'vg.civcraft.mc.citadel:Citadel:3.9.0' + shadow 'vg.civcraft.mc.civmodcore:CivModCore:1.6.1' + shadow 'vg.civcraft.mc.namelayer:NameLayer:2.11.0' + shadow 'org.spigotmc:spigot-api:1.12.2-R0.1-SNAPSHOT' + compile 'com.zaxxer:HikariCP:3.3.1' } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java index d521fb15a..fb81eb1de 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java @@ -3,11 +3,14 @@ package sh.okx.railswitch; import com.google.common.base.CharMatcher; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.plugin.PluginManager; +import org.bukkit.plugin.java.JavaPlugin; +import sh.okx.railswitch.database.ConnectionPool; +import sh.okx.railswitch.database.MySQLConnectionPool; import sh.okx.railswitch.database.RailSwitchDatabase; +import sh.okx.railswitch.database.SQLiteConnectionPool; import sh.okx.railswitch.listener.DectorRailActivateListener; -import vg.civcraft.mc.civmodcore.ACivMod; -public class RailSwitch extends ACivMod { +public class RailSwitch extends JavaPlugin { private boolean timings; private RailSwitchDatabase database; @@ -24,7 +27,6 @@ public class RailSwitch extends ACivMod { PluginManager pm = getServer().getPluginManager(); pm.registerEvents(new DectorRailActivateListener(this), this); - //pm.registerEvents(new DetectorRailUseListener(this), this); getCommand("setdestination").setExecutor(new SetDestinationCommand(this)); } @@ -35,14 +37,23 @@ public class RailSwitch extends ACivMod { private void loadDatabase() { ConfigurationSection config = getConfig(); - String username = config.getString("mysql.username"); - String host = config.getString("mysql.host"); - String password = config.getString("mysql.password"); - String database = config.getString("mysql.database"); - String prefix = config.getString("mysql.prefix"); - int port = config.getInt("mysql.port"); + ConnectionPool pool; + String type = config.getString("database-type"); + if (type.equalsIgnoreCase("mysql")) { + String username = config.getString("mysql.username"); + String host = config.getString("mysql.host"); + String password = config.getString("mysql.password"); + String database = config.getString("mysql.database"); + int port = config.getInt("mysql.port"); - this.database = new RailSwitchDatabase(host, port, database, username, password, prefix, getLogger()); + pool = new MySQLConnectionPool(host, port, database, username, password); + } else if (type.equalsIgnoreCase("sqlite")) { + pool = new SQLiteConnectionPool(getDataFolder(), config.getString("sqlite.file-name")); + } else { + throw new RuntimeException("Invalid database-type in config.yml. Disabling plugin."); + } + + this.database = new RailSwitchDatabase(pool, getLogger()); } /** @@ -59,9 +70,4 @@ public class RailSwitch extends ACivMod { public boolean isTimings() { return timings; } - - @Override - protected String getPluginName() { - return "RailSwitch"; - } } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/AbstractConnectionPool.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/AbstractConnectionPool.java new file mode 100644 index 000000000..6c18a6ce3 --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/AbstractConnectionPool.java @@ -0,0 +1,17 @@ +package sh.okx.railswitch.database; + +public abstract class AbstractConnectionPool implements ConnectionPool { + protected final String host; + protected final int port; + protected final String database; + protected final String user; + protected final String password; + + protected AbstractConnectionPool(String host, int port, String database, String user, String password) { + this.host = host; + this.port = port; + this.database = database; + this.user = user; + this.password = password; + } +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/ConnectionPool.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/ConnectionPool.java new file mode 100644 index 000000000..9f2a2015e --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/ConnectionPool.java @@ -0,0 +1,8 @@ +package sh.okx.railswitch.database; + +import java.sql.Connection; +import java.sql.SQLException; + +public interface ConnectionPool { + Connection getConnection() throws SQLException; +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/MySQLConnectionPool.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/MySQLConnectionPool.java new file mode 100644 index 000000000..6faca1ee2 --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/MySQLConnectionPool.java @@ -0,0 +1,22 @@ +package sh.okx.railswitch.database; + +import com.zaxxer.hikari.HikariDataSource; + +import java.sql.Connection; +import java.sql.SQLException; + +public class MySQLConnectionPool implements ConnectionPool { + private final HikariDataSource hikariDataSource; + + public MySQLConnectionPool(String host, int port, String database, String user, String password) { + hikariDataSource = new HikariDataSource(); + hikariDataSource.setJdbcUrl("jdbc:mysql://" + host + ":" + port + "/" + database); + hikariDataSource.setUsername(user); + hikariDataSource.setPassword(password); + } + + @Override + public Connection getConnection() throws SQLException { + return hikariDataSource.getConnection(); + } +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java index fdee9654b..4228a704b 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java @@ -1,18 +1,22 @@ package sh.okx.railswitch.database; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import org.bukkit.entity.Player; -import vg.civcraft.mc.civmodcore.dao.ConnectionPool; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; public class RailSwitchDatabase { - //private String switchTable; - private String destTable; + private static final String DESTINATION_TABLE = "rsw_dest"; private ConnectionPool pool; private Logger log; @@ -20,11 +24,30 @@ public class RailSwitchDatabase { private String setPlayerDestination; private String getPlayerDestination; - public RailSwitchDatabase(String host, int port, String db, String user, String password, String prefix, Logger logger) { - log = logger; - pool = new ConnectionPool(logger, user, password, host, port, db, 10, 1000, 600000, 7200000); + private final LoadingCache> destinations = CacheBuilder.newBuilder() + .expireAfterAccess(10, TimeUnit.MINUTES) + .maximumSize(1000) + .build(new CacheLoader>() { + @Override + public Optional load(Player key) throws Exception { + try (Connection connection = pool.getConnection()) { + PreparedStatement statement = connection.prepareStatement(getPlayerDestination); + statement.setString(1, key.getUniqueId().toString()); + + ResultSet resultSet = statement.executeQuery(); + if (resultSet.next()) { + return Optional.of(resultSet.getString("dest")); + } else { + return Optional.empty(); + } + } + } + }); + + public RailSwitchDatabase(ConnectionPool pool, Logger logger) { + log = logger; + this.pool = pool; - destTable = prefix + "dest"; createTable(); loadStatements(); } @@ -32,7 +55,7 @@ public class RailSwitchDatabase { private void createTable() { try (Connection connection = pool.getConnection()) { connection.createStatement().executeUpdate( - "CREATE TABLE IF NOT EXISTS `" + destTable + "` (" + + "CREATE TABLE IF NOT EXISTS `" + DESTINATION_TABLE + "` (" + "`uuid` VARCHAR(36) NOT NULL," + "`dest` VARCHAR(40) NOT NULL," + "PRIMARY KEY (`uuid`))"); @@ -44,13 +67,14 @@ public class RailSwitchDatabase { private void loadStatements() { setPlayerDestination = String.format( "REPLACE INTO `%1$s` VALUES (?, ?)", - destTable); + DESTINATION_TABLE); getPlayerDestination = String.format( "SELECT `dest` FROM `%1$s` WHERE `uuid`=?", - destTable); + DESTINATION_TABLE); } public void setPlayerDestination(Player player, String destination) { + destinations.put(player, Optional.of(destination)); try (Connection connection = pool.getConnection()) { PreparedStatement statement = connection.prepareStatement(setPlayerDestination); statement.setString(1, player.getUniqueId().toString()); @@ -63,16 +87,9 @@ public class RailSwitchDatabase { } public String getPlayerDestination(Player player) { - try (Connection connection = pool.getConnection()) { - PreparedStatement statement = connection.prepareStatement(getPlayerDestination); - statement.setString(1, player.getUniqueId().toString()); - - ResultSet resultSet = statement.executeQuery(); - if (!resultSet.next()) { - return null; - } - return resultSet.getString("dest"); - } catch (SQLException e) { + try { + return destinations.get(player).orElse(null); + } catch (ExecutionException e) { log.log(Level.SEVERE, "Could not get destination", e); return null; } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/SQLiteConnectionPool.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/SQLiteConnectionPool.java new file mode 100644 index 000000000..ac5b76bab --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/SQLiteConnectionPool.java @@ -0,0 +1,20 @@ +package sh.okx.railswitch.database; + +import java.io.File; +import java.nio.file.Path; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +public class SQLiteConnectionPool implements ConnectionPool { + private Path file; + + public SQLiteConnectionPool(File dataFolder, String file) { + this.file = dataFolder.toPath().resolve(file); + } + + @Override + public Connection getConnection() throws SQLException { + return DriverManager.getConnection("jdbc:sqlite:" + file); + } +} diff --git a/plugins/railswitch-paper/src/main/resources/config.yml b/plugins/railswitch-paper/src/main/resources/config.yml index 7570bcb26..d789c4f78 100644 --- a/plugins/railswitch-paper/src/main/resources/config.yml +++ b/plugins/railswitch-paper/src/main/resources/config.yml @@ -1,9 +1,13 @@ +# can be mysql or sqlite +database-type: mysql + mysql: username: '' host: localhost password: '' database: minecraft port: 3306 - prefix: '' +sqlite: + file-name: data.db timings: false \ No newline at end of file diff --git a/plugins/railswitch-paper/src/main/resources/plugin.yml b/plugins/railswitch-paper/src/main/resources/plugin.yml index fdbb81379..2946a2176 100644 --- a/plugins/railswitch-paper/src/main/resources/plugin.yml +++ b/plugins/railswitch-paper/src/main/resources/plugin.yml @@ -1,9 +1,8 @@ name: RailSwitch -version: 1.0 +version: '1.1' main: sh.okx.railswitch.RailSwitch author: Okx description: Easily make rail switches for automated destination selection -depend: [CivModCore] softdepend: [Citadel, NameLayer] commands: setdestination: From daca8c05689a5f4b12002ac4b5a89eba9d6440f1 Mon Sep 17 00:00:00 2001 From: Okx Date: Sun, 16 Jun 2019 12:50:48 +0100 Subject: [PATCH 07/78] expire after 10 hours, not 10 minutes --- .../java/sh/okx/railswitch/database/RailSwitchDatabase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java index 4228a704b..fae645fed 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java @@ -25,7 +25,7 @@ public class RailSwitchDatabase { private String getPlayerDestination; private final LoadingCache> destinations = CacheBuilder.newBuilder() - .expireAfterAccess(10, TimeUnit.MINUTES) + .expireAfterAccess(10, TimeUnit.HOURS) .maximumSize(1000) .build(new CacheLoader>() { @Override From b6ef1d978eb1c52541b2a8cf4753acc01883ac5c Mon Sep 17 00:00:00 2001 From: okx-code Date: Sun, 16 Jun 2019 21:02:00 +0200 Subject: [PATCH 08/78] Update README.md --- plugins/railswitch-paper/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/railswitch-paper/README.md b/plugins/railswitch-paper/README.md index 43985273e..99adc25cd 100644 --- a/plugins/railswitch-paper/README.md +++ b/plugins/railswitch-paper/README.md @@ -4,7 +4,7 @@ ### Setup -1. Place a sign in the block above a detector rail. +1. Place a sign in the block above a detector rail. **It must be reinforced on the same group as the detector rail** 2. The first line must be `[destination]` (case insentitive) 3. The second, third and fourth lines can be a destination name 4. When a player passes over the detector rail, it will only activate and emit redstone if a player set their destination as any of the three on the sign. From 31dbae9ac0073a16a56cc8aba902b07285473539 Mon Sep 17 00:00:00 2001 From: okx-code Date: Sun, 30 Jun 2019 23:42:18 +0100 Subject: [PATCH 09/78] 1.2 - players without a destination set no longer activate rails - can now unset destination --- plugins/railswitch-paper/build.gradle | 2 +- .../sh/okx/railswitch/SetDestinationCommand.java | 9 ++++++++- .../railswitch/database/RailSwitchDatabase.java | 16 ++++++++++++++++ .../listener/DectorRailActivateListener.java | 6 ++++-- .../src/main/resources/plugin.yml | 2 +- 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/plugins/railswitch-paper/build.gradle b/plugins/railswitch-paper/build.gradle index 05de8402c..450a87984 100644 --- a/plugins/railswitch-paper/build.gradle +++ b/plugins/railswitch-paper/build.gradle @@ -4,7 +4,7 @@ plugins { } group 'sh.okx' -version '1.1' +version '1.2' sourceCompatibility = 1.8 diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java index 95556fdef..8b2d7a2e0 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java @@ -15,11 +15,18 @@ public class SetDestinationCommand implements CommandExecutor { @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - if (!(sender instanceof Player) || args.length < 1) { + if (!(sender instanceof Player)) { return false; } + Player player = (Player) sender; + if (args.length < 1) { + player.sendMessage(ChatColor.GREEN + "Unset rail destination (use /" + label + " to set your destination)."); + plugin.getDatabase().removePlayerDestination(player); + return true; + } + String dest = args[0]; if (!plugin.isValidDestination(dest)) { player.sendMessage(ChatColor.RED + "Destinations can not be more than 40 characters and may only use alphanumerical characters and ASCII symbols."); diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java index fae645fed..b36292ef6 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java @@ -23,6 +23,7 @@ public class RailSwitchDatabase { private String setPlayerDestination; private String getPlayerDestination; + private String removePlayerDestination; private final LoadingCache> destinations = CacheBuilder.newBuilder() .expireAfterAccess(10, TimeUnit.HOURS) @@ -71,6 +72,9 @@ public class RailSwitchDatabase { getPlayerDestination = String.format( "SELECT `dest` FROM `%1$s` WHERE `uuid`=?", DESTINATION_TABLE); + removePlayerDestination = String.format( + "DELETE FROM `%1$s` WHERE `uuid`=?`,", + DESTINATION_TABLE); } public void setPlayerDestination(Player player, String destination) { @@ -86,6 +90,18 @@ public class RailSwitchDatabase { } } + public void removePlayerDestination(Player player) { + destinations.refresh(player); + try (Connection connection = pool.getConnection()) { + PreparedStatement statement = connection.prepareStatement(removePlayerDestination); + statement.setString(1, player.getUniqueId().toString()); + + statement.executeUpdate(); + } catch (SQLException e) { + log.log(Level.SEVERE, "Could not remove destination", e); + } + } + public String getPlayerDestination(Player player) { try { return destinations.get(player).orElse(null); diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DectorRailActivateListener.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DectorRailActivateListener.java index af2658577..8e069624f 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DectorRailActivateListener.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DectorRailActivateListener.java @@ -72,13 +72,15 @@ public class DectorRailActivateListener implements Listener { } String destination = plugin.getDatabase().getPlayerDestination(player); + // don't activate rail if a destination is not set if (destination == null) { - return true; + return false; } // only activate redstone if the sign has a destination the player is set to + // if the sign has a * and a player has a destination set, activate it for (int i = 1; i < 4; i++) { - if (destination.equalsIgnoreCase(lines[i])) { + if (destination.equalsIgnoreCase(lines[i]) || lines[i].equals("*")) { return true; } } diff --git a/plugins/railswitch-paper/src/main/resources/plugin.yml b/plugins/railswitch-paper/src/main/resources/plugin.yml index 2946a2176..e9c176229 100644 --- a/plugins/railswitch-paper/src/main/resources/plugin.yml +++ b/plugins/railswitch-paper/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: RailSwitch -version: '1.1' +version: '1.2' main: sh.okx.railswitch.RailSwitch author: Okx description: Easily make rail switches for automated destination selection From 8b00834bb9589c4be5f19207b1c103a6079523df Mon Sep 17 00:00:00 2001 From: Kyle Date: Thu, 4 Jul 2019 23:51:07 -0400 Subject: [PATCH 10/78] Allow spaces in destinations --- .../src/main/java/sh/okx/railswitch/RailSwitch.java | 2 +- .../main/java/sh/okx/railswitch/SetDestinationCommand.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java index fb81eb1de..9608b91d6 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java @@ -64,7 +64,7 @@ public class RailSwitch extends JavaPlugin { && CharMatcher.inRange('0', '9') .or(CharMatcher.inRange('a', 'z')) .or(CharMatcher.inRange('A', 'Z')) - .or(CharMatcher.anyOf("!\"#$%&'()*+,-./;:<=>?@[]\\^_`{|}~")).matchesAllOf(message); + .or(CharMatcher.anyOf("!\"#$%&'()*+,-./;:<=>?@[]\\^_`{|}~ ")).matchesAllOf(message); } public boolean isTimings() { diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java index 8b2d7a2e0..bf0a42ce2 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java @@ -27,9 +27,9 @@ public class SetDestinationCommand implements CommandExecutor { return true; } - String dest = args[0]; + String dest = String.join(" ", args); if (!plugin.isValidDestination(dest)) { - player.sendMessage(ChatColor.RED + "Destinations can not be more than 40 characters and may only use alphanumerical characters and ASCII symbols."); + player.sendMessage(ChatColor.RED + "Destinations can not be more than 40 characters and may only use alphanumerical characters, ASCII symbols, and spaces."); return true; } From 32cae6c953fe767bdd3eb1ed5c0a735689687aab Mon Sep 17 00:00:00 2001 From: Kyle Date: Fri, 5 Jul 2019 00:18:43 -0400 Subject: [PATCH 11/78] Fix error on unset destination --- .../java/sh/okx/railswitch/database/RailSwitchDatabase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java index b36292ef6..b52f456c0 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java @@ -73,7 +73,7 @@ public class RailSwitchDatabase { "SELECT `dest` FROM `%1$s` WHERE `uuid`=?", DESTINATION_TABLE); removePlayerDestination = String.format( - "DELETE FROM `%1$s` WHERE `uuid`=?`,", + "DELETE FROM `%1$s` WHERE `uuid`=?", DESTINATION_TABLE); } From 4588ae7f792a823417a385d6f7c4c34aa20b941a Mon Sep 17 00:00:00 2001 From: okx-code Date: Sat, 7 Sep 2019 17:01:16 +0100 Subject: [PATCH 12/78] Update gradle.yml --- .../.github/workflows/gradle.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 plugins/railswitch-paper/.github/workflows/gradle.yml diff --git a/plugins/railswitch-paper/.github/workflows/gradle.yml b/plugins/railswitch-paper/.github/workflows/gradle.yml new file mode 100644 index 000000000..0e1231212 --- /dev/null +++ b/plugins/railswitch-paper/.github/workflows/gradle.yml @@ -0,0 +1,17 @@ +name: Java CI + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Build with Gradle + run: ./gradlew build From a55af4658e953357481fb72cd6d692bfed01994e Mon Sep 17 00:00:00 2001 From: Okx Date: Mon, 16 Sep 2019 22:33:40 +0100 Subject: [PATCH 13/78] do stuff async --- plugins/railswitch-paper/build.gradle | 4 +- .../java/sh/okx/railswitch/RailSwitch.java | 4 +- .../okx/railswitch/SetDestinationCommand.java | 4 ++ .../database/RailSwitchDatabase.java | 47 ++++++++++--------- ...java => DetectorRailActivateListener.java} | 12 +++-- .../src/main/resources/plugin.yml | 3 +- 6 files changed, 43 insertions(+), 31 deletions(-) rename plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/{DectorRailActivateListener.java => DetectorRailActivateListener.java} (91%) diff --git a/plugins/railswitch-paper/build.gradle b/plugins/railswitch-paper/build.gradle index 450a87984..1b94b8fe9 100644 --- a/plugins/railswitch-paper/build.gradle +++ b/plugins/railswitch-paper/build.gradle @@ -4,7 +4,7 @@ plugins { } group 'sh.okx' -version '1.2' +version '1.2.1' sourceCompatibility = 1.8 @@ -27,6 +27,6 @@ dependencies { shadow 'vg.civcraft.mc.citadel:Citadel:3.9.0' shadow 'vg.civcraft.mc.civmodcore:CivModCore:1.6.1' shadow 'vg.civcraft.mc.namelayer:NameLayer:2.11.0' - shadow 'org.spigotmc:spigot-api:1.12.2-R0.1-SNAPSHOT' + shadow 'org.spigotmc:spigot-api:1.14.4-R0.1-SNAPSHOT' compile 'com.zaxxer:HikariCP:3.3.1' } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java index 9608b91d6..24a92435f 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java @@ -8,7 +8,7 @@ import sh.okx.railswitch.database.ConnectionPool; import sh.okx.railswitch.database.MySQLConnectionPool; import sh.okx.railswitch.database.RailSwitchDatabase; import sh.okx.railswitch.database.SQLiteConnectionPool; -import sh.okx.railswitch.listener.DectorRailActivateListener; +import sh.okx.railswitch.listener.DetectorRailActivateListener; public class RailSwitch extends JavaPlugin { private boolean timings; @@ -26,7 +26,7 @@ public class RailSwitch extends JavaPlugin { PluginManager pm = getServer().getPluginManager(); - pm.registerEvents(new DectorRailActivateListener(this), this); + pm.registerEvents(new DetectorRailActivateListener(this), this); getCommand("setdestination").setExecutor(new SetDestinationCommand(this)); } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java index bf0a42ce2..8d7d80edd 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java @@ -33,7 +33,11 @@ public class SetDestinationCommand implements CommandExecutor { return true; } + long start = System.currentTimeMillis(); plugin.getDatabase().setPlayerDestination(player, dest); + if (plugin.isTimings()) { + plugin.getLogger().info("Set destination took " + (System.currentTimeMillis() - start) + "ms"); + } player.sendMessage(ChatColor.GREEN + "Set your rail destination to: " + dest); return true; } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java index b52f456c0..d0598ba44 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java @@ -11,7 +11,8 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Optional; import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.logging.Level; import java.util.logging.Logger; @@ -24,9 +25,9 @@ public class RailSwitchDatabase { private String setPlayerDestination; private String getPlayerDestination; private String removePlayerDestination; + private ExecutorService service = Executors.newSingleThreadExecutor(); private final LoadingCache> destinations = CacheBuilder.newBuilder() - .expireAfterAccess(10, TimeUnit.HOURS) .maximumSize(1000) .build(new CacheLoader>() { @Override @@ -79,35 +80,39 @@ public class RailSwitchDatabase { public void setPlayerDestination(Player player, String destination) { destinations.put(player, Optional.of(destination)); - try (Connection connection = pool.getConnection()) { - PreparedStatement statement = connection.prepareStatement(setPlayerDestination); - statement.setString(1, player.getUniqueId().toString()); - statement.setString(2, destination); + service.execute(() -> { + try (Connection connection = pool.getConnection()) { + PreparedStatement statement = connection.prepareStatement(setPlayerDestination); + statement.setString(1, player.getUniqueId().toString()); + statement.setString(2, destination); - statement.executeUpdate(); - } catch (SQLException e) { - log.log(Level.SEVERE, "Could not set destination", e); - } + statement.executeUpdate(); + } catch (SQLException e) { + log.log(Level.SEVERE, "Could not set destination", e); + } + }); } public void removePlayerDestination(Player player) { - destinations.refresh(player); - try (Connection connection = pool.getConnection()) { - PreparedStatement statement = connection.prepareStatement(removePlayerDestination); - statement.setString(1, player.getUniqueId().toString()); + destinations.put(player, Optional.empty()); + service.execute(() -> { + try (Connection connection = pool.getConnection()) { + PreparedStatement statement = connection.prepareStatement(removePlayerDestination); + statement.setString(1, player.getUniqueId().toString()); - statement.executeUpdate(); - } catch (SQLException e) { - log.log(Level.SEVERE, "Could not remove destination", e); - } + statement.executeUpdate(); + } catch (SQLException e) { + log.log(Level.SEVERE, "Could not remove destination", e); + } + }); } - public String getPlayerDestination(Player player) { + public Optional getPlayerDestination(Player player) { try { - return destinations.get(player).orElse(null); + return destinations.get(player); } catch (ExecutionException e) { log.log(Level.SEVERE, "Could not get destination", e); - return null; + return Optional.empty(); } } } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DectorRailActivateListener.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java similarity index 91% rename from plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DectorRailActivateListener.java rename to plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java index 8e069624f..7e8bdbb37 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DectorRailActivateListener.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java @@ -19,10 +19,12 @@ import vg.civcraft.mc.citadel.ReinforcementManager; import vg.civcraft.mc.citadel.reinforcement.PlayerReinforcement; import vg.civcraft.mc.citadel.reinforcement.Reinforcement; -public class DectorRailActivateListener implements Listener { +import java.util.Optional; + +public class DetectorRailActivateListener implements Listener { private final RailSwitch plugin; - public DectorRailActivateListener(RailSwitch plugin) { + public DetectorRailActivateListener(RailSwitch plugin) { this.plugin = plugin; } @@ -71,16 +73,16 @@ public class DectorRailActivateListener implements Listener { return true; } - String destination = plugin.getDatabase().getPlayerDestination(player); + Optional destination = plugin.getDatabase().getPlayerDestination(player); // don't activate rail if a destination is not set - if (destination == null) { + if (!destination.isPresent()) { return false; } // only activate redstone if the sign has a destination the player is set to // if the sign has a * and a player has a destination set, activate it for (int i = 1; i < 4; i++) { - if (destination.equalsIgnoreCase(lines[i]) || lines[i].equals("*")) { + if (destination.get().equalsIgnoreCase(lines[i]) || lines[i].equals("*")) { return true; } } diff --git a/plugins/railswitch-paper/src/main/resources/plugin.yml b/plugins/railswitch-paper/src/main/resources/plugin.yml index e9c176229..5435ac464 100644 --- a/plugins/railswitch-paper/src/main/resources/plugin.yml +++ b/plugins/railswitch-paper/src/main/resources/plugin.yml @@ -1,7 +1,8 @@ name: RailSwitch -version: '1.2' +version: '1.2.1' main: sh.okx.railswitch.RailSwitch author: Okx +api-version: '1.14' description: Easily make rail switches for automated destination selection softdepend: [Citadel, NameLayer] commands: From 4e5f8e5fe38cdf00c201b4e2cfe780d82ff95dfd Mon Sep 17 00:00:00 2001 From: okx-code Date: Mon, 16 Sep 2019 22:36:29 +0100 Subject: [PATCH 14/78] Update gradle.yml --- plugins/railswitch-paper/.github/workflows/gradle.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/railswitch-paper/.github/workflows/gradle.yml b/plugins/railswitch-paper/.github/workflows/gradle.yml index 0e1231212..efeb2a2e8 100644 --- a/plugins/railswitch-paper/.github/workflows/gradle.yml +++ b/plugins/railswitch-paper/.github/workflows/gradle.yml @@ -14,4 +14,6 @@ jobs: with: java-version: 1.8 - name: Build with Gradle - run: ./gradlew build + run: | + chmod +x gradlew + ./gradlew build From e1298afe858d5919d81106239dde9e7d5f4edb62 Mon Sep 17 00:00:00 2001 From: okx-code Date: Sat, 21 Sep 2019 20:08:12 +0100 Subject: [PATCH 15/78] fix typos --- plugins/railswitch-paper/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/railswitch-paper/README.md b/plugins/railswitch-paper/README.md index 99adc25cd..77fb122a5 100644 --- a/plugins/railswitch-paper/README.md +++ b/plugins/railswitch-paper/README.md @@ -4,12 +4,12 @@ ### Setup -1. Place a sign in the block above a detector rail. **It must be reinforced on the same group as the detector rail** -2. The first line must be `[destination]` (case insentitive) -3. The second, third and fourth lines can be a destination name +1. Place a sign in the block above a detector rail. **It must be reinforced on the same group as the detector rail.** +2. The first line must be `[destination]` (case insensitive) +3. The second, third and fourth lines can be destination names. 4. When a player passes over the detector rail, it will only activate and emit redstone if a player set their destination as any of the three on the sign. -### Using +### Usage 1. Type `/dest ` 2. Go AFK From ae25bad1c8e30c9c69b3b2f29adf4962f2aea6c1 Mon Sep 17 00:00:00 2001 From: Alexander <52572989+Protonull@users.noreply.github.com> Date: Tue, 5 Nov 2019 21:14:37 +0000 Subject: [PATCH 16/78] Added inverted rail switch + clean up --- .../DetectorRailActivateListener.java | 120 +++++++++++------- 1 file changed, 72 insertions(+), 48 deletions(-) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java index 7e8bdbb37..d31151d46 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java @@ -22,71 +22,95 @@ import vg.civcraft.mc.citadel.reinforcement.Reinforcement; import java.util.Optional; public class DetectorRailActivateListener implements Listener { + + private static final String NORMAL_SWITCH_TYPE = "[destination]"; + private static final String INVERTED_SWITCH_TYPE = "[!destination]"; + private final RailSwitch plugin; + private long timingsStartTime = System.currentTimeMillis(); public DetectorRailActivateListener(RailSwitch plugin) { this.plugin = plugin; } - @EventHandler - public void on(BlockRedstoneEvent e) { - Block block = e.getBlock(); - // check if block is a detector rail - if (block == null || block.getType() != Material.DETECTOR_RAIL || e.getNewCurrent() != 15) { - return; - } - - Player nearestPlayer = findNearestPlayerInMinecart(block.getLocation()); - if (nearestPlayer == null) { - // vanilla behaviour if no player - return; - } - - // check if the player has the same destination as - // the destination on the detector rail - long start = System.currentTimeMillis(); - boolean activate = isActivateRail(nearestPlayer, block.getLocation()); + private void concludeTiming() { if (plugin.isTimings()) { - plugin.getLogger().info("Took " + (System.currentTimeMillis() - start) + "ms"); - } - if (!activate) { - e.setNewCurrent(0); + plugin.getLogger().info("Took " + (System.currentTimeMillis() - timingsStartTime) + "ms"); } } - private boolean isActivateRail(Player player, Location location) { - Block above = location.getBlock().getRelative(BlockFace.UP); + @EventHandler + public void onRailSwitch(BlockRedstoneEvent event) { + // If Citadel is not enabled, do nothing + if (!Bukkit.getPluginManager().isPluginEnabled("Citadel")) { + return; + } + // If the block is null or empty, do nothing + Block block = event.getBlock(); + if (block == null) { + return; + } + // If the block is not a detector rail, do nothing + if (block.getType() != Material.DETECTOR_RAIL) { + return; + } + // If the detector rail is not being activated, do nothing + if (event.getNewCurrent() != 15) { + return; + } + // If the block above the detector rail is not a sign, do nothing + Block above = block.getRelative(BlockFace.UP); if (!(above.getState() instanceof Sign)) { - // true = don't change vanilla behaviour - return true; + return; } - Sign sign = (Sign) above.getState(); - String[] lines = sign.getLines(); - if (!"[destination]".equalsIgnoreCase(lines[0])) { - return true; + // If the sign is not a rail switch sign, do nothing + String[] lines = ((Sign) above.getState()).getLines(); + if (!(lines[0].equalsIgnoreCase(NORMAL_SWITCH_TYPE) || lines[0].equalsIgnoreCase(INVERTED_SWITCH_TYPE))) { + return; } - - // sign and rail must be on the same reinforcement group - // to prevent abuse - if (Bukkit.getPluginManager().isPluginEnabled("Citadel") - && !isSameReinforcementGroup(above.getLocation(), location)) { - return true; + // Start timings here - there's some expensive calls coming up + timingsStartTime = System.currentTimeMillis(); + // If detector rail and the sign are not on the same reinforcement group, do nothing + if (!isSameReinforcementGroup(block.getLocation(), above.getLocation())) { + concludeTiming(); + return; } - + // If the player riding the cart cannot be found, do nothing + Player player = findNearestPlayerInMinecart(block.getLocation()); + if (player == null) { + concludeTiming(); + return; + } + // Determine if the destination is present on the sign Optional destination = plugin.getDatabase().getPlayerDestination(player); - // don't activate rail if a destination is not set - if (!destination.isPresent()) { - return false; - } - - // only activate redstone if the sign has a destination the player is set to - // if the sign has a * and a player has a destination set, activate it - for (int i = 1; i < 4; i++) { - if (destination.get().equalsIgnoreCase(lines[i]) || lines[i].equals("*")) { - return true; + boolean matched = false; + boolean wildcard = false; + if (destination.isPresent()) { + for (String line : lines) { + if (line.equals("*")) { + matched = true; + wildcard = true; + break; + } + if (line.equalsIgnoreCase(destination.get())) { + matched = true; + break; + } } } - return false; + // Determine the behaviour of the switch by its type + if (lines[0].equalsIgnoreCase(NORMAL_SWITCH_TYPE)) { + if (!matched) { + event.setNewCurrent(0); + } + } + else if (lines[0].equalsIgnoreCase(INVERTED_SWITCH_TYPE)) { + if (matched) { + event.setNewCurrent(0); + } + } + // Calculate how much time this process took + concludeTiming(); } private boolean isSameReinforcementGroup(Location a, Location b) { From 2bf0996630da4b4604ac1c57968ed5a53bb684c2 Mon Sep 17 00:00:00 2001 From: Alexander <52572989+Protonull@users.noreply.github.com> Date: Tue, 5 Nov 2019 21:31:38 +0000 Subject: [PATCH 17/78] Re-allows Citadel to be optional --- .../listener/DetectorRailActivateListener.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java index d31151d46..030de7021 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java @@ -41,10 +41,6 @@ public class DetectorRailActivateListener implements Listener { @EventHandler public void onRailSwitch(BlockRedstoneEvent event) { - // If Citadel is not enabled, do nothing - if (!Bukkit.getPluginManager().isPluginEnabled("Citadel")) { - return; - } // If the block is null or empty, do nothing Block block = event.getBlock(); if (block == null) { @@ -71,9 +67,11 @@ public class DetectorRailActivateListener implements Listener { // Start timings here - there's some expensive calls coming up timingsStartTime = System.currentTimeMillis(); // If detector rail and the sign are not on the same reinforcement group, do nothing - if (!isSameReinforcementGroup(block.getLocation(), above.getLocation())) { - concludeTiming(); - return; + if (Bukkit.getPluginManager().isPluginEnabled("Citadel")) { + if (!isSameReinforcementGroup(block.getLocation(), above.getLocation())) { + concludeTiming(); + return; + } } // If the player riding the cart cannot be found, do nothing Player player = findNearestPlayerInMinecart(block.getLocation()); From e4f109b1f210a353e986c63d93e6f2929a6a16e2 Mon Sep 17 00:00:00 2001 From: Okx Date: Tue, 12 Nov 2019 23:17:18 +0000 Subject: [PATCH 18/78] manually merge #5 --- plugins/railswitch-paper/build.gradle | 5 +- .../DetectorRailActivateListener.java | 85 ++++++------------- .../src/main/resources/plugin.yml | 2 +- 3 files changed, 32 insertions(+), 60 deletions(-) diff --git a/plugins/railswitch-paper/build.gradle b/plugins/railswitch-paper/build.gradle index 1b94b8fe9..47e4f254d 100644 --- a/plugins/railswitch-paper/build.gradle +++ b/plugins/railswitch-paper/build.gradle @@ -4,7 +4,7 @@ plugins { } group 'sh.okx' -version '1.2.1' +version '1.2.2' sourceCompatibility = 1.8 @@ -14,6 +14,7 @@ shadowJar { repositories { mavenCentral() + mavenLocal() maven { url 'https://hub.spigotmc.org/nexus/content/groups/public/' } @@ -27,6 +28,6 @@ dependencies { shadow 'vg.civcraft.mc.citadel:Citadel:3.9.0' shadow 'vg.civcraft.mc.civmodcore:CivModCore:1.6.1' shadow 'vg.civcraft.mc.namelayer:NameLayer:2.11.0' - shadow 'org.spigotmc:spigot-api:1.14.4-R0.1-SNAPSHOT' + shadow 'org.spigotmc:spigot-api:1.12.2-R0.1-SNAPSHOT' compile 'com.zaxxer:HikariCP:3.3.1' } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java index 030de7021..497cf4232 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java @@ -27,88 +27,59 @@ public class DetectorRailActivateListener implements Listener { private static final String INVERTED_SWITCH_TYPE = "[!destination]"; private final RailSwitch plugin; - private long timingsStartTime = System.currentTimeMillis(); public DetectorRailActivateListener(RailSwitch plugin) { this.plugin = plugin; } - private void concludeTiming() { - if (plugin.isTimings()) { - plugin.getLogger().info("Took " + (System.currentTimeMillis() - timingsStartTime) + "ms"); - } - } - @EventHandler public void onRailSwitch(BlockRedstoneEvent event) { - // If the block is null or empty, do nothing Block block = event.getBlock(); - if (block == null) { + if (block == null + || block.getType() != Material.DETECTOR_RAIL + || event.getNewCurrent() != 15) { return; } - // If the block is not a detector rail, do nothing - if (block.getType() != Material.DETECTOR_RAIL) { - return; - } - // If the detector rail is not being activated, do nothing - if (event.getNewCurrent() != 15) { - return; - } - // If the block above the detector rail is not a sign, do nothing + Block above = block.getRelative(BlockFace.UP); if (!(above.getState() instanceof Sign)) { return; } - // If the sign is not a rail switch sign, do nothing + String[] lines = ((Sign) above.getState()).getLines(); - if (!(lines[0].equalsIgnoreCase(NORMAL_SWITCH_TYPE) || lines[0].equalsIgnoreCase(INVERTED_SWITCH_TYPE))) { + if (!(lines[0].equalsIgnoreCase(NORMAL_SWITCH_TYPE) + || lines[0].equalsIgnoreCase(INVERTED_SWITCH_TYPE))) { return; } - // Start timings here - there's some expensive calls coming up - timingsStartTime = System.currentTimeMillis(); - // If detector rail and the sign are not on the same reinforcement group, do nothing - if (Bukkit.getPluginManager().isPluginEnabled("Citadel")) { - if (!isSameReinforcementGroup(block.getLocation(), above.getLocation())) { - concludeTiming(); - return; - } + + if (Bukkit.getPluginManager().isPluginEnabled("Citadel") + && !isSameReinforcementGroup(block.getLocation(), above.getLocation())) { + return; } - // If the player riding the cart cannot be found, do nothing + Player player = findNearestPlayerInMinecart(block.getLocation()); if (player == null) { - concludeTiming(); return; } - // Determine if the destination is present on the sign + Optional destination = plugin.getDatabase().getPlayerDestination(player); - boolean matched = false; - boolean wildcard = false; - if (destination.isPresent()) { - for (String line : lines) { - if (line.equals("*")) { - matched = true; - wildcard = true; - break; - } - if (line.equalsIgnoreCase(destination.get())) { - matched = true; - break; - } + + boolean matched = destination.map(d -> isSignMatchDestination(lines, d)).orElse(false); + + if (lines[0].equalsIgnoreCase(NORMAL_SWITCH_TYPE) && !matched) { + event.setNewCurrent(0); + } else if (lines[0].equalsIgnoreCase(INVERTED_SWITCH_TYPE) && matched) { + event.setNewCurrent(0); + } + } + + private boolean isSignMatchDestination(String[] lines, String destination) { + for (String line : lines) { + if ("*".equals(line) || destination.equalsIgnoreCase(line)) { + return true; } } - // Determine the behaviour of the switch by its type - if (lines[0].equalsIgnoreCase(NORMAL_SWITCH_TYPE)) { - if (!matched) { - event.setNewCurrent(0); - } - } - else if (lines[0].equalsIgnoreCase(INVERTED_SWITCH_TYPE)) { - if (matched) { - event.setNewCurrent(0); - } - } - // Calculate how much time this process took - concludeTiming(); + return false; } private boolean isSameReinforcementGroup(Location a, Location b) { diff --git a/plugins/railswitch-paper/src/main/resources/plugin.yml b/plugins/railswitch-paper/src/main/resources/plugin.yml index 5435ac464..768dd356c 100644 --- a/plugins/railswitch-paper/src/main/resources/plugin.yml +++ b/plugins/railswitch-paper/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: RailSwitch -version: '1.2.1' +version: '1.2.2' main: sh.okx.railswitch.RailSwitch author: Okx api-version: '1.14' From 77f6df693ff22557a786cf72fdc15d3d8ed095a7 Mon Sep 17 00:00:00 2001 From: okx-code Date: Sat, 23 Nov 2019 18:17:19 +0000 Subject: [PATCH 19/78] add [!destination] to readme --- plugins/railswitch-paper/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/railswitch-paper/README.md b/plugins/railswitch-paper/README.md index 77fb122a5..5a4321fd4 100644 --- a/plugins/railswitch-paper/README.md +++ b/plugins/railswitch-paper/README.md @@ -9,6 +9,8 @@ 3. The second, third and fourth lines can be destination names. 4. When a player passes over the detector rail, it will only activate and emit redstone if a player set their destination as any of the three on the sign. +You may also use `[!destination]`, which will activate the rail if a player's destination is **not** on the signs. + ### Usage 1. Type `/dest ` From 582ea4e0fb48ecb3f90b1d857f2af7d908777041 Mon Sep 17 00:00:00 2001 From: okx-code Date: Sat, 23 Nov 2019 18:17:31 +0000 Subject: [PATCH 20/78] add [!destination] to readme From 651ffb107c02255c360cdf9300e5bc8f47260362 Mon Sep 17 00:00:00 2001 From: Amelorate Date: Thu, 12 Dec 2019 12:52:33 -0500 Subject: [PATCH 21/78] Add or operator with spaces You can use spaces as an "or" operator. So if you `/dest 0,0 icenia`, if the dest sign contains either `0,0` or `icenia` you will be routed towards that sign. This allows for a server-wide rail switch network without going absolutely insane with every station having to list every destination. --- .../listener/DetectorRailActivateListener.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java index 497cf4232..75d065daf 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java @@ -78,6 +78,15 @@ public class DetectorRailActivateListener implements Listener { if ("*".equals(line) || destination.equalsIgnoreCase(line)) { return true; } + + // Use spaces as an "or" operator, allowing for more advanced routing. + // We preserve the check above for backwards compatibility. + String[] destinations = destination.split(" "); + for (String dest : destinations) { + if (dest.equalsIgnoreCase(line)) { + return true; + } + } } return false; } From 5d8f020b63a3c594c0189f3cf7ca9e1e0684cb8b Mon Sep 17 00:00:00 2001 From: Amelorate Date: Thu, 12 Dec 2019 13:03:41 -0500 Subject: [PATCH 22/78] Fix 40 character check to be for each destination --- .../src/main/java/sh/okx/railswitch/RailSwitch.java | 12 ++++++++++-- .../sh/okx/railswitch/SetDestinationCommand.java | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java index 24a92435f..20df303e7 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java @@ -10,6 +10,8 @@ import sh.okx.railswitch.database.RailSwitchDatabase; import sh.okx.railswitch.database.SQLiteConnectionPool; import sh.okx.railswitch.listener.DetectorRailActivateListener; +import java.util.Arrays; + public class RailSwitch extends JavaPlugin { private boolean timings; private RailSwitchDatabase database; @@ -60,8 +62,14 @@ public class RailSwitch extends JavaPlugin { * make sure the message doesn't have any weirdness */ public boolean isValidDestination(String message) { - return message.length() <= 40 - && CharMatcher.inRange('0', '9') + // Each destination must be fewer than 40 characters. + for (String dest : message.split(" ")) { + if (dest.length() <= 40) { + return false; + } + } + + return CharMatcher.inRange('0', '9') .or(CharMatcher.inRange('a', 'z')) .or(CharMatcher.inRange('A', 'Z')) .or(CharMatcher.anyOf("!\"#$%&'()*+,-./;:<=>?@[]\\^_`{|}~ ")).matchesAllOf(message); diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java index 8d7d80edd..9688fbc3b 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java @@ -29,7 +29,7 @@ public class SetDestinationCommand implements CommandExecutor { String dest = String.join(" ", args); if (!plugin.isValidDestination(dest)) { - player.sendMessage(ChatColor.RED + "Destinations can not be more than 40 characters and may only use alphanumerical characters, ASCII symbols, and spaces."); + player.sendMessage(ChatColor.RED + "Destinations can each not be more than 40 characters and may only use alphanumerical characters, ASCII symbols, and spaces."); return true; } From d0bdeac517d4c643a806bdf7c3b8e42969c1c3b2 Mon Sep 17 00:00:00 2001 From: Amelorate Date: Thu, 12 Dec 2019 13:05:10 -0500 Subject: [PATCH 23/78] Document spaces feature in README.md --- plugins/railswitch-paper/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/railswitch-paper/README.md b/plugins/railswitch-paper/README.md index 5a4321fd4..3eb86ebeb 100644 --- a/plugins/railswitch-paper/README.md +++ b/plugins/railswitch-paper/README.md @@ -17,6 +17,10 @@ You may also use `[!destination]`, which will activate the rail if a player's de 2. Go AFK 3. Arrive +You can also use spaces in a `/dest` command as an "or" operator, like `/dest `. In this command, RailSwitch will route players towards signs that contain either `` or ``. + +Another way to think about it is that `/dest` takes multiple arguments. Each argument is a destination that will be checked as you pass a sign. + ### Example Video [![RailSwitch Demo Video](https://img.youtube.com/vi/GKku2fcB-wY/0.jpg)](https://www.youtube.com/watch?v=GKku2fcB-wY) From 04981365afbffbb1b771797f5573ba3f308dd9c7 Mon Sep 17 00:00:00 2001 From: Amelorate Date: Thu, 12 Dec 2019 13:09:57 -0500 Subject: [PATCH 24/78] Remove unneeded java.util.Arrays import I wrote a stream and used intellij to refactor it into a for loop. This worked good, but it didn't optimize out the import. --- .../src/main/java/sh/okx/railswitch/RailSwitch.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java index 20df303e7..5535b357d 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java @@ -10,8 +10,6 @@ import sh.okx.railswitch.database.RailSwitchDatabase; import sh.okx.railswitch.database.SQLiteConnectionPool; import sh.okx.railswitch.listener.DetectorRailActivateListener; -import java.util.Arrays; - public class RailSwitch extends JavaPlugin { private boolean timings; private RailSwitchDatabase database; From 2083c9c7aa70cc1105a2409041fd17f325a41aab Mon Sep 17 00:00:00 2001 From: Okx Date: Thu, 12 Dec 2019 19:09:39 +0000 Subject: [PATCH 25/78] bump version to 1.3 --- plugins/railswitch-paper/build.gradle | 2 +- plugins/railswitch-paper/src/main/resources/plugin.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/railswitch-paper/build.gradle b/plugins/railswitch-paper/build.gradle index 47e4f254d..e7a813d8a 100644 --- a/plugins/railswitch-paper/build.gradle +++ b/plugins/railswitch-paper/build.gradle @@ -4,7 +4,7 @@ plugins { } group 'sh.okx' -version '1.2.2' +version '1.3' sourceCompatibility = 1.8 diff --git a/plugins/railswitch-paper/src/main/resources/plugin.yml b/plugins/railswitch-paper/src/main/resources/plugin.yml index 768dd356c..171158b4a 100644 --- a/plugins/railswitch-paper/src/main/resources/plugin.yml +++ b/plugins/railswitch-paper/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: RailSwitch -version: '1.2.2' +version: '1.3' main: sh.okx.railswitch.RailSwitch author: Okx api-version: '1.14' From 082e3bcc92723024476d4071492fb5a158b349d4 Mon Sep 17 00:00:00 2001 From: Alexander <52572989+Protonull@users.noreply.github.com> Date: Sun, 12 Jan 2020 11:29:48 +0000 Subject: [PATCH 26/78] Update RailSwitch.java Bug fix --- .../src/main/java/sh/okx/railswitch/RailSwitch.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java index 5535b357d..a4eb2c581 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java @@ -62,7 +62,7 @@ public class RailSwitch extends JavaPlugin { public boolean isValidDestination(String message) { // Each destination must be fewer than 40 characters. for (String dest : message.split(" ")) { - if (dest.length() <= 40) { + if (dest.length() > 40) { return false; } } From e4e35dfb3faf5071b1a97ad5b87f53f06fb36dd2 Mon Sep 17 00:00:00 2001 From: Okx Date: Wed, 15 Jan 2020 21:36:21 +0000 Subject: [PATCH 27/78] bump version --- plugins/railswitch-paper/build.gradle | 2 +- plugins/railswitch-paper/src/main/resources/plugin.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/railswitch-paper/build.gradle b/plugins/railswitch-paper/build.gradle index e7a813d8a..3b32976ba 100644 --- a/plugins/railswitch-paper/build.gradle +++ b/plugins/railswitch-paper/build.gradle @@ -4,7 +4,7 @@ plugins { } group 'sh.okx' -version '1.3' +version '1.3.1' sourceCompatibility = 1.8 diff --git a/plugins/railswitch-paper/src/main/resources/plugin.yml b/plugins/railswitch-paper/src/main/resources/plugin.yml index 171158b4a..4a8b023c4 100644 --- a/plugins/railswitch-paper/src/main/resources/plugin.yml +++ b/plugins/railswitch-paper/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: RailSwitch -version: '1.3' +version: '1.3.1' main: sh.okx.railswitch.RailSwitch author: Okx api-version: '1.14' From d5a756c278669c7f72e92b17ccfca1db27d1dcd0 Mon Sep 17 00:00:00 2001 From: Maxopoly Date: Fri, 17 Jan 2020 23:46:32 +0100 Subject: [PATCH 28/78] Mavenify project --- .../.github/workflows/gradle.yml | 19 -- plugins/railswitch-paper/.gitignore | 214 +++++++++++++++++- plugins/railswitch-paper/build.gradle | 33 --- .../gradle/wrapper/gradle-wrapper.jar | Bin 56177 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 - plugins/railswitch-paper/gradlew | 172 -------------- plugins/railswitch-paper/gradlew.bat | 84 ------- plugins/railswitch-paper/pom.xml | 74 ++++++ plugins/railswitch-paper/settings.gradle | 2 - 9 files changed, 281 insertions(+), 323 deletions(-) delete mode 100644 plugins/railswitch-paper/.github/workflows/gradle.yml delete mode 100644 plugins/railswitch-paper/build.gradle delete mode 100644 plugins/railswitch-paper/gradle/wrapper/gradle-wrapper.jar delete mode 100644 plugins/railswitch-paper/gradle/wrapper/gradle-wrapper.properties delete mode 100644 plugins/railswitch-paper/gradlew delete mode 100644 plugins/railswitch-paper/gradlew.bat create mode 100644 plugins/railswitch-paper/pom.xml delete mode 100644 plugins/railswitch-paper/settings.gradle diff --git a/plugins/railswitch-paper/.github/workflows/gradle.yml b/plugins/railswitch-paper/.github/workflows/gradle.yml deleted file mode 100644 index efeb2a2e8..000000000 --- a/plugins/railswitch-paper/.github/workflows/gradle.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Java CI - -on: [push] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v1 - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 - with: - java-version: 1.8 - - name: Build with Gradle - run: | - chmod +x gradlew - ./gradlew build diff --git a/plugins/railswitch-paper/.gitignore b/plugins/railswitch-paper/.gitignore index 0f56cbe93..391808759 100644 --- a/plugins/railswitch-paper/.gitignore +++ b/plugins/railswitch-paper/.gitignore @@ -1,9 +1,209 @@ -.gradle -build/ -# Ignore Gradle GUI config -gradle-app.setting +# Created by https://www.gitignore.io/api/java,batch,linux,macos,maven,windows,intellij+all +# Edit at https://www.gitignore.io/?templates=java,batch,linux,macos,maven,windows,intellij+all -# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) -!gradle-wrapper.jar -\.idea \ No newline at end of file +### Batch ### +# BatchFiles +*.bat +*.cmd +*.btm + +### Intellij+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/modules.xml +# .idea/*.iml +# .idea/modules + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij+all Patch ### +# Ignores the whole .idea folder and all .iml files +# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 + +.idea/ + +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + +*.iml +modules.xml +.idea/misc.xml +*.ipr + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Maven ### +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.gitignore.io/api/java,batch,linux,macos,maven,windows,intellij+all + + +#Eclipse stuff +/target/ +.classpath +.settings/** +.project +/logs/ +dependency-reduced-pom.xml +/target/ + +/bin/ diff --git a/plugins/railswitch-paper/build.gradle b/plugins/railswitch-paper/build.gradle deleted file mode 100644 index 3b32976ba..000000000 --- a/plugins/railswitch-paper/build.gradle +++ /dev/null @@ -1,33 +0,0 @@ -plugins { - id 'java' - id 'com.github.johnrengelman.shadow' version '5.0.0' -} - -group 'sh.okx' -version '1.3.1' - -sourceCompatibility = 1.8 - -shadowJar { - classifier = null -} - -repositories { - mavenCentral() - mavenLocal() - maven { - url 'https://hub.spigotmc.org/nexus/content/groups/public/' - } - maven { - url 'http://build.devotedmc.com/plugin/repository/everything/' - } -} - -dependencies { - testCompile group: 'junit', name: 'junit', version: '4.12' - shadow 'vg.civcraft.mc.citadel:Citadel:3.9.0' - shadow 'vg.civcraft.mc.civmodcore:CivModCore:1.6.1' - shadow 'vg.civcraft.mc.namelayer:NameLayer:2.11.0' - shadow 'org.spigotmc:spigot-api:1.12.2-R0.1-SNAPSHOT' - compile 'com.zaxxer:HikariCP:3.3.1' -} diff --git a/plugins/railswitch-paper/gradle/wrapper/gradle-wrapper.jar b/plugins/railswitch-paper/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 94336fcae912db8a11d55634156fa011f4686124..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56177 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 i;x|p)^~-yfPOPa3(|vBayXKz \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - -exec "$JAVACMD" "$@" diff --git a/plugins/railswitch-paper/gradlew.bat b/plugins/railswitch-paper/gradlew.bat deleted file mode 100644 index f9553162f..000000000 --- a/plugins/railswitch-paper/gradlew.bat +++ /dev/null @@ -1,84 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/plugins/railswitch-paper/pom.xml b/plugins/railswitch-paper/pom.xml new file mode 100644 index 000000000..570914c22 --- /dev/null +++ b/plugins/railswitch-paper/pom.xml @@ -0,0 +1,74 @@ + + 4.0.0 + sh.okx + RailSwitch + jar + 1.3.2 + RailSwitch + https://github.com/civclassic/railswitch + + + 1.8 + 1.8 + UTF-8 + UTF-8 + + + + + + ${basedir}/src/main/resources + + *.yml + + true + + + ${basedir} + + LICENSE.txt + + + + + + + + org.spigotmc + spigot-api + 1.12.2-R0.1-SNAPSHOT + provided + + + vg.civcraft.mc.namelayer + NameLayer + 2.11.0 + provided + + + vg.civcraft.mc.citadel + Citadel + 3.9.0 + provided + + + vg.civcraft.mc.civmodcore + CivModCore + 1.6.1 + provided + + + + + + devoted-repo + http://build.devotedmc.com/plugin/repository/everything/ + + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + diff --git a/plugins/railswitch-paper/settings.gradle b/plugins/railswitch-paper/settings.gradle deleted file mode 100644 index f33bf5dcf..000000000 --- a/plugins/railswitch-paper/settings.gradle +++ /dev/null @@ -1,2 +0,0 @@ -rootProject.name = 'RailSwitch' - From dba62c7fc9598f64917592fc8c05fa045ec5eb4d Mon Sep 17 00:00:00 2001 From: Maxopoly Date: Fri, 17 Jan 2020 23:51:20 +0100 Subject: [PATCH 29/78] Upgrade to 1.14 --- plugins/railswitch-paper/pom.xml | 10 +++++----- .../listener/DetectorRailActivateListener.java | 14 +++----------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/plugins/railswitch-paper/pom.xml b/plugins/railswitch-paper/pom.xml index 570914c22..ebc7d0c47 100644 --- a/plugins/railswitch-paper/pom.xml +++ b/plugins/railswitch-paper/pom.xml @@ -5,7 +5,7 @@ sh.okx RailSwitch jar - 1.3.2 + 1.4.0 RailSwitch https://github.com/civclassic/railswitch @@ -38,25 +38,25 @@ org.spigotmc spigot-api - 1.12.2-R0.1-SNAPSHOT + 1.14.4-R0.1-SNAPSHOT provided vg.civcraft.mc.namelayer NameLayer - 2.11.0 + 2.13.0 provided vg.civcraft.mc.citadel Citadel - 3.9.0 + 4.0.1 provided vg.civcraft.mc.civmodcore CivModCore - 1.6.1 + 1.7.7 provided diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java index 75d065daf..3b9ebad9e 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java @@ -16,8 +16,7 @@ import org.bukkit.event.block.BlockRedstoneEvent; import sh.okx.railswitch.RailSwitch; import vg.civcraft.mc.citadel.Citadel; import vg.civcraft.mc.citadel.ReinforcementManager; -import vg.civcraft.mc.citadel.reinforcement.PlayerReinforcement; -import vg.civcraft.mc.citadel.reinforcement.Reinforcement; +import vg.civcraft.mc.citadel.model.Reinforcement; import java.util.Optional; @@ -92,17 +91,10 @@ public class DetectorRailActivateListener implements Listener { } private boolean isSameReinforcementGroup(Location a, Location b) { - ReinforcementManager rein = Citadel.getReinforcementManager(); + ReinforcementManager rein = Citadel.getInstance().getReinforcementManager(); Reinforcement reinA = rein.getReinforcement(a); Reinforcement reinB = rein.getReinforcement(b); - if (!(reinA instanceof PlayerReinforcement) || !(reinB instanceof PlayerReinforcement)) { - return false; - } - - PlayerReinforcement playerReinA = (PlayerReinforcement) reinA; - PlayerReinforcement playerReinB = (PlayerReinforcement) reinB; - - return playerReinA.getGroup().equals(playerReinB.getGroup()); + return reinA.getGroup().equals(reinB.getGroup()); } /** From a68606f0d91294cb87877a61342b790f185e9bcf Mon Sep 17 00:00:00 2001 From: Alexander <52572989+Protonull@users.noreply.github.com> Date: Thu, 23 Jan 2020 22:06:49 +0000 Subject: [PATCH 30/78] Added and applied .editorconfig --- plugins/railswitch-paper/.editorconfig | 16 ++ plugins/railswitch-paper/pom.xml | 136 ++++++------ .../java/sh/okx/railswitch/RailSwitch.java | 124 +++++------ .../okx/railswitch/SetDestinationCommand.java | 64 +++--- .../database/AbstractConnectionPool.java | 26 +-- .../railswitch/database/ConnectionPool.java | 2 +- .../database/MySQLConnectionPool.java | 26 +-- .../database/RailSwitchDatabase.java | 190 ++++++++--------- .../database/SQLiteConnectionPool.java | 20 +- .../DetectorRailActivateListener.java | 196 +++++++++--------- 10 files changed, 408 insertions(+), 392 deletions(-) create mode 100644 plugins/railswitch-paper/.editorconfig diff --git a/plugins/railswitch-paper/.editorconfig b/plugins/railswitch-paper/.editorconfig new file mode 100644 index 000000000..c6bb9baec --- /dev/null +++ b/plugins/railswitch-paper/.editorconfig @@ -0,0 +1,16 @@ +# Editorconfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +end_of_line = crlf +insert_final_newline = true +continuation_indent_size = 8 + +[{*.xml, *.yml}] +indent_size = 2 diff --git a/plugins/railswitch-paper/pom.xml b/plugins/railswitch-paper/pom.xml index 570914c22..534f5d68a 100644 --- a/plugins/railswitch-paper/pom.xml +++ b/plugins/railswitch-paper/pom.xml @@ -1,74 +1,74 @@ - 4.0.0 - sh.okx - RailSwitch - jar - 1.3.2 - RailSwitch - https://github.com/civclassic/railswitch + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 + http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + sh.okx + RailSwitch + jar + 1.3.2 + RailSwitch + https://github.com/civclassic/railswitch - - 1.8 - 1.8 - UTF-8 - UTF-8 - + + 1.8 + 1.8 + UTF-8 + UTF-8 + - - - - ${basedir}/src/main/resources - - *.yml - - true - - - ${basedir} - - LICENSE.txt - - - - + + + + ${basedir}/src/main/resources + + *.yml + + true + + + ${basedir} + + LICENSE.txt + + + + - - - org.spigotmc - spigot-api - 1.12.2-R0.1-SNAPSHOT - provided - - - vg.civcraft.mc.namelayer - NameLayer - 2.11.0 - provided - - - vg.civcraft.mc.citadel - Citadel - 3.9.0 - provided - - - vg.civcraft.mc.civmodcore - CivModCore - 1.6.1 - provided - - + + + org.spigotmc + spigot-api + 1.12.2-R0.1-SNAPSHOT + provided + + + vg.civcraft.mc.namelayer + NameLayer + 2.11.0 + provided + + + vg.civcraft.mc.citadel + Citadel + 3.9.0 + provided + + + vg.civcraft.mc.civmodcore + CivModCore + 1.6.1 + provided + + - - - devoted-repo - http://build.devotedmc.com/plugin/repository/everything/ - - - spigot-repo - https://hub.spigotmc.org/nexus/content/repositories/snapshots/ - - + + + devoted-repo + http://build.devotedmc.com/plugin/repository/everything/ + + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java index a4eb2c581..cca0954b3 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java @@ -11,69 +11,69 @@ import sh.okx.railswitch.database.SQLiteConnectionPool; import sh.okx.railswitch.listener.DetectorRailActivateListener; public class RailSwitch extends JavaPlugin { - private boolean timings; - private RailSwitchDatabase database; - - @Override - public void onEnable() { - super.onEnable(); - saveDefaultConfig(); - loadDatabase(); - - if (getConfig().getBoolean("timings")) { - timings = true; + private boolean timings; + private RailSwitchDatabase database; + + @Override + public void onEnable() { + super.onEnable(); + saveDefaultConfig(); + loadDatabase(); + + if (getConfig().getBoolean("timings")) { + timings = true; + } + + PluginManager pm = getServer().getPluginManager(); + + pm.registerEvents(new DetectorRailActivateListener(this), this); + getCommand("setdestination").setExecutor(new SetDestinationCommand(this)); } - - PluginManager pm = getServer().getPluginManager(); - - pm.registerEvents(new DetectorRailActivateListener(this), this); - getCommand("setdestination").setExecutor(new SetDestinationCommand(this)); - } - - public RailSwitchDatabase getDatabase() { - return database; - } - - private void loadDatabase() { - ConfigurationSection config = getConfig(); - - ConnectionPool pool; - String type = config.getString("database-type"); - if (type.equalsIgnoreCase("mysql")) { - String username = config.getString("mysql.username"); - String host = config.getString("mysql.host"); - String password = config.getString("mysql.password"); - String database = config.getString("mysql.database"); - int port = config.getInt("mysql.port"); - - pool = new MySQLConnectionPool(host, port, database, username, password); - } else if (type.equalsIgnoreCase("sqlite")) { - pool = new SQLiteConnectionPool(getDataFolder(), config.getString("sqlite.file-name")); - } else { - throw new RuntimeException("Invalid database-type in config.yml. Disabling plugin."); + + public RailSwitchDatabase getDatabase() { + return database; } - - this.database = new RailSwitchDatabase(pool, getLogger()); - } - - /** - * make sure the message doesn't have any weirdness - */ - public boolean isValidDestination(String message) { - // Each destination must be fewer than 40 characters. - for (String dest : message.split(" ")) { - if (dest.length() > 40) { - return false; - } + + private void loadDatabase() { + ConfigurationSection config = getConfig(); + + ConnectionPool pool; + String type = config.getString("database-type"); + if (type.equalsIgnoreCase("mysql")) { + String username = config.getString("mysql.username"); + String host = config.getString("mysql.host"); + String password = config.getString("mysql.password"); + String database = config.getString("mysql.database"); + int port = config.getInt("mysql.port"); + + pool = new MySQLConnectionPool(host, port, database, username, password); + } else if (type.equalsIgnoreCase("sqlite")) { + pool = new SQLiteConnectionPool(getDataFolder(), config.getString("sqlite.file-name")); + } else { + throw new RuntimeException("Invalid database-type in config.yml. Disabling plugin."); + } + + this.database = new RailSwitchDatabase(pool, getLogger()); + } + + /** + * make sure the message doesn't have any weirdness + */ + public boolean isValidDestination(String message) { + // Each destination must be fewer than 40 characters. + for (String dest : message.split(" ")) { + if (dest.length() > 40) { + return false; + } + } + + return CharMatcher.inRange('0', '9') + .or(CharMatcher.inRange('a', 'z')) + .or(CharMatcher.inRange('A', 'Z')) + .or(CharMatcher.anyOf("!\"#$%&'()*+,-./;:<=>?@[]\\^_`{|}~ ")).matchesAllOf(message); + } + + public boolean isTimings() { + return timings; } - - return CharMatcher.inRange('0', '9') - .or(CharMatcher.inRange('a', 'z')) - .or(CharMatcher.inRange('A', 'Z')) - .or(CharMatcher.anyOf("!\"#$%&'()*+,-./;:<=>?@[]\\^_`{|}~ ")).matchesAllOf(message); - } - - public boolean isTimings() { - return timings; - } } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java index 9688fbc3b..3ddb478d6 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java @@ -7,38 +7,38 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; public class SetDestinationCommand implements CommandExecutor { - private final RailSwitch plugin; - - public SetDestinationCommand(RailSwitch plugin) { - this.plugin = plugin; - } - - @Override - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - if (!(sender instanceof Player)) { - return false; + private final RailSwitch plugin; + + public SetDestinationCommand(RailSwitch plugin) { + this.plugin = plugin; } - - - Player player = (Player) sender; - if (args.length < 1) { - player.sendMessage(ChatColor.GREEN + "Unset rail destination (use /" + label + " to set your destination)."); - plugin.getDatabase().removePlayerDestination(player); - return true; + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player)) { + return false; + } + + + Player player = (Player) sender; + if (args.length < 1) { + player.sendMessage(ChatColor.GREEN + "Unset rail destination (use /" + label + " to set your destination)."); + plugin.getDatabase().removePlayerDestination(player); + return true; + } + + String dest = String.join(" ", args); + if (!plugin.isValidDestination(dest)) { + player.sendMessage(ChatColor.RED + "Destinations can each not be more than 40 characters and may only use alphanumerical characters, ASCII symbols, and spaces."); + return true; + } + + long start = System.currentTimeMillis(); + plugin.getDatabase().setPlayerDestination(player, dest); + if (plugin.isTimings()) { + plugin.getLogger().info("Set destination took " + (System.currentTimeMillis() - start) + "ms"); + } + player.sendMessage(ChatColor.GREEN + "Set your rail destination to: " + dest); + return true; } - - String dest = String.join(" ", args); - if (!plugin.isValidDestination(dest)) { - player.sendMessage(ChatColor.RED + "Destinations can each not be more than 40 characters and may only use alphanumerical characters, ASCII symbols, and spaces."); - return true; - } - - long start = System.currentTimeMillis(); - plugin.getDatabase().setPlayerDestination(player, dest); - if (plugin.isTimings()) { - plugin.getLogger().info("Set destination took " + (System.currentTimeMillis() - start) + "ms"); - } - player.sendMessage(ChatColor.GREEN + "Set your rail destination to: " + dest); - return true; - } } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/AbstractConnectionPool.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/AbstractConnectionPool.java index 6c18a6ce3..65c3671de 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/AbstractConnectionPool.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/AbstractConnectionPool.java @@ -1,17 +1,17 @@ package sh.okx.railswitch.database; public abstract class AbstractConnectionPool implements ConnectionPool { - protected final String host; - protected final int port; - protected final String database; - protected final String user; - protected final String password; - - protected AbstractConnectionPool(String host, int port, String database, String user, String password) { - this.host = host; - this.port = port; - this.database = database; - this.user = user; - this.password = password; - } + protected final String host; + protected final int port; + protected final String database; + protected final String user; + protected final String password; + + protected AbstractConnectionPool(String host, int port, String database, String user, String password) { + this.host = host; + this.port = port; + this.database = database; + this.user = user; + this.password = password; + } } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/ConnectionPool.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/ConnectionPool.java index 9f2a2015e..f4f4368cd 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/ConnectionPool.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/ConnectionPool.java @@ -4,5 +4,5 @@ import java.sql.Connection; import java.sql.SQLException; public interface ConnectionPool { - Connection getConnection() throws SQLException; + Connection getConnection() throws SQLException; } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/MySQLConnectionPool.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/MySQLConnectionPool.java index 6faca1ee2..65cc14d25 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/MySQLConnectionPool.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/MySQLConnectionPool.java @@ -6,17 +6,17 @@ import java.sql.Connection; import java.sql.SQLException; public class MySQLConnectionPool implements ConnectionPool { - private final HikariDataSource hikariDataSource; - - public MySQLConnectionPool(String host, int port, String database, String user, String password) { - hikariDataSource = new HikariDataSource(); - hikariDataSource.setJdbcUrl("jdbc:mysql://" + host + ":" + port + "/" + database); - hikariDataSource.setUsername(user); - hikariDataSource.setPassword(password); - } - - @Override - public Connection getConnection() throws SQLException { - return hikariDataSource.getConnection(); - } + private final HikariDataSource hikariDataSource; + + public MySQLConnectionPool(String host, int port, String database, String user, String password) { + hikariDataSource = new HikariDataSource(); + hikariDataSource.setJdbcUrl("jdbc:mysql://" + host + ":" + port + "/" + database); + hikariDataSource.setUsername(user); + hikariDataSource.setPassword(password); + } + + @Override + public Connection getConnection() throws SQLException { + return hikariDataSource.getConnection(); + } } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java index d0598ba44..b98bf169e 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java @@ -17,102 +17,102 @@ import java.util.logging.Level; import java.util.logging.Logger; public class RailSwitchDatabase { - private static final String DESTINATION_TABLE = "rsw_dest"; - - private ConnectionPool pool; - private Logger log; - - private String setPlayerDestination; - private String getPlayerDestination; - private String removePlayerDestination; - private ExecutorService service = Executors.newSingleThreadExecutor(); - - private final LoadingCache> destinations = CacheBuilder.newBuilder() - .maximumSize(1000) - .build(new CacheLoader>() { - @Override - public Optional load(Player key) throws Exception { - try (Connection connection = pool.getConnection()) { - PreparedStatement statement = connection.prepareStatement(getPlayerDestination); - statement.setString(1, key.getUniqueId().toString()); - - ResultSet resultSet = statement.executeQuery(); - if (resultSet.next()) { - return Optional.of(resultSet.getString("dest")); - } else { - return Optional.empty(); - } - } + private static final String DESTINATION_TABLE = "rsw_dest"; + + private ConnectionPool pool; + private Logger log; + + private String setPlayerDestination; + private String getPlayerDestination; + private String removePlayerDestination; + private ExecutorService service = Executors.newSingleThreadExecutor(); + + private final LoadingCache> destinations = CacheBuilder.newBuilder() + .maximumSize(1000) + .build(new CacheLoader>() { + @Override + public Optional load(Player key) throws Exception { + try (Connection connection = pool.getConnection()) { + PreparedStatement statement = connection.prepareStatement(getPlayerDestination); + statement.setString(1, key.getUniqueId().toString()); + + ResultSet resultSet = statement.executeQuery(); + if (resultSet.next()) { + return Optional.of(resultSet.getString("dest")); + } else { + return Optional.empty(); + } + } + } + }); + + public RailSwitchDatabase(ConnectionPool pool, Logger logger) { + log = logger; + this.pool = pool; + + createTable(); + loadStatements(); + } + + private void createTable() { + try (Connection connection = pool.getConnection()) { + connection.createStatement().executeUpdate( + "CREATE TABLE IF NOT EXISTS `" + DESTINATION_TABLE + "` (" + + "`uuid` VARCHAR(36) NOT NULL," + + "`dest` VARCHAR(40) NOT NULL," + + "PRIMARY KEY (`uuid`))"); + } catch (SQLException e) { + log.log(Level.SEVERE, "Could not create tables", e); } - }); - - public RailSwitchDatabase(ConnectionPool pool, Logger logger) { - log = logger; - this.pool = pool; - - createTable(); - loadStatements(); - } - - private void createTable() { - try (Connection connection = pool.getConnection()) { - connection.createStatement().executeUpdate( - "CREATE TABLE IF NOT EXISTS `" + DESTINATION_TABLE + "` (" + - "`uuid` VARCHAR(36) NOT NULL," + - "`dest` VARCHAR(40) NOT NULL," + - "PRIMARY KEY (`uuid`))"); - } catch (SQLException e) { - log.log(Level.SEVERE, "Could not create tables", e); } - } - - private void loadStatements() { - setPlayerDestination = String.format( - "REPLACE INTO `%1$s` VALUES (?, ?)", - DESTINATION_TABLE); - getPlayerDestination = String.format( - "SELECT `dest` FROM `%1$s` WHERE `uuid`=?", - DESTINATION_TABLE); - removePlayerDestination = String.format( - "DELETE FROM `%1$s` WHERE `uuid`=?", - DESTINATION_TABLE); - } - - public void setPlayerDestination(Player player, String destination) { - destinations.put(player, Optional.of(destination)); - service.execute(() -> { - try (Connection connection = pool.getConnection()) { - PreparedStatement statement = connection.prepareStatement(setPlayerDestination); - statement.setString(1, player.getUniqueId().toString()); - statement.setString(2, destination); - - statement.executeUpdate(); - } catch (SQLException e) { - log.log(Level.SEVERE, "Could not set destination", e); - } - }); - } - - public void removePlayerDestination(Player player) { - destinations.put(player, Optional.empty()); - service.execute(() -> { - try (Connection connection = pool.getConnection()) { - PreparedStatement statement = connection.prepareStatement(removePlayerDestination); - statement.setString(1, player.getUniqueId().toString()); - - statement.executeUpdate(); - } catch (SQLException e) { - log.log(Level.SEVERE, "Could not remove destination", e); - } - }); - } - - public Optional getPlayerDestination(Player player) { - try { - return destinations.get(player); - } catch (ExecutionException e) { - log.log(Level.SEVERE, "Could not get destination", e); - return Optional.empty(); + + private void loadStatements() { + setPlayerDestination = String.format( + "REPLACE INTO `%1$s` VALUES (?, ?)", + DESTINATION_TABLE); + getPlayerDestination = String.format( + "SELECT `dest` FROM `%1$s` WHERE `uuid`=?", + DESTINATION_TABLE); + removePlayerDestination = String.format( + "DELETE FROM `%1$s` WHERE `uuid`=?", + DESTINATION_TABLE); + } + + public void setPlayerDestination(Player player, String destination) { + destinations.put(player, Optional.of(destination)); + service.execute(() -> { + try (Connection connection = pool.getConnection()) { + PreparedStatement statement = connection.prepareStatement(setPlayerDestination); + statement.setString(1, player.getUniqueId().toString()); + statement.setString(2, destination); + + statement.executeUpdate(); + } catch (SQLException e) { + log.log(Level.SEVERE, "Could not set destination", e); + } + }); + } + + public void removePlayerDestination(Player player) { + destinations.put(player, Optional.empty()); + service.execute(() -> { + try (Connection connection = pool.getConnection()) { + PreparedStatement statement = connection.prepareStatement(removePlayerDestination); + statement.setString(1, player.getUniqueId().toString()); + + statement.executeUpdate(); + } catch (SQLException e) { + log.log(Level.SEVERE, "Could not remove destination", e); + } + }); + } + + public Optional getPlayerDestination(Player player) { + try { + return destinations.get(player); + } catch (ExecutionException e) { + log.log(Level.SEVERE, "Could not get destination", e); + return Optional.empty(); + } } - } } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/SQLiteConnectionPool.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/SQLiteConnectionPool.java index ac5b76bab..eb98796e2 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/SQLiteConnectionPool.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/SQLiteConnectionPool.java @@ -7,14 +7,14 @@ import java.sql.DriverManager; import java.sql.SQLException; public class SQLiteConnectionPool implements ConnectionPool { - private Path file; - - public SQLiteConnectionPool(File dataFolder, String file) { - this.file = dataFolder.toPath().resolve(file); - } - - @Override - public Connection getConnection() throws SQLException { - return DriverManager.getConnection("jdbc:sqlite:" + file); - } + private Path file; + + public SQLiteConnectionPool(File dataFolder, String file) { + this.file = dataFolder.toPath().resolve(file); + } + + @Override + public Connection getConnection() throws SQLException { + return DriverManager.getConnection("jdbc:sqlite:" + file); + } } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java index 75d065daf..67c21af1c 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java @@ -22,106 +22,106 @@ import vg.civcraft.mc.citadel.reinforcement.Reinforcement; import java.util.Optional; public class DetectorRailActivateListener implements Listener { - - private static final String NORMAL_SWITCH_TYPE = "[destination]"; - private static final String INVERTED_SWITCH_TYPE = "[!destination]"; - - private final RailSwitch plugin; - - public DetectorRailActivateListener(RailSwitch plugin) { - this.plugin = plugin; - } - - @EventHandler - public void onRailSwitch(BlockRedstoneEvent event) { - Block block = event.getBlock(); - if (block == null - || block.getType() != Material.DETECTOR_RAIL - || event.getNewCurrent() != 15) { - return; + + private static final String NORMAL_SWITCH_TYPE = "[destination]"; + private static final String INVERTED_SWITCH_TYPE = "[!destination]"; + + private final RailSwitch plugin; + + public DetectorRailActivateListener(RailSwitch plugin) { + this.plugin = plugin; } - - Block above = block.getRelative(BlockFace.UP); - if (!(above.getState() instanceof Sign)) { - return; - } - - String[] lines = ((Sign) above.getState()).getLines(); - if (!(lines[0].equalsIgnoreCase(NORMAL_SWITCH_TYPE) - || lines[0].equalsIgnoreCase(INVERTED_SWITCH_TYPE))) { - return; - } - - if (Bukkit.getPluginManager().isPluginEnabled("Citadel") - && !isSameReinforcementGroup(block.getLocation(), above.getLocation())) { - return; - } - - Player player = findNearestPlayerInMinecart(block.getLocation()); - if (player == null) { - return; - } - - Optional destination = plugin.getDatabase().getPlayerDestination(player); - - boolean matched = destination.map(d -> isSignMatchDestination(lines, d)).orElse(false); - - if (lines[0].equalsIgnoreCase(NORMAL_SWITCH_TYPE) && !matched) { - event.setNewCurrent(0); - } else if (lines[0].equalsIgnoreCase(INVERTED_SWITCH_TYPE) && matched) { - event.setNewCurrent(0); - } - } - - private boolean isSignMatchDestination(String[] lines, String destination) { - for (String line : lines) { - if ("*".equals(line) || destination.equalsIgnoreCase(line)) { - return true; - } - - // Use spaces as an "or" operator, allowing for more advanced routing. - // We preserve the check above for backwards compatibility. - String[] destinations = destination.split(" "); - for (String dest : destinations) { - if (dest.equalsIgnoreCase(line)) { - return true; + + @EventHandler + public void onRailSwitch(BlockRedstoneEvent event) { + Block block = event.getBlock(); + if (block == null + || block.getType() != Material.DETECTOR_RAIL + || event.getNewCurrent() != 15) { + return; } - } - } - return false; - } - - private boolean isSameReinforcementGroup(Location a, Location b) { - ReinforcementManager rein = Citadel.getReinforcementManager(); - Reinforcement reinA = rein.getReinforcement(a); - Reinforcement reinB = rein.getReinforcement(b); - if (!(reinA instanceof PlayerReinforcement) || !(reinB instanceof PlayerReinforcement)) { - return false; - } - - PlayerReinforcement playerReinA = (PlayerReinforcement) reinA; - PlayerReinforcement playerReinB = (PlayerReinforcement) reinB; - - return playerReinA.getGroup().equals(playerReinB.getGroup()); - } - - /** - * @param location the centre - * @return the nearest player riding a minecart in a 3 block radius of the centre - */ - private Player findNearestPlayerInMinecart(Location location) { - World world = location.getWorld(); - Player nearestPlayer = null; - double distance = Double.MAX_VALUE; - for (Entity entity : world.getNearbyEntities(location, 3, 3, 3)) { - if (entity instanceof Player && entity.getVehicle() instanceof Minecart) { - double entityDistance = location.distanceSquared(entity.getLocation()); - if (entityDistance < distance) { - distance = entityDistance; - nearestPlayer = (Player) entity; + + Block above = block.getRelative(BlockFace.UP); + if (!(above.getState() instanceof Sign)) { + return; + } + + String[] lines = ((Sign) above.getState()).getLines(); + if (!(lines[0].equalsIgnoreCase(NORMAL_SWITCH_TYPE) + || lines[0].equalsIgnoreCase(INVERTED_SWITCH_TYPE))) { + return; + } + + if (Bukkit.getPluginManager().isPluginEnabled("Citadel") + && !isSameReinforcementGroup(block.getLocation(), above.getLocation())) { + return; + } + + Player player = findNearestPlayerInMinecart(block.getLocation()); + if (player == null) { + return; + } + + Optional destination = plugin.getDatabase().getPlayerDestination(player); + + boolean matched = destination.map(d -> isSignMatchDestination(lines, d)).orElse(false); + + if (lines[0].equalsIgnoreCase(NORMAL_SWITCH_TYPE) && !matched) { + event.setNewCurrent(0); + } else if (lines[0].equalsIgnoreCase(INVERTED_SWITCH_TYPE) && matched) { + event.setNewCurrent(0); } - } } - return nearestPlayer; - } + + private boolean isSignMatchDestination(String[] lines, String destination) { + for (String line : lines) { + if ("*".equals(line) || destination.equalsIgnoreCase(line)) { + return true; + } + + // Use spaces as an "or" operator, allowing for more advanced routing. + // We preserve the check above for backwards compatibility. + String[] destinations = destination.split(" "); + for (String dest : destinations) { + if (dest.equalsIgnoreCase(line)) { + return true; + } + } + } + return false; + } + + private boolean isSameReinforcementGroup(Location a, Location b) { + ReinforcementManager rein = Citadel.getReinforcementManager(); + Reinforcement reinA = rein.getReinforcement(a); + Reinforcement reinB = rein.getReinforcement(b); + if (!(reinA instanceof PlayerReinforcement) || !(reinB instanceof PlayerReinforcement)) { + return false; + } + + PlayerReinforcement playerReinA = (PlayerReinforcement) reinA; + PlayerReinforcement playerReinB = (PlayerReinforcement) reinB; + + return playerReinA.getGroup().equals(playerReinB.getGroup()); + } + + /** + * @param location the centre + * @return the nearest player riding a minecart in a 3 block radius of the centre + */ + private Player findNearestPlayerInMinecart(Location location) { + World world = location.getWorld(); + Player nearestPlayer = null; + double distance = Double.MAX_VALUE; + for (Entity entity : world.getNearbyEntities(location, 3, 3, 3)) { + if (entity instanceof Player && entity.getVehicle() instanceof Minecart) { + double entityDistance = location.distanceSquared(entity.getLocation()); + if (entityDistance < distance) { + distance = entityDistance; + nearestPlayer = (Player) entity; + } + } + } + return nearestPlayer; + } } From c79bd0d8aa6d99e0e71c19535a9a473781bfadc9 Mon Sep 17 00:00:00 2001 From: Alexander <52572989+Protonull@users.noreply.github.com> Date: Sat, 25 Jan 2020 02:08:23 +0000 Subject: [PATCH 31/78] Re-organised and updated the POM Now has the most up to date dependencies for 1.12.2, and now includes Devoted's style checker --- plugins/railswitch-paper/pom.xml | 47 ++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/plugins/railswitch-paper/pom.xml b/plugins/railswitch-paper/pom.xml index 534f5d68a..9bacbc7a1 100644 --- a/plugins/railswitch-paper/pom.xml +++ b/plugins/railswitch-paper/pom.xml @@ -6,6 +6,7 @@ RailSwitch jar 1.3.2 + RailSwitch https://github.com/civclassic/railswitch @@ -32,6 +33,28 @@ + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.1.0 + + + https://build.devotedmc.com/job/Style-guide-master/lastSuccessfulBuild/artifact/src/main/resources/devoted_checks.xml + + true + + + + checkstyle + + checkstyle + + prepare-package + + + + @@ -41,34 +64,34 @@ 1.12.2-R0.1-SNAPSHOT provided + + vg.civcraft.mc.civmodcore + CivModCore + 1.6.2 + provided + vg.civcraft.mc.namelayer NameLayer - 2.11.0 + 2.11.2 provided vg.civcraft.mc.citadel Citadel - 3.9.0 - provided - - - vg.civcraft.mc.civmodcore - CivModCore - 1.6.1 + 3.11.4 provided - - devoted-repo - http://build.devotedmc.com/plugin/repository/everything/ - spigot-repo https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + devoted-repo + http://build.devotedmc.com/plugin/repository/everything/ + From 48611353e7025f1cb0f1d1800a8093c98e69672c Mon Sep 17 00:00:00 2001 From: Alexander <52572989+Protonull@users.noreply.github.com> Date: Sat, 25 Jan 2020 02:09:57 +0000 Subject: [PATCH 32/78] Cleaned up RailSwitch --- .../java/sh/okx/railswitch/RailSwitch.java | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java index cca0954b3..3d80868bb 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java @@ -2,7 +2,7 @@ package sh.okx.railswitch; import com.google.common.base.CharMatcher; import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.plugin.PluginManager; +import org.bukkit.event.HandlerList; import org.bukkit.plugin.java.JavaPlugin; import sh.okx.railswitch.database.ConnectionPool; import sh.okx.railswitch.database.MySQLConnectionPool; @@ -11,6 +11,7 @@ import sh.okx.railswitch.database.SQLiteConnectionPool; import sh.okx.railswitch.listener.DetectorRailActivateListener; public class RailSwitch extends JavaPlugin { + private boolean timings; private RailSwitchDatabase database; @@ -19,40 +20,41 @@ public class RailSwitch extends JavaPlugin { super.onEnable(); saveDefaultConfig(); loadDatabase(); - - if (getConfig().getBoolean("timings")) { - timings = true; - } - - PluginManager pm = getServer().getPluginManager(); - - pm.registerEvents(new DetectorRailActivateListener(this), this); + this.timings = getConfig().getBoolean("timings", false); + getServer().getPluginManager().registerEvents(new DetectorRailActivateListener(this), this); getCommand("setdestination").setExecutor(new SetDestinationCommand(this)); } + @Override + public void onDisable() { + super.onDisable(); + HandlerList.unregisterAll(this); + } + public RailSwitchDatabase getDatabase() { return database; } private void loadDatabase() { ConfigurationSection config = getConfig(); - ConnectionPool pool; String type = config.getString("database-type"); if (type.equalsIgnoreCase("mysql")) { - String username = config.getString("mysql.username"); - String host = config.getString("mysql.host"); - String password = config.getString("mysql.password"); - String database = config.getString("mysql.database"); - int port = config.getInt("mysql.port"); - - pool = new MySQLConnectionPool(host, port, database, username, password); - } else if (type.equalsIgnoreCase("sqlite")) { - pool = new SQLiteConnectionPool(getDataFolder(), config.getString("sqlite.file-name")); - } else { + pool = new MySQLConnectionPool( + config.getString("mysql.username"), + config.getInt("mysql.port"), + config.getString("mysql.database"), + config.getString("mysql.username"), + config.getString("mysql.password")); + } + else if (type.equalsIgnoreCase("sqlite")) { + pool = new SQLiteConnectionPool( + getDataFolder(), + config.getString("sqlite.file-name")); + } + else { throw new RuntimeException("Invalid database-type in config.yml. Disabling plugin."); } - this.database = new RailSwitchDatabase(pool, getLogger()); } @@ -76,4 +78,5 @@ public class RailSwitch extends JavaPlugin { public boolean isTimings() { return timings; } + } From 8a1fc998fad0700763bb2fe62ae031b712588e87 Mon Sep 17 00:00:00 2001 From: Alexander <52572989+Protonull@users.noreply.github.com> Date: Sat, 25 Jan 2020 02:15:00 +0000 Subject: [PATCH 33/78] Updated DetectorRailActivateListener The file has been updated and improved and flattened. While some of the comments might seem a little obvious, that you can reasonably easily deduce what's going on, it's in my opinion better to save future developers, especially newer developers the effort of having to parse code. The comments allow me to define what's happening but also *why* it's happening. --- .../java/sh/okx/railswitch/SwitchType.java | 26 +++ .../DetectorRailActivateListener.java | 154 ++++++++++-------- 2 files changed, 112 insertions(+), 68 deletions(-) create mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SwitchType.java diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SwitchType.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SwitchType.java new file mode 100644 index 000000000..d332be995 --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SwitchType.java @@ -0,0 +1,26 @@ +package sh.okx.railswitch; + +public enum SwitchType { + + NORMAL("[destination]"), + INVERTED("[!destination]"); + + private String tag; + + SwitchType(String tag) { + this.tag = tag; + } + + public static SwitchType find(String tag) { + if (tag == null || tag.isEmpty()) { + return null; + } + for (SwitchType type : values()) { + if (tag.equalsIgnoreCase(type.tag)) { + return type; + } + } + return null; + } + +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java index 67c21af1c..fdb79a47d 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java @@ -1,31 +1,29 @@ package sh.okx.railswitch.listener; import org.bukkit.Bukkit; -import org.bukkit.Location; import org.bukkit.Material; -import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.Sign; import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; import org.bukkit.entity.Minecart; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.block.BlockRedstoneEvent; import sh.okx.railswitch.RailSwitch; +import sh.okx.railswitch.SwitchType; import vg.civcraft.mc.citadel.Citadel; import vg.civcraft.mc.citadel.ReinforcementManager; import vg.civcraft.mc.citadel.reinforcement.PlayerReinforcement; import vg.civcraft.mc.citadel.reinforcement.Reinforcement; -import java.util.Optional; +import java.util.Arrays; +import java.util.Objects; public class DetectorRailActivateListener implements Listener { - private static final String NORMAL_SWITCH_TYPE = "[destination]"; - private static final String INVERTED_SWITCH_TYPE = "[!destination]"; - private final RailSwitch plugin; public DetectorRailActivateListener(RailSwitch plugin) { @@ -35,93 +33,113 @@ public class DetectorRailActivateListener implements Listener { @EventHandler public void onRailSwitch(BlockRedstoneEvent event) { Block block = event.getBlock(); + + // Block must be a detector rail being triggered if (block == null || block.getType() != Material.DETECTOR_RAIL || event.getNewCurrent() != 15) { return; } + // Check that the block above the rail is a sign Block above = block.getRelative(BlockFace.UP); - if (!(above.getState() instanceof Sign)) { + if (above == null + // Do not check for Material.SIGN as that is an item material, not a block material + || (above.getType() != Material.SIGN_POST && above.getType() != Material.WALL_SIGN) + || !(above.getState() instanceof Sign)) { return; } + // Check that the sign has a valid switch type String[] lines = ((Sign) above.getState()).getLines(); - if (!(lines[0].equalsIgnoreCase(NORMAL_SWITCH_TYPE) - || lines[0].equalsIgnoreCase(INVERTED_SWITCH_TYPE))) { + SwitchType type = SwitchType.find(lines[0]); + if (type == null) { return; } - if (Bukkit.getPluginManager().isPluginEnabled("Citadel") - && !isSameReinforcementGroup(block.getLocation(), above.getLocation())) { - return; + // If Citadel is enabled, check that the sign and the rail are on the same group + if (Bukkit.getPluginManager().isPluginEnabled("Citadel")) { + ReinforcementManager rein = Citadel.getReinforcementManager(); + Reinforcement railReinforcement = rein.getReinforcement(block); + Reinforcement signReinforcement = rein.getReinforcement(above); + // Allow the switch to work if the rail and the sign are BOTH unreinforced + // otherwise servers with established rail networks will suddenly not work + // if Citadel is added. + if (railReinforcement != null || signReinforcement != null) { + if (!(railReinforcement instanceof PlayerReinforcement)) { + return; + } + if (!(signReinforcement instanceof PlayerReinforcement)) { + return; + } + if (!Objects.equals( + ((PlayerReinforcement) railReinforcement).getGroup(), + ((PlayerReinforcement) signReinforcement).getGroup())) { + return; + } + } } - Player player = findNearestPlayerInMinecart(block.getLocation()); + // Check that a player is triggering the switch + // NOTE: The event doesn't provide the information and so the next best thing is searching for a + // player who is nearby and riding a minecart. + Player player = null; { + double searchDistance = Double.MAX_VALUE; + for (Entity entity : block.getWorld().getNearbyEntities(block.getLocation(), 3, 3, 3)) { + if (entity.getType() != EntityType.PLAYER + || !(entity instanceof Player)) { + continue; + } + Entity vehicle = entity.getVehicle(); + if (vehicle == null + || vehicle.getType() != EntityType.MINECART + || !(vehicle instanceof Minecart)) { + continue; + } + double distance = block.getLocation().distanceSquared(entity.getLocation()); + if (distance < searchDistance) { + searchDistance = distance; + player = (Player) entity; + } + } + } if (player == null) { return; } - Optional destination = plugin.getDatabase().getPlayerDestination(player); - - boolean matched = destination.map(d -> isSignMatchDestination(lines, d)).orElse(false); - - if (lines[0].equalsIgnoreCase(NORMAL_SWITCH_TYPE) && !matched) { - event.setNewCurrent(0); - } else if (lines[0].equalsIgnoreCase(INVERTED_SWITCH_TYPE) && matched) { - event.setNewCurrent(0); - } - } - - private boolean isSignMatchDestination(String[] lines, String destination) { - for (String line : lines) { - if ("*".equals(line) || destination.equalsIgnoreCase(line)) { - return true; - } - - // Use spaces as an "or" operator, allowing for more advanced routing. - // We preserve the check above for backwards compatibility. - String[] destinations = destination.split(" "); - for (String dest : destinations) { - if (dest.equalsIgnoreCase(line)) { - return true; + // Determine whether a player has a destination that matches one of the destinations + // listed on the switch signs, or match if there's a wildcard. + boolean matched = false; + String[] playerDestinations = plugin.getDatabase(). + getPlayerDestination(player). + map(dest -> dest.split(" ")). + orElse(null); + if (playerDestinations != null && playerDestinations.length > 0) { + String[] switchDestinations = Arrays.copyOfRange(lines, 1, lines.length); + matcher: for (String playerDestination : playerDestinations) { + if (playerDestination.equals("*")) { + matched = true; + break; + } + for (String switchDestination : switchDestinations) { + if (switchDestination.equals("*") + || playerDestination.equalsIgnoreCase(switchDestination)) { + matched = true; + break matcher; + } } } } - return false; - } - - private boolean isSameReinforcementGroup(Location a, Location b) { - ReinforcementManager rein = Citadel.getReinforcementManager(); - Reinforcement reinA = rein.getReinforcement(a); - Reinforcement reinB = rein.getReinforcement(b); - if (!(reinA instanceof PlayerReinforcement) || !(reinB instanceof PlayerReinforcement)) { - return false; + + switch (type) { + case NORMAL: + event.setNewCurrent(matched ? 15 : 0); + break; + case INVERTED: + event.setNewCurrent(matched ? 0 : 15); + break; } - PlayerReinforcement playerReinA = (PlayerReinforcement) reinA; - PlayerReinforcement playerReinB = (PlayerReinforcement) reinB; - - return playerReinA.getGroup().equals(playerReinB.getGroup()); } - /** - * @param location the centre - * @return the nearest player riding a minecart in a 3 block radius of the centre - */ - private Player findNearestPlayerInMinecart(Location location) { - World world = location.getWorld(); - Player nearestPlayer = null; - double distance = Double.MAX_VALUE; - for (Entity entity : world.getNearbyEntities(location, 3, 3, 3)) { - if (entity instanceof Player && entity.getVehicle() instanceof Minecart) { - double entityDistance = location.distanceSquared(entity.getLocation()); - if (entityDistance < distance) { - distance = entityDistance; - nearestPlayer = (Player) entity; - } - } - } - return nearestPlayer; - } } From f142f07fceb987a95be4e2f2e87b90d2447a264b Mon Sep 17 00:00:00 2001 From: Alexander <52572989+Protonull@users.noreply.github.com> Date: Sat, 25 Jan 2020 02:16:20 +0000 Subject: [PATCH 34/78] Updated SetDestinationCommand Improved, updated, and flattened the set command handler. --- .../java/sh/okx/railswitch/RailSwitch.java | 18 ------- .../okx/railswitch/SetDestinationCommand.java | 50 +++++++++++++++---- 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java index 3d80868bb..038ec8afa 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java @@ -1,6 +1,5 @@ package sh.okx.railswitch; -import com.google.common.base.CharMatcher; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.event.HandlerList; import org.bukkit.plugin.java.JavaPlugin; @@ -58,23 +57,6 @@ public class RailSwitch extends JavaPlugin { this.database = new RailSwitchDatabase(pool, getLogger()); } - /** - * make sure the message doesn't have any weirdness - */ - public boolean isValidDestination(String message) { - // Each destination must be fewer than 40 characters. - for (String dest : message.split(" ")) { - if (dest.length() > 40) { - return false; - } - } - - return CharMatcher.inRange('0', '9') - .or(CharMatcher.inRange('a', 'z')) - .or(CharMatcher.inRange('A', 'Z')) - .or(CharMatcher.anyOf("!\"#$%&'()*+,-./;:<=>?@[]\\^_`{|}~ ")).matchesAllOf(message); - } - public boolean isTimings() { return timings; } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java index 3ddb478d6..a54647f8e 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java @@ -1,12 +1,18 @@ package sh.okx.railswitch; +import com.google.common.base.CharMatcher; +import com.google.common.base.Strings; import org.bukkit.ChatColor; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import java.util.ArrayList; +import java.util.List; + public class SetDestinationCommand implements CommandExecutor { + private final RailSwitch plugin; public SetDestinationCommand(RailSwitch plugin) { @@ -14,31 +20,53 @@ public class SetDestinationCommand implements CommandExecutor { } @Override - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + public boolean onCommand(CommandSender sender, Command command, String label, String[] arguments) { if (!(sender instanceof Player)) { return false; } - Player player = (Player) sender; - if (args.length < 1) { - player.sendMessage(ChatColor.GREEN + "Unset rail destination (use /" + label + " to set your destination)."); + + // Convert + List destinations = new ArrayList<>(); + args: for (String argument : arguments) { + // If the argument is null or empty, it's likely from a double space, just ignore it + if (Strings.isNullOrEmpty(argument)) { + continue; + } + if (argument.length() > 40 || !CharMatcher.inRange('0', '9'). + or(CharMatcher.inRange('a', 'z')). + or(CharMatcher.inRange('A', 'Z')). + or(CharMatcher.anyOf("!\"#$%&'()*+,-./;:<=>?@[]\\^_`{|}~")). + matchesAllOf(argument)) { + player.sendMessage(ChatColor.RED + "Destinations can not be more than 40 characters and may only " + + "use alphanumerical characters, and ASCII symbols."); + return true; + } + for (String destination : destinations) { + if (argument.equalsIgnoreCase(destination)) { + continue args; + } + } + destinations.add(argument); + } + + if (destinations.isEmpty()) { + player.sendMessage(ChatColor.GREEN + "Unset rail destination (use /" + label + + " to set your destination)."); plugin.getDatabase().removePlayerDestination(player); return true; } - String dest = String.join(" ", args); - if (!plugin.isValidDestination(dest)) { - player.sendMessage(ChatColor.RED + "Destinations can each not be more than 40 characters and may only use alphanumerical characters, ASCII symbols, and spaces."); - return true; - } + String destination = String.join(", ", destinations); long start = System.currentTimeMillis(); - plugin.getDatabase().setPlayerDestination(player, dest); + plugin.getDatabase().setPlayerDestination(player, destination); if (plugin.isTimings()) { plugin.getLogger().info("Set destination took " + (System.currentTimeMillis() - start) + "ms"); } - player.sendMessage(ChatColor.GREEN + "Set your rail destination to: " + dest); + player.sendMessage(ChatColor.GREEN + "Set your rail destination to: " + destination); return true; } + } From d2423d62fffb9e876d5e2df7a93b05ce1bfd2ee0 Mon Sep 17 00:00:00 2001 From: Alexander <52572989+Protonull@users.noreply.github.com> Date: Sat, 25 Jan 2020 02:37:02 +0000 Subject: [PATCH 35/78] Oopsie --- .../src/main/java/sh/okx/railswitch/RailSwitch.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java index 038ec8afa..2c1b93018 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java @@ -40,7 +40,7 @@ public class RailSwitch extends JavaPlugin { String type = config.getString("database-type"); if (type.equalsIgnoreCase("mysql")) { pool = new MySQLConnectionPool( - config.getString("mysql.username"), + config.getString("mysql.host"), config.getInt("mysql.port"), config.getString("mysql.database"), config.getString("mysql.username"), From d5998cd984d32f65bd83c73ce92f09c447b0a0dc Mon Sep 17 00:00:00 2001 From: Alexander <52572989+Protonull@users.noreply.github.com> Date: Sat, 25 Jan 2020 02:59:35 +0000 Subject: [PATCH 36/78] Rename RailSwitch to RailSwitchPlugin --- .../okx/railswitch/{RailSwitch.java => RailSwitchPlugin.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename plugins/railswitch-paper/src/main/java/sh/okx/railswitch/{RailSwitch.java => RailSwitchPlugin.java} (97%) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java similarity index 97% rename from plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java rename to plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java index 2c1b93018..bdc739e95 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java @@ -9,7 +9,7 @@ import sh.okx.railswitch.database.RailSwitchDatabase; import sh.okx.railswitch.database.SQLiteConnectionPool; import sh.okx.railswitch.listener.DetectorRailActivateListener; -public class RailSwitch extends JavaPlugin { +public class RailSwitchPlugin extends JavaPlugin { private boolean timings; private RailSwitchDatabase database; From 94f4293208411ec853ecfb6de302c614be14d7b5 Mon Sep 17 00:00:00 2001 From: Alexander <52572989+Protonull@users.noreply.github.com> Date: Sat, 25 Jan 2020 03:03:02 +0000 Subject: [PATCH 37/78] Moved DetectorRailActivateListener into the main project folder. If the set command doesn't have its own package, why should the listener have one? It's not like the database package where it's actually necessary to distinguish them. --- .../railswitch/{listener => }/DetectorRailActivateListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename plugins/railswitch-paper/src/main/java/sh/okx/railswitch/{listener => }/DetectorRailActivateListener.java (99%) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/DetectorRailActivateListener.java similarity index 99% rename from plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java rename to plugins/railswitch-paper/src/main/java/sh/okx/railswitch/DetectorRailActivateListener.java index fdb79a47d..01a4f8135 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/DetectorRailActivateListener.java @@ -1,4 +1,4 @@ -package sh.okx.railswitch.listener; +package sh.okx.railswitch; import org.bukkit.Bukkit; import org.bukkit.Material; From ebc2a052767d62fbc3616bc1109e396fcdd78e43 Mon Sep 17 00:00:00 2001 From: Alexander <52572989+Protonull@users.noreply.github.com> Date: Sat, 25 Jan 2020 03:08:00 +0000 Subject: [PATCH 38/78] Package cleanup for 94f4293 --- .../src/main/java/sh/okx/railswitch/RailSwitchPlugin.java | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java index bdc739e95..d4077b1f6 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java @@ -7,7 +7,6 @@ import sh.okx.railswitch.database.ConnectionPool; import sh.okx.railswitch.database.MySQLConnectionPool; import sh.okx.railswitch.database.RailSwitchDatabase; import sh.okx.railswitch.database.SQLiteConnectionPool; -import sh.okx.railswitch.listener.DetectorRailActivateListener; public class RailSwitchPlugin extends JavaPlugin { From b3b3391cad008786769cbac532618e3cfb4d800f Mon Sep 17 00:00:00 2001 From: Alexander <52572989+Protonull@users.noreply.github.com> Date: Sat, 25 Jan 2020 03:09:32 +0000 Subject: [PATCH 39/78] Changed config option timings to debug The previous option "timings" is supported for backwards compatibility. --- .../src/main/java/sh/okx/railswitch/RailSwitchPlugin.java | 8 ++++---- .../java/sh/okx/railswitch/SetDestinationCommand.java | 2 +- plugins/railswitch-paper/src/main/resources/config.yml | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java index d4077b1f6..f1488451b 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java @@ -10,7 +10,7 @@ import sh.okx.railswitch.database.SQLiteConnectionPool; public class RailSwitchPlugin extends JavaPlugin { - private boolean timings; + private boolean debug; private RailSwitchDatabase database; @Override @@ -18,7 +18,7 @@ public class RailSwitchPlugin extends JavaPlugin { super.onEnable(); saveDefaultConfig(); loadDatabase(); - this.timings = getConfig().getBoolean("timings", false); + this.debug = getConfig().getBoolean("debug", getConfig().getBoolean("timings", false)); getServer().getPluginManager().registerEvents(new DetectorRailActivateListener(this), this); getCommand("setdestination").setExecutor(new SetDestinationCommand(this)); } @@ -56,8 +56,8 @@ public class RailSwitchPlugin extends JavaPlugin { this.database = new RailSwitchDatabase(pool, getLogger()); } - public boolean isTimings() { - return timings; + public boolean isDebug() { + return this.debug; } } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java index a54647f8e..2e2a1941f 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java @@ -62,7 +62,7 @@ public class SetDestinationCommand implements CommandExecutor { long start = System.currentTimeMillis(); plugin.getDatabase().setPlayerDestination(player, destination); - if (plugin.isTimings()) { + if (plugin.isDebug()) { plugin.getLogger().info("Set destination took " + (System.currentTimeMillis() - start) + "ms"); } player.sendMessage(ChatColor.GREEN + "Set your rail destination to: " + destination); diff --git a/plugins/railswitch-paper/src/main/resources/config.yml b/plugins/railswitch-paper/src/main/resources/config.yml index d789c4f78..adf25687e 100644 --- a/plugins/railswitch-paper/src/main/resources/config.yml +++ b/plugins/railswitch-paper/src/main/resources/config.yml @@ -1,3 +1,5 @@ +debug: false + # can be mysql or sqlite database-type: mysql @@ -9,5 +11,3 @@ mysql: port: 3306 sqlite: file-name: data.db - -timings: false \ No newline at end of file From 301751b4912b5b7d3230aaff548408531b28cfbd Mon Sep 17 00:00:00 2001 From: Alexander <52572989+Protonull@users.noreply.github.com> Date: Sat, 25 Jan 2020 03:21:37 +0000 Subject: [PATCH 40/78] Removed AbstractConnectionPool It's not used by anything, not even the MySQLConnectionPool which uses all of the parameters defined. --- .../database/AbstractConnectionPool.java | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/AbstractConnectionPool.java diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/AbstractConnectionPool.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/AbstractConnectionPool.java deleted file mode 100644 index 65c3671de..000000000 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/AbstractConnectionPool.java +++ /dev/null @@ -1,17 +0,0 @@ -package sh.okx.railswitch.database; - -public abstract class AbstractConnectionPool implements ConnectionPool { - protected final String host; - protected final int port; - protected final String database; - protected final String user; - protected final String password; - - protected AbstractConnectionPool(String host, int port, String database, String user, String password) { - this.host = host; - this.port = port; - this.database = database; - this.user = user; - this.password = password; - } -} From cd5e27f02296385a1e5f013e38334c6b0de8bb45 Mon Sep 17 00:00:00 2001 From: Alexander <52572989+Protonull@users.noreply.github.com> Date: Sat, 25 Jan 2020 03:41:27 +0000 Subject: [PATCH 41/78] RailSwitch now conforms to Devoted's style checker --- .../DetectorRailActivateListener.java | 38 +++++-- .../sh/okx/railswitch/RailSwitchPlugin.java | 17 ++++ .../okx/railswitch/SetDestinationCommand.java | 24 +++-- .../java/sh/okx/railswitch/SwitchType.java | 15 +++ .../railswitch/database/ConnectionPool.java | 11 +++ .../database/MySQLConnectionPool.java | 19 +++- .../database/RailSwitchDatabase.java | 98 ++++++++++++------- .../database/SQLiteConnectionPool.java | 15 ++- 8 files changed, 181 insertions(+), 56 deletions(-) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/DetectorRailActivateListener.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/DetectorRailActivateListener.java index 01a4f8135..14f0ef987 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/DetectorRailActivateListener.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/DetectorRailActivateListener.java @@ -1,5 +1,7 @@ package sh.okx.railswitch; +import java.util.Arrays; +import java.util.Objects; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.block.Block; @@ -12,24 +14,36 @@ import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.block.BlockRedstoneEvent; -import sh.okx.railswitch.RailSwitch; -import sh.okx.railswitch.SwitchType; import vg.civcraft.mc.citadel.Citadel; import vg.civcraft.mc.citadel.ReinforcementManager; import vg.civcraft.mc.citadel.reinforcement.PlayerReinforcement; import vg.civcraft.mc.citadel.reinforcement.Reinforcement; -import java.util.Arrays; -import java.util.Objects; - +/** + * Event listener to enact the functionality of rail switches. + */ public class DetectorRailActivateListener implements Listener { - private final RailSwitch plugin; + private static final String WILDCARD = "*"; - public DetectorRailActivateListener(RailSwitch plugin) { + private final RailSwitchPlugin plugin; + + /** + * Creates a new event listener, passing through the current RailSwitch plugin instance. + * + * @param plugin The current RailSwitch plugin instance. + */ + public DetectorRailActivateListener(RailSwitchPlugin plugin) { this.plugin = plugin; } + /** + * Event handler for rail switches. Will determine if a switch exists at the target location, and if so will process + * it accordingly, allowing it to trigger or not trigger depending on the rider's set destination, the listed + * destinations on the switch, and the switch type. + * + * @param event The block redstone event to base the switch's existence on. + */ @EventHandler public void onRailSwitch(BlockRedstoneEvent event) { Block block = event.getBlock(); @@ -116,13 +130,14 @@ public class DetectorRailActivateListener implements Listener { orElse(null); if (playerDestinations != null && playerDestinations.length > 0) { String[] switchDestinations = Arrays.copyOfRange(lines, 1, lines.length); - matcher: for (String playerDestination : playerDestinations) { - if (playerDestination.equals("*")) { + matcher: + for (String playerDestination : playerDestinations) { + if (playerDestination.equals(WILDCARD)) { matched = true; break; } for (String switchDestination : switchDestinations) { - if (switchDestination.equals("*") + if (switchDestination.equals(WILDCARD) || playerDestination.equalsIgnoreCase(switchDestination)) { matched = true; break matcher; @@ -138,6 +153,9 @@ public class DetectorRailActivateListener implements Listener { case INVERTED: event.setNewCurrent(matched ? 0 : 15); break; + // Gotta have this or the Devoted style checker will screech + default: + break; } } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java index f1488451b..c47ff5e66 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java @@ -8,9 +8,13 @@ import sh.okx.railswitch.database.MySQLConnectionPool; import sh.okx.railswitch.database.RailSwitchDatabase; import sh.okx.railswitch.database.SQLiteConnectionPool; +/** + * The Rail Switch plugin class + */ public class RailSwitchPlugin extends JavaPlugin { private boolean debug; + private RailSwitchDatabase database; @Override @@ -29,10 +33,18 @@ public class RailSwitchPlugin extends JavaPlugin { HandlerList.unregisterAll(this); } + /** + * Retrieves the database connection setup that was created by {@link RailSwitchPlugin#loadDatabase()}. + * + * @return Returns the database connection setup. + */ public RailSwitchDatabase getDatabase() { return database; } + /** + * Create a database connection setup by the configuration. + */ private void loadDatabase() { ConfigurationSection config = getConfig(); ConnectionPool pool; @@ -56,6 +68,11 @@ public class RailSwitchPlugin extends JavaPlugin { this.database = new RailSwitchDatabase(pool, getLogger()); } + /** + * Determines whether the plugin is in debug mode. + * + * @return Returns whether the plugin is in debug mode. + */ public boolean isDebug() { return this.debug; } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java index 2e2a1941f..7dad22d0b 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java @@ -2,20 +2,27 @@ package sh.okx.railswitch; import com.google.common.base.CharMatcher; import com.google.common.base.Strings; +import java.util.ArrayList; +import java.util.List; import org.bukkit.ChatColor; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import java.util.ArrayList; -import java.util.List; - +/** + * The Set Destination command handler. + */ public class SetDestinationCommand implements CommandExecutor { - private final RailSwitch plugin; + private final RailSwitchPlugin plugin; - public SetDestinationCommand(RailSwitch plugin) { + /** + * Creates a new set destination command handler, passing through the current RailSwitch plugin instance. + * + * @param plugin The plugin to pass through to this command handler. + */ + public SetDestinationCommand(RailSwitchPlugin plugin) { this.plugin = plugin; } @@ -26,10 +33,11 @@ public class SetDestinationCommand implements CommandExecutor { } Player player = (Player) sender; - + // Convert List destinations = new ArrayList<>(); - args: for (String argument : arguments) { + checker: + for (String argument : arguments) { // If the argument is null or empty, it's likely from a double space, just ignore it if (Strings.isNullOrEmpty(argument)) { continue; @@ -45,7 +53,7 @@ public class SetDestinationCommand implements CommandExecutor { } for (String destination : destinations) { if (argument.equalsIgnoreCase(destination)) { - continue args; + continue checker; } } destinations.add(argument); diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SwitchType.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SwitchType.java index d332be995..eb3cf99d3 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SwitchType.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SwitchType.java @@ -1,5 +1,8 @@ package sh.okx.railswitch; +/** + * Switch type matcher, will match a type to a tag. + */ public enum SwitchType { NORMAL("[destination]"), @@ -7,10 +10,22 @@ public enum SwitchType { private String tag; + /** + * Defines a new switch type by its tag. Should there be a duplicate tag, the {@link SwitchType#find(String) find()} + * function will return the first value found. + * + * @param tag The tag to associate with this type. + */ SwitchType(String tag) { this.tag = tag; } + /** + * Attempts to match a switch type to a switch tag. + * + * @param tag The tag to match with a type. + * @return Returns a match switch type, or null if none are found. + */ public static SwitchType find(String tag) { if (tag == null || tag.isEmpty()) { return null; diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/ConnectionPool.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/ConnectionPool.java index f4f4368cd..27189c467 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/ConnectionPool.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/ConnectionPool.java @@ -3,6 +3,17 @@ package sh.okx.railswitch.database; import java.sql.Connection; import java.sql.SQLException; +/** + * Interface for creating connections to databases for {@link RailSwitchDatabase} + */ public interface ConnectionPool { + + /** + * Gets the connection for this setup. + * + * @return Returns the connection. + * @throws SQLException Throws an exception if there's an error during the connection. + */ Connection getConnection() throws SQLException; + } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/MySQLConnectionPool.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/MySQLConnectionPool.java index 65cc14d25..c700bed7b 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/MySQLConnectionPool.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/MySQLConnectionPool.java @@ -1,17 +1,29 @@ package sh.okx.railswitch.database; import com.zaxxer.hikari.HikariDataSource; - import java.sql.Connection; import java.sql.SQLException; +/** + * Database connection implementation for MySQL databases. + */ public class MySQLConnectionPool implements ConnectionPool { + private final HikariDataSource hikariDataSource; - public MySQLConnectionPool(String host, int port, String database, String user, String password) { + /** + * Sets up a connection to a MySQL database. + * + * @param host The connection address + * @param port The connection port + * @param database The inner-database to use + * @param username The username to login with + * @param password The password to authenticate the login + */ + public MySQLConnectionPool(String host, int port, String database, String username, String password) { hikariDataSource = new HikariDataSource(); hikariDataSource.setJdbcUrl("jdbc:mysql://" + host + ":" + port + "/" + database); - hikariDataSource.setUsername(user); + hikariDataSource.setUsername(username); hikariDataSource.setPassword(password); } @@ -19,4 +31,5 @@ public class MySQLConnectionPool implements ConnectionPool { public Connection getConnection() throws SQLException { return hikariDataSource.getConnection(); } + } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java index b98bf169e..3ba6a6182 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java @@ -3,8 +3,6 @@ package sh.okx.railswitch.database; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; -import org.bukkit.entity.Player; - import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -15,104 +13,138 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Level; import java.util.logging.Logger; +import org.bukkit.entity.Player; +/** + * RailSwitch database class + */ public class RailSwitchDatabase { + private static final String DESTINATION_TABLE = "rsw_dest"; private ConnectionPool pool; - private Logger log; + + private Logger logger; private String setPlayerDestination; + private String getPlayerDestination; + private String removePlayerDestination; + private ExecutorService service = Executors.newSingleThreadExecutor(); - private final LoadingCache> destinations = CacheBuilder.newBuilder() - .maximumSize(1000) - .build(new CacheLoader>() { + private final LoadingCache> destinations = CacheBuilder.newBuilder(). + maximumSize(1000). + build(new CacheLoader>() { @Override public Optional load(Player key) throws Exception { try (Connection connection = pool.getConnection()) { PreparedStatement statement = connection.prepareStatement(getPlayerDestination); statement.setString(1, key.getUniqueId().toString()); - ResultSet resultSet = statement.executeQuery(); if (resultSet.next()) { return Optional.of(resultSet.getString("dest")); - } else { + } + else { return Optional.empty(); } } } }); + /** + * Creates a new Rail Switch database manager + * + * @param pool The database connection to manage + * @param logger The logger to log to + */ public RailSwitchDatabase(ConnectionPool pool, Logger logger) { - log = logger; + this.logger = logger; this.pool = pool; - createTable(); loadStatements(); } private void createTable() { - try (Connection connection = pool.getConnection()) { + try (Connection connection = this.pool.getConnection()) { connection.createStatement().executeUpdate( "CREATE TABLE IF NOT EXISTS `" + DESTINATION_TABLE + "` (" + "`uuid` VARCHAR(36) NOT NULL," + "`dest` VARCHAR(40) NOT NULL," + "PRIMARY KEY (`uuid`))"); - } catch (SQLException e) { - log.log(Level.SEVERE, "Could not create tables", e); + } + catch (SQLException e) { + this.logger.log(Level.SEVERE, "Could not create tables", e); } } private void loadStatements() { - setPlayerDestination = String.format( + this.setPlayerDestination = String.format( "REPLACE INTO `%1$s` VALUES (?, ?)", DESTINATION_TABLE); - getPlayerDestination = String.format( + this.getPlayerDestination = String.format( "SELECT `dest` FROM `%1$s` WHERE `uuid`=?", DESTINATION_TABLE); - removePlayerDestination = String.format( + this.removePlayerDestination = String.format( "DELETE FROM `%1$s` WHERE `uuid`=?", DESTINATION_TABLE); } + /** + * Sets a player's destination. + * + * @param player The player to set the destination for + * @param destination The destination to set + */ public void setPlayerDestination(Player player, String destination) { - destinations.put(player, Optional.of(destination)); - service.execute(() -> { - try (Connection connection = pool.getConnection()) { - PreparedStatement statement = connection.prepareStatement(setPlayerDestination); + this.destinations.put(player, Optional.of(destination)); + this.service.execute(() -> { + try (Connection connection = this.pool.getConnection()) { + PreparedStatement statement = connection.prepareStatement(this.setPlayerDestination); statement.setString(1, player.getUniqueId().toString()); statement.setString(2, destination); - statement.executeUpdate(); - } catch (SQLException e) { - log.log(Level.SEVERE, "Could not set destination", e); + } + catch (SQLException e) { + this.logger.log(Level.SEVERE, "Could not set destination", e); } }); } + /** + * Resetting a player's destination by removing their entry + * + * @param player The player to reset + */ public void removePlayerDestination(Player player) { - destinations.put(player, Optional.empty()); - service.execute(() -> { - try (Connection connection = pool.getConnection()) { - PreparedStatement statement = connection.prepareStatement(removePlayerDestination); + this.destinations.put(player, Optional.empty()); + this.service.execute(() -> { + try (Connection connection = this.pool.getConnection()) { + PreparedStatement statement = connection.prepareStatement(this.removePlayerDestination); statement.setString(1, player.getUniqueId().toString()); - statement.executeUpdate(); - } catch (SQLException e) { - log.log(Level.SEVERE, "Could not remove destination", e); + } + catch (SQLException e) { + this.logger.log(Level.SEVERE, "Could not remove destination", e); } }); } + /** + * Retrieves a player's set destination + * + * @param player The player to get the destination of + * @return Returns an the player's set destinations in a optional string form + */ public Optional getPlayerDestination(Player player) { try { - return destinations.get(player); - } catch (ExecutionException e) { - log.log(Level.SEVERE, "Could not get destination", e); + return this.destinations.get(player); + } + catch (ExecutionException exception) { + this.logger.log(Level.SEVERE, "Could not get destination", exception); return Optional.empty(); } } + } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/SQLiteConnectionPool.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/SQLiteConnectionPool.java index eb98796e2..baaae64bb 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/SQLiteConnectionPool.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/SQLiteConnectionPool.java @@ -6,15 +6,26 @@ import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; +/** + * Database connection implementation for SQLite databases. + */ public class SQLiteConnectionPool implements ConnectionPool { + private Path file; - public SQLiteConnectionPool(File dataFolder, String file) { - this.file = dataFolder.toPath().resolve(file); + /** + * Sets up a connection to an SQLite database. + * + * @param folder The folder that the database is located in + * @param file The database file itself + */ + public SQLiteConnectionPool(File folder, String file) { + this.file = folder.toPath().resolve(file); } @Override public Connection getConnection() throws SQLException { return DriverManager.getConnection("jdbc:sqlite:" + file); } + } From d772eec2a617958eb91876211a369d325f16332a Mon Sep 17 00:00:00 2001 From: Alexander <52572989+Protonull@users.noreply.github.com> Date: Sat, 25 Jan 2020 03:53:45 +0000 Subject: [PATCH 42/78] Fixed weird XRef error --- plugins/railswitch-paper/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/railswitch-paper/pom.xml b/plugins/railswitch-paper/pom.xml index 9bacbc7a1..0a4b46763 100644 --- a/plugins/railswitch-paper/pom.xml +++ b/plugins/railswitch-paper/pom.xml @@ -43,6 +43,7 @@ https://build.devotedmc.com/job/Style-guide-master/lastSuccessfulBuild/artifact/src/main/resources/devoted_checks.xml true + false From 24c8d04efdaff25f9fcb80815366a4dc14898705 Mon Sep 17 00:00:00 2001 From: Alexander <52572989+Protonull@users.noreply.github.com> Date: Sat, 25 Jan 2020 03:54:22 +0000 Subject: [PATCH 43/78] Bump version - 1.3.4 --- plugins/railswitch-paper/pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/railswitch-paper/pom.xml b/plugins/railswitch-paper/pom.xml index 0a4b46763..555d2d187 100644 --- a/plugins/railswitch-paper/pom.xml +++ b/plugins/railswitch-paper/pom.xml @@ -2,10 +2,11 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + sh.okx RailSwitch jar - 1.3.2 + 1.3.4 RailSwitch https://github.com/civclassic/railswitch From 3fceb39d91b1b9f3ed23b3bbfa30d63109d3d764 Mon Sep 17 00:00:00 2001 From: Alexander <52572989+Protonull@users.noreply.github.com> Date: Sat, 25 Jan 2020 05:38:26 +0000 Subject: [PATCH 44/78] Updated plugin.yml --- plugins/railswitch-paper/pom.xml | 3 ++- plugins/railswitch-paper/src/main/resources/plugin.yml | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/plugins/railswitch-paper/pom.xml b/plugins/railswitch-paper/pom.xml index 555d2d187..c608b951b 100644 --- a/plugins/railswitch-paper/pom.xml +++ b/plugins/railswitch-paper/pom.xml @@ -3,7 +3,7 @@ http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - sh.okx + sh.okx.railswitch RailSwitch jar 1.3.4 @@ -96,4 +96,5 @@ http://build.devotedmc.com/plugin/repository/everything/ + diff --git a/plugins/railswitch-paper/src/main/resources/plugin.yml b/plugins/railswitch-paper/src/main/resources/plugin.yml index 4a8b023c4..1db5c2065 100644 --- a/plugins/railswitch-paper/src/main/resources/plugin.yml +++ b/plugins/railswitch-paper/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ -name: RailSwitch -version: '1.3.1' -main: sh.okx.railswitch.RailSwitch +name: ${project.artifactId} +version: ${project.version} +main: ${project.groupId}.${project.artifactId}Plugin author: Okx api-version: '1.14' description: Easily make rail switches for automated destination selection @@ -9,4 +9,4 @@ commands: setdestination: usage: / description: Set your rail destination - aliases: [dest, destination, switch, setswitch, setsw] \ No newline at end of file + aliases: [dest, destination, switch, setswitch, setsw] From b483eec15dd0027c9205ab9c82c66ff7753a68d8 Mon Sep 17 00:00:00 2001 From: Alexander <52572989+Protonull@users.noreply.github.com> Date: Sat, 25 Jan 2020 05:39:23 +0000 Subject: [PATCH 45/78] Not sure why this was set to 1.14, this is the 1.12 branch --- plugins/railswitch-paper/src/main/resources/plugin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/railswitch-paper/src/main/resources/plugin.yml b/plugins/railswitch-paper/src/main/resources/plugin.yml index 1db5c2065..1549d3367 100644 --- a/plugins/railswitch-paper/src/main/resources/plugin.yml +++ b/plugins/railswitch-paper/src/main/resources/plugin.yml @@ -2,7 +2,7 @@ name: ${project.artifactId} version: ${project.version} main: ${project.groupId}.${project.artifactId}Plugin author: Okx -api-version: '1.14' +api-version: '1.12' description: Easily make rail switches for automated destination selection softdepend: [Citadel, NameLayer] commands: From 30ddd55c028c81f4ee9d11e5b0a6daea2582ccfa Mon Sep 17 00:00:00 2001 From: Alexander <52572989+Protonull@users.noreply.github.com> Date: Sun, 26 Jan 2020 01:21:04 +0000 Subject: [PATCH 46/78] Moved the Citadel check As advised, the Citadel check has been moved below the player check, as it is common for player's to use hopper minecarts around farms, and best to not keep querying the database. --- .../DetectorRailActivateListener.java | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/DetectorRailActivateListener.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/DetectorRailActivateListener.java index 14f0ef987..10956c4d7 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/DetectorRailActivateListener.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/DetectorRailActivateListener.java @@ -70,30 +70,7 @@ public class DetectorRailActivateListener implements Listener { if (type == null) { return; } - - // If Citadel is enabled, check that the sign and the rail are on the same group - if (Bukkit.getPluginManager().isPluginEnabled("Citadel")) { - ReinforcementManager rein = Citadel.getReinforcementManager(); - Reinforcement railReinforcement = rein.getReinforcement(block); - Reinforcement signReinforcement = rein.getReinforcement(above); - // Allow the switch to work if the rail and the sign are BOTH unreinforced - // otherwise servers with established rail networks will suddenly not work - // if Citadel is added. - if (railReinforcement != null || signReinforcement != null) { - if (!(railReinforcement instanceof PlayerReinforcement)) { - return; - } - if (!(signReinforcement instanceof PlayerReinforcement)) { - return; - } - if (!Objects.equals( - ((PlayerReinforcement) railReinforcement).getGroup(), - ((PlayerReinforcement) signReinforcement).getGroup())) { - return; - } - } - } - + // Check that a player is triggering the switch // NOTE: The event doesn't provide the information and so the next best thing is searching for a // player who is nearby and riding a minecart. @@ -121,6 +98,29 @@ public class DetectorRailActivateListener implements Listener { return; } + // If Citadel is enabled, check that the sign and the rail are on the same group + if (Bukkit.getPluginManager().isPluginEnabled("Citadel")) { + ReinforcementManager rein = Citadel.getReinforcementManager(); + Reinforcement railReinforcement = rein.getReinforcement(block); + Reinforcement signReinforcement = rein.getReinforcement(above); + // Allow the switch to work if the rail and the sign are BOTH unreinforced + // otherwise servers with established rail networks will suddenly not work + // if Citadel is added. + if (railReinforcement != null || signReinforcement != null) { + if (!(railReinforcement instanceof PlayerReinforcement)) { + return; + } + if (!(signReinforcement instanceof PlayerReinforcement)) { + return; + } + if (!Objects.equals( + ((PlayerReinforcement) railReinforcement).getGroup(), + ((PlayerReinforcement) signReinforcement).getGroup())) { + return; + } + } + } + // Determine whether a player has a destination that matches one of the destinations // listed on the switch signs, or match if there's a wildcard. boolean matched = false; From e0a41cdf1fad42dd02d09475918ab6e83399bd7d Mon Sep 17 00:00:00 2001 From: Alexander <52572989+Protonull@users.noreply.github.com> Date: Sun, 26 Jan 2020 01:24:21 +0000 Subject: [PATCH 47/78] Removed Material.SIGN_POST check --- .../java/sh/okx/railswitch/DetectorRailActivateListener.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/DetectorRailActivateListener.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/DetectorRailActivateListener.java index 10956c4d7..4e410165d 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/DetectorRailActivateListener.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/DetectorRailActivateListener.java @@ -58,8 +58,7 @@ public class DetectorRailActivateListener implements Listener { // Check that the block above the rail is a sign Block above = block.getRelative(BlockFace.UP); if (above == null - // Do not check for Material.SIGN as that is an item material, not a block material - || (above.getType() != Material.SIGN_POST && above.getType() != Material.WALL_SIGN) + || above.getType() != Material.WALL_SIGN || !(above.getState() instanceof Sign)) { return; } From 79a1c876817ba23b2b4cad39adf482c0dca343d5 Mon Sep 17 00:00:00 2001 From: Alexander <52572989+Protonull@users.noreply.github.com> Date: Sun, 26 Jan 2020 03:31:52 +0000 Subject: [PATCH 48/78] Set Destination now uses a regex --- .../sh/okx/railswitch/SetDestinationCommand.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java index 7dad22d0b..1b3a0192e 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java @@ -1,9 +1,10 @@ package sh.okx.railswitch; -import com.google.common.base.CharMatcher; import com.google.common.base.Strings; import java.util.ArrayList; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.bukkit.ChatColor; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; @@ -15,6 +16,9 @@ import org.bukkit.entity.Player; */ public class SetDestinationCommand implements CommandExecutor { + private static final Pattern validDestinationPattern = Pattern.compile( + "^[\\w!\"#$%&'()*+,-./;:<=>?@\\[\\]^`{|}~]{1,40}$"); + private final RailSwitchPlugin plugin; /** @@ -42,11 +46,7 @@ public class SetDestinationCommand implements CommandExecutor { if (Strings.isNullOrEmpty(argument)) { continue; } - if (argument.length() > 40 || !CharMatcher.inRange('0', '9'). - or(CharMatcher.inRange('a', 'z')). - or(CharMatcher.inRange('A', 'Z')). - or(CharMatcher.anyOf("!\"#$%&'()*+,-./;:<=>?@[]\\^_`{|}~")). - matchesAllOf(argument)) { + if (!validDestinationPattern.matcher(argument).matches()) { player.sendMessage(ChatColor.RED + "Destinations can not be more than 40 characters and may only " + "use alphanumerical characters, and ASCII symbols."); return true; From f5d0870fc3bc32d17ee42e7516160cf32426e46c Mon Sep 17 00:00:00 2001 From: Alexander <52572989+Protonull@users.noreply.github.com> Date: Sun, 26 Jan 2020 03:32:15 +0000 Subject: [PATCH 49/78] Include cleanup --- .../src/main/java/sh/okx/railswitch/SetDestinationCommand.java | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java index 1b3a0192e..06d1d0238 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java @@ -3,7 +3,6 @@ package sh.okx.railswitch; import com.google.common.base.Strings; import java.util.ArrayList; import java.util.List; -import java.util.regex.Matcher; import java.util.regex.Pattern; import org.bukkit.ChatColor; import org.bukkit.command.Command; From bb12f3c4196041879ace45b7d097afa831c95066 Mon Sep 17 00:00:00 2001 From: Alexander <52572989+Protonull@users.noreply.github.com> Date: Sun, 16 Feb 2020 05:47:26 +0000 Subject: [PATCH 50/78] Fixed a silly bug and also added an additional check --- .../main/java/sh/okx/railswitch/SetDestinationCommand.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java index 06d1d0238..75e3cd0d4 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java @@ -65,7 +65,12 @@ public class SetDestinationCommand implements CommandExecutor { return true; } - String destination = String.join(", ", destinations); + String destination = String.join(" ", destinations); + + if (destination.length() > 40) { + player.sendMessage(ChatColor.RED + "Destinations as a whole cannot have more than 40 characters."); + return true; + } long start = System.currentTimeMillis(); plugin.getDatabase().setPlayerDestination(player, destination); From 35a79beb50775f4afc8bc1e5e433e20f85b012ee Mon Sep 17 00:00:00 2001 From: Alexander <52572989+Protonull@users.noreply.github.com> Date: Sun, 16 Feb 2020 06:04:21 +0000 Subject: [PATCH 51/78] Bump version - 1.3.5 --- plugins/railswitch-paper/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/railswitch-paper/pom.xml b/plugins/railswitch-paper/pom.xml index c608b951b..3c0ba1786 100644 --- a/plugins/railswitch-paper/pom.xml +++ b/plugins/railswitch-paper/pom.xml @@ -6,7 +6,7 @@ sh.okx.railswitch RailSwitch jar - 1.3.4 + 1.3.5 RailSwitch https://github.com/civclassic/railswitch From c415fbcad0e6776e8d8b0bb7c9c895043e486ac3 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 24 Apr 2020 17:33:38 +0100 Subject: [PATCH 52/78] RailSwitch refactoring --- plugins/railswitch-paper/pom.xml | 107 ++++++++++++---- .../java/sh/okx/railswitch/RailSwitch.java | 79 ------------ .../sh/okx/railswitch/RailSwitchPlugin.java | 43 +++++++ .../okx/railswitch/SetDestinationCommand.java | 44 ------- .../commands/DestinationCommand.java | 23 ++++ .../database/AbstractConnectionPool.java | 17 --- .../railswitch/database/ConnectionPool.java | 8 -- .../database/MySQLConnectionPool.java | 22 ---- .../database/RailSwitchDatabase.java | 118 ----------------- .../database/SQLiteConnectionPool.java | 20 --- .../sh/okx/railswitch/glue/CitadelGlue.java | 67 ++++++++++ .../DetectorRailActivateListener.java | 119 ----------------- .../settings/DestinationSetting.java | 27 ++++ .../railswitch/settings/RailSwitchMenu.java | 18 +++ .../okx/railswitch/settings/ResetSetting.java | 50 ++++++++ .../railswitch/settings/SettingsManager.java | 85 ++++++++++++ .../railswitch/switches/SwitchListener.java | 121 ++++++++++++++++++ .../okx/railswitch/switches/SwitchType.java | 43 +++++++ .../src/main/resources/config.yml | 13 -- .../src/main/resources/plugin.yml | 18 ++- 20 files changed, 570 insertions(+), 472 deletions(-) delete mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java create mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java delete mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java create mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/commands/DestinationCommand.java delete mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/AbstractConnectionPool.java delete mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/ConnectionPool.java delete mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/MySQLConnectionPool.java delete mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java delete mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/SQLiteConnectionPool.java create mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/glue/CitadelGlue.java delete mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java create mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/DestinationSetting.java create mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/RailSwitchMenu.java create mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/ResetSetting.java create mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/SettingsManager.java create mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchListener.java create mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchType.java delete mode 100644 plugins/railswitch-paper/src/main/resources/config.yml diff --git a/plugins/railswitch-paper/pom.xml b/plugins/railswitch-paper/pom.xml index ebc7d0c47..d2481921e 100644 --- a/plugins/railswitch-paper/pom.xml +++ b/plugins/railswitch-paper/pom.xml @@ -2,7 +2,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - sh.okx + sh.okx.railswitch RailSwitch jar 1.4.0 @@ -10,28 +10,91 @@ https://github.com/civclassic/railswitch - 1.8 - 1.8 + 1.8 UTF-8 UTF-8 + ${project.groupId}.${project.artifactId}Plugin + 1.14 + ${project.artifactId}-${project.version}-${minecraft.version} - ${basedir}/src/main/resources + ${basedir} - *.yml + LICENSE.txt + README.md + + + src/main/resources/ true - - ${basedir} - - LICENSE.txt - - + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.compile.version} + ${java.compile.version} + + + + org.apache.maven.plugins + maven-source-plugin + 3.1.0 + + + attach-sources + verify + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + false + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.1 + + + package + + shade + + + false + + + + Maven + + + + + + + + @@ -41,10 +104,16 @@ 1.14.4-R0.1-SNAPSHOT provided + + vg.civcraft.mc.civmodcore + CivModCore + 1.7.8 + provided + vg.civcraft.mc.namelayer NameLayer - 2.13.0 + 2.13.1 provided @@ -53,22 +122,16 @@ 4.0.1 provided - - vg.civcraft.mc.civmodcore - CivModCore - 1.7.7 - provided - - - devoted-repo - http://build.devotedmc.com/plugin/repository/everything/ - spigot-repo https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + devoted-repo + http://build.devotedmc.com/plugin/repository/everything/ + diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java deleted file mode 100644 index a4eb2c581..000000000 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitch.java +++ /dev/null @@ -1,79 +0,0 @@ -package sh.okx.railswitch; - -import com.google.common.base.CharMatcher; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.plugin.PluginManager; -import org.bukkit.plugin.java.JavaPlugin; -import sh.okx.railswitch.database.ConnectionPool; -import sh.okx.railswitch.database.MySQLConnectionPool; -import sh.okx.railswitch.database.RailSwitchDatabase; -import sh.okx.railswitch.database.SQLiteConnectionPool; -import sh.okx.railswitch.listener.DetectorRailActivateListener; - -public class RailSwitch extends JavaPlugin { - private boolean timings; - private RailSwitchDatabase database; - - @Override - public void onEnable() { - super.onEnable(); - saveDefaultConfig(); - loadDatabase(); - - if (getConfig().getBoolean("timings")) { - timings = true; - } - - PluginManager pm = getServer().getPluginManager(); - - pm.registerEvents(new DetectorRailActivateListener(this), this); - getCommand("setdestination").setExecutor(new SetDestinationCommand(this)); - } - - public RailSwitchDatabase getDatabase() { - return database; - } - - private void loadDatabase() { - ConfigurationSection config = getConfig(); - - ConnectionPool pool; - String type = config.getString("database-type"); - if (type.equalsIgnoreCase("mysql")) { - String username = config.getString("mysql.username"); - String host = config.getString("mysql.host"); - String password = config.getString("mysql.password"); - String database = config.getString("mysql.database"); - int port = config.getInt("mysql.port"); - - pool = new MySQLConnectionPool(host, port, database, username, password); - } else if (type.equalsIgnoreCase("sqlite")) { - pool = new SQLiteConnectionPool(getDataFolder(), config.getString("sqlite.file-name")); - } else { - throw new RuntimeException("Invalid database-type in config.yml. Disabling plugin."); - } - - this.database = new RailSwitchDatabase(pool, getLogger()); - } - - /** - * make sure the message doesn't have any weirdness - */ - public boolean isValidDestination(String message) { - // Each destination must be fewer than 40 characters. - for (String dest : message.split(" ")) { - if (dest.length() > 40) { - return false; - } - } - - return CharMatcher.inRange('0', '9') - .or(CharMatcher.inRange('a', 'z')) - .or(CharMatcher.inRange('A', 'Z')) - .or(CharMatcher.anyOf("!\"#$%&'()*+,-./;:<=>?@[]\\^_`{|}~ ")).matchesAllOf(message); - } - - public boolean isTimings() { - return timings; - } -} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java new file mode 100644 index 000000000..eba21afa8 --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java @@ -0,0 +1,43 @@ +package sh.okx.railswitch; + +import org.bukkit.event.Listener; +import sh.okx.railswitch.commands.DestinationCommand; +import sh.okx.railswitch.glue.CitadelGlue; +import sh.okx.railswitch.settings.SettingsManager; +import sh.okx.railswitch.switches.SwitchListener; +import vg.civcraft.mc.civmodcore.ACivMod; +import vg.civcraft.mc.civmodcore.command.AikarCommandManager; + +/** + * The Rail Switch plugin class + */ +public final class RailSwitchPlugin extends ACivMod implements Listener { + + private static AikarCommandManager commands; + + @Override + public void onEnable() { + useNewCommandHandler = false; + super.onEnable(); + SettingsManager.init(this); + registerListener(new CitadelGlue()); + registerListener(new SwitchListener()); + commands = new AikarCommandManager(this) { + @Override + public void registerCommands() { + registerCommand(new DestinationCommand()); + } + }; + } + + @Override + public void onDisable() { + SettingsManager.reset(); + if (commands != null) { + commands.reset(); + commands = null; + } + super.onDisable(); + } + +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java deleted file mode 100644 index 9688fbc3b..000000000 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java +++ /dev/null @@ -1,44 +0,0 @@ -package sh.okx.railswitch; - -import org.bukkit.ChatColor; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - -public class SetDestinationCommand implements CommandExecutor { - private final RailSwitch plugin; - - public SetDestinationCommand(RailSwitch plugin) { - this.plugin = plugin; - } - - @Override - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - if (!(sender instanceof Player)) { - return false; - } - - - Player player = (Player) sender; - if (args.length < 1) { - player.sendMessage(ChatColor.GREEN + "Unset rail destination (use /" + label + " to set your destination)."); - plugin.getDatabase().removePlayerDestination(player); - return true; - } - - String dest = String.join(" ", args); - if (!plugin.isValidDestination(dest)) { - player.sendMessage(ChatColor.RED + "Destinations can each not be more than 40 characters and may only use alphanumerical characters, ASCII symbols, and spaces."); - return true; - } - - long start = System.currentTimeMillis(); - plugin.getDatabase().setPlayerDestination(player, dest); - if (plugin.isTimings()) { - plugin.getLogger().info("Set destination took " + (System.currentTimeMillis() - start) + "ms"); - } - player.sendMessage(ChatColor.GREEN + "Set your rail destination to: " + dest); - return true; - } -} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/commands/DestinationCommand.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/commands/DestinationCommand.java new file mode 100644 index 000000000..38771161b --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/commands/DestinationCommand.java @@ -0,0 +1,23 @@ +package sh.okx.railswitch.commands; + +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Syntax; +import org.bukkit.entity.Player; +import sh.okx.railswitch.settings.SettingsManager; +import vg.civcraft.mc.civmodcore.command.AikarCommand; + +/** + * Continued support for setting and resetting your destination via a command. + */ +public final class DestinationCommand extends AikarCommand { + + @CommandAlias("dest|destination|setdestination|switch|setswitch|setsw") + @Description("Set your rail destination(s)") + @Syntax("[destination]") + public void onSetDestination(Player player, @Optional String destination) { + SettingsManager.setDestination(player, destination); + } + +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/AbstractConnectionPool.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/AbstractConnectionPool.java deleted file mode 100644 index 6c18a6ce3..000000000 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/AbstractConnectionPool.java +++ /dev/null @@ -1,17 +0,0 @@ -package sh.okx.railswitch.database; - -public abstract class AbstractConnectionPool implements ConnectionPool { - protected final String host; - protected final int port; - protected final String database; - protected final String user; - protected final String password; - - protected AbstractConnectionPool(String host, int port, String database, String user, String password) { - this.host = host; - this.port = port; - this.database = database; - this.user = user; - this.password = password; - } -} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/ConnectionPool.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/ConnectionPool.java deleted file mode 100644 index 9f2a2015e..000000000 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/ConnectionPool.java +++ /dev/null @@ -1,8 +0,0 @@ -package sh.okx.railswitch.database; - -import java.sql.Connection; -import java.sql.SQLException; - -public interface ConnectionPool { - Connection getConnection() throws SQLException; -} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/MySQLConnectionPool.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/MySQLConnectionPool.java deleted file mode 100644 index 6faca1ee2..000000000 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/MySQLConnectionPool.java +++ /dev/null @@ -1,22 +0,0 @@ -package sh.okx.railswitch.database; - -import com.zaxxer.hikari.HikariDataSource; - -import java.sql.Connection; -import java.sql.SQLException; - -public class MySQLConnectionPool implements ConnectionPool { - private final HikariDataSource hikariDataSource; - - public MySQLConnectionPool(String host, int port, String database, String user, String password) { - hikariDataSource = new HikariDataSource(); - hikariDataSource.setJdbcUrl("jdbc:mysql://" + host + ":" + port + "/" + database); - hikariDataSource.setUsername(user); - hikariDataSource.setPassword(password); - } - - @Override - public Connection getConnection() throws SQLException { - return hikariDataSource.getConnection(); - } -} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java deleted file mode 100644 index d0598ba44..000000000 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java +++ /dev/null @@ -1,118 +0,0 @@ -package sh.okx.railswitch.database; - -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import org.bukkit.entity.Player; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.Optional; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.logging.Level; -import java.util.logging.Logger; - -public class RailSwitchDatabase { - private static final String DESTINATION_TABLE = "rsw_dest"; - - private ConnectionPool pool; - private Logger log; - - private String setPlayerDestination; - private String getPlayerDestination; - private String removePlayerDestination; - private ExecutorService service = Executors.newSingleThreadExecutor(); - - private final LoadingCache> destinations = CacheBuilder.newBuilder() - .maximumSize(1000) - .build(new CacheLoader>() { - @Override - public Optional load(Player key) throws Exception { - try (Connection connection = pool.getConnection()) { - PreparedStatement statement = connection.prepareStatement(getPlayerDestination); - statement.setString(1, key.getUniqueId().toString()); - - ResultSet resultSet = statement.executeQuery(); - if (resultSet.next()) { - return Optional.of(resultSet.getString("dest")); - } else { - return Optional.empty(); - } - } - } - }); - - public RailSwitchDatabase(ConnectionPool pool, Logger logger) { - log = logger; - this.pool = pool; - - createTable(); - loadStatements(); - } - - private void createTable() { - try (Connection connection = pool.getConnection()) { - connection.createStatement().executeUpdate( - "CREATE TABLE IF NOT EXISTS `" + DESTINATION_TABLE + "` (" + - "`uuid` VARCHAR(36) NOT NULL," + - "`dest` VARCHAR(40) NOT NULL," + - "PRIMARY KEY (`uuid`))"); - } catch (SQLException e) { - log.log(Level.SEVERE, "Could not create tables", e); - } - } - - private void loadStatements() { - setPlayerDestination = String.format( - "REPLACE INTO `%1$s` VALUES (?, ?)", - DESTINATION_TABLE); - getPlayerDestination = String.format( - "SELECT `dest` FROM `%1$s` WHERE `uuid`=?", - DESTINATION_TABLE); - removePlayerDestination = String.format( - "DELETE FROM `%1$s` WHERE `uuid`=?", - DESTINATION_TABLE); - } - - public void setPlayerDestination(Player player, String destination) { - destinations.put(player, Optional.of(destination)); - service.execute(() -> { - try (Connection connection = pool.getConnection()) { - PreparedStatement statement = connection.prepareStatement(setPlayerDestination); - statement.setString(1, player.getUniqueId().toString()); - statement.setString(2, destination); - - statement.executeUpdate(); - } catch (SQLException e) { - log.log(Level.SEVERE, "Could not set destination", e); - } - }); - } - - public void removePlayerDestination(Player player) { - destinations.put(player, Optional.empty()); - service.execute(() -> { - try (Connection connection = pool.getConnection()) { - PreparedStatement statement = connection.prepareStatement(removePlayerDestination); - statement.setString(1, player.getUniqueId().toString()); - - statement.executeUpdate(); - } catch (SQLException e) { - log.log(Level.SEVERE, "Could not remove destination", e); - } - }); - } - - public Optional getPlayerDestination(Player player) { - try { - return destinations.get(player); - } catch (ExecutionException e) { - log.log(Level.SEVERE, "Could not get destination", e); - return Optional.empty(); - } - } -} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/SQLiteConnectionPool.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/SQLiteConnectionPool.java deleted file mode 100644 index ac5b76bab..000000000 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/SQLiteConnectionPool.java +++ /dev/null @@ -1,20 +0,0 @@ -package sh.okx.railswitch.database; - -import java.io.File; -import java.nio.file.Path; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; - -public class SQLiteConnectionPool implements ConnectionPool { - private Path file; - - public SQLiteConnectionPool(File dataFolder, String file) { - this.file = dataFolder.toPath().resolve(file); - } - - @Override - public Connection getConnection() throws SQLException { - return DriverManager.getConnection("jdbc:sqlite:" + file); - } -} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/glue/CitadelGlue.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/glue/CitadelGlue.java new file mode 100644 index 000000000..f348ef10c --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/glue/CitadelGlue.java @@ -0,0 +1,67 @@ +package sh.okx.railswitch.glue; + +import org.bukkit.block.Block; +import vg.civcraft.mc.citadel.Citadel; +import vg.civcraft.mc.citadel.ReinforcementManager; +import vg.civcraft.mc.citadel.model.Reinforcement; +import vg.civcraft.mc.civmodcore.api.BlockAPI; +import vg.civcraft.mc.civmodcore.util.DependencyGlue; +import vg.civcraft.mc.civmodcore.util.NullCoalescing; + +/** + * Glue for Citadel. + */ +public final class CitadelGlue extends DependencyGlue { + + private ReinforcementManager manager; + + public CitadelGlue() { + super("Citadel"); + } + + @Override + public boolean isSafeToUse() { + if (!super.isSafeToUse()) { + return false; + } + if (this.manager == null) { + return false; + } + return true; + } + + @Override + protected void onGlueEnabled() { + this.manager = Citadel.getInstance().getReinforcementManager(); + } + + @Override + protected void onGlueDisabled() { + this.manager = null; + } + + /** + * Do two blocks, representing the sign and the rail, have the same reinforcement group? + * + * @param sign The block representing the sign. + * @param rail The block representing the rail. + * @return Returns true both blocks share the same reinforcement group, or if both are un-reinforced. + */ + public boolean doSignAndRailHaveSameReinforcement(Block sign, Block rail) { + if (!isSafeToUse() || !BlockAPI.isValidBlock(sign) || !BlockAPI.isValidBlock(rail)) { + return false; + } + Reinforcement signReinforcement = this.manager.getReinforcement(sign); + Reinforcement railReinforcement = this.manager.getReinforcement(rail); + if (signReinforcement == null && railReinforcement == null) { + return true; + } + if (signReinforcement == null || railReinforcement == null) { + return false; + } + return NullCoalescing.equalsNotNull( + signReinforcement.getGroup(), + railReinforcement.getGroup()); + } + +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java deleted file mode 100644 index 3b9ebad9e..000000000 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/listener/DetectorRailActivateListener.java +++ /dev/null @@ -1,119 +0,0 @@ -package sh.okx.railswitch.listener; - -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.World; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.block.Sign; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Minecart; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.block.BlockRedstoneEvent; -import sh.okx.railswitch.RailSwitch; -import vg.civcraft.mc.citadel.Citadel; -import vg.civcraft.mc.citadel.ReinforcementManager; -import vg.civcraft.mc.citadel.model.Reinforcement; - -import java.util.Optional; - -public class DetectorRailActivateListener implements Listener { - - private static final String NORMAL_SWITCH_TYPE = "[destination]"; - private static final String INVERTED_SWITCH_TYPE = "[!destination]"; - - private final RailSwitch plugin; - - public DetectorRailActivateListener(RailSwitch plugin) { - this.plugin = plugin; - } - - @EventHandler - public void onRailSwitch(BlockRedstoneEvent event) { - Block block = event.getBlock(); - if (block == null - || block.getType() != Material.DETECTOR_RAIL - || event.getNewCurrent() != 15) { - return; - } - - Block above = block.getRelative(BlockFace.UP); - if (!(above.getState() instanceof Sign)) { - return; - } - - String[] lines = ((Sign) above.getState()).getLines(); - if (!(lines[0].equalsIgnoreCase(NORMAL_SWITCH_TYPE) - || lines[0].equalsIgnoreCase(INVERTED_SWITCH_TYPE))) { - return; - } - - if (Bukkit.getPluginManager().isPluginEnabled("Citadel") - && !isSameReinforcementGroup(block.getLocation(), above.getLocation())) { - return; - } - - Player player = findNearestPlayerInMinecart(block.getLocation()); - if (player == null) { - return; - } - - Optional destination = plugin.getDatabase().getPlayerDestination(player); - - boolean matched = destination.map(d -> isSignMatchDestination(lines, d)).orElse(false); - - if (lines[0].equalsIgnoreCase(NORMAL_SWITCH_TYPE) && !matched) { - event.setNewCurrent(0); - } else if (lines[0].equalsIgnoreCase(INVERTED_SWITCH_TYPE) && matched) { - event.setNewCurrent(0); - } - } - - private boolean isSignMatchDestination(String[] lines, String destination) { - for (String line : lines) { - if ("*".equals(line) || destination.equalsIgnoreCase(line)) { - return true; - } - - // Use spaces as an "or" operator, allowing for more advanced routing. - // We preserve the check above for backwards compatibility. - String[] destinations = destination.split(" "); - for (String dest : destinations) { - if (dest.equalsIgnoreCase(line)) { - return true; - } - } - } - return false; - } - - private boolean isSameReinforcementGroup(Location a, Location b) { - ReinforcementManager rein = Citadel.getInstance().getReinforcementManager(); - Reinforcement reinA = rein.getReinforcement(a); - Reinforcement reinB = rein.getReinforcement(b); - return reinA.getGroup().equals(reinB.getGroup()); - } - - /** - * @param location the centre - * @return the nearest player riding a minecart in a 3 block radius of the centre - */ - private Player findNearestPlayerInMinecart(Location location) { - World world = location.getWorld(); - Player nearestPlayer = null; - double distance = Double.MAX_VALUE; - for (Entity entity : world.getNearbyEntities(location, 3, 3, 3)) { - if (entity instanceof Player && entity.getVehicle() instanceof Minecart) { - double entityDistance = location.distanceSquared(entity.getLocation()); - if (entityDistance < distance) { - distance = entityDistance; - nearestPlayer = (Player) entity; - } - } - } - return nearestPlayer; - } -} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/DestinationSetting.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/DestinationSetting.java new file mode 100644 index 000000000..b5f0dc43f --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/DestinationSetting.java @@ -0,0 +1,27 @@ +package sh.okx.railswitch.settings; + +import com.google.common.base.Strings; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.java.JavaPlugin; +import vg.civcraft.mc.civmodcore.playersettings.impl.StringSetting; + +/** + * Setting representing a player's destination. + */ +public final class DestinationSetting extends StringSetting { + + public DestinationSetting(JavaPlugin plugin) { + super(plugin, "", "Destination", "dest", new ItemStack(Material.MINECART), + "The destination(s) that will be used to route you at rail junctions."); + } + + @Override + public String toText(String value) { + if (Strings.isNullOrEmpty(value)) { + return ""; + } + return value; + } + +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/RailSwitchMenu.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/RailSwitchMenu.java new file mode 100644 index 000000000..0708d63af --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/RailSwitchMenu.java @@ -0,0 +1,18 @@ +package sh.okx.railswitch.settings; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.playersettings.PlayerSettingAPI; +import vg.civcraft.mc.civmodcore.playersettings.gui.MenuSection; + +/** + * The menu for RailSwitch. This is what all RailSwitch settings will be registered to. + */ +public final class RailSwitchMenu extends MenuSection { + + public RailSwitchMenu() { + super("RailSwitch", "Settings relating to RailSwitch", PlayerSettingAPI.getMainMenu(), + new ItemStack(Material.RAIL)); + } + +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/ResetSetting.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/ResetSetting.java new file mode 100644 index 000000000..8bff68c78 --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/ResetSetting.java @@ -0,0 +1,50 @@ +package sh.okx.railswitch.settings; + +import java.util.UUID; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.java.JavaPlugin; +import vg.civcraft.mc.civmodcore.playersettings.gui.MenuSection; +import vg.civcraft.mc.civmodcore.playersettings.impl.StringSetting; + +/** + * ResetSetting, a setting that functions more like a GUI button than an actual setting. + */ +public final class ResetSetting extends StringSetting { + + private final DestinationSetting destinationSetting; + + public ResetSetting(JavaPlugin plugin, DestinationSetting destinationSetting) { + super(plugin, "", "Clear Destination", "dest", new ItemStack(Material.BARRIER), "Clears your destination."); + this.destinationSetting = destinationSetting; + } + + @Override + public void handleMenuClick(Player player, MenuSection menu) { + resetPlayerDestination(player); + menu.showScreen(player); + } + + /** + * Resets a player's destination value. + * + * @param player The player to reset the destination for. + */ + public void resetPlayerDestination(Player player) { + this.destinationSetting.setValue(player, ""); + player.sendMessage(ChatColor.GREEN + "Your destination has been reset."); + } + + @Override + public String getValue(UUID player) { + return this.destinationSetting.getValue(player); + } + + @Override + public String toText(String value) { + return this.destinationSetting.toText(value); + } + +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/SettingsManager.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/SettingsManager.java new file mode 100644 index 000000000..459ec99bb --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/SettingsManager.java @@ -0,0 +1,85 @@ +package sh.okx.railswitch.settings; + +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import sh.okx.railswitch.RailSwitchPlugin; +import vg.civcraft.mc.civmodcore.util.NullCoalescing; + +/** + * Manages the initialisation and registration of menu settings. + */ +public final class SettingsManager { + + private static RailSwitchMenu menu; + + private static DestinationSetting destSetting; + + private static ResetSetting resetSetting; + + /** + * Initialise the settings manager. This should only be called within RailSwitch onEnable(). + * + * @param plugin The enabled RailSwitch plugin instance. + */ + public static void init(RailSwitchPlugin plugin) { + // Create the menu elements + menu = new RailSwitchMenu(); + destSetting = new DestinationSetting(plugin); + resetSetting = new ResetSetting(plugin, destSetting); + // Register those elements + menu.registerToParentMenu(); + menu.registerSetting(destSetting); + menu.registerSetting(resetSetting); + } + + /** + * Gracefully resets the settings manager. This should only be called within RailSwitch onDisable(). + */ + public static void reset() { + // TODO: Deregister and unload all the menu elements once PlayerSettingAPI becomes reload safe + menu = null; + destSetting = null; + resetSetting = null; + } + + /** + * Sets a player's destination. This is for when the player settings don't themselves provide a way to do so. + * + * @param player The player to set the destination to. + * @param destination The destination to set. + */ + public static void setDestination(Player player, String destination) { + Preconditions.checkArgument(player != null); + if (Strings.isNullOrEmpty(destination)) { + if (resetSetting != null) { + resetSetting.resetPlayerDestination(player); + player.sendMessage(ChatColor.GREEN + "Your destination has been reset."); + } + else { + player.sendMessage(ChatColor.RED + "Could not reset your destination."); + } + } + else { + if (destSetting != null) { + destSetting.setValue(player, destination); + player.sendMessage(ChatColor.GREEN + "Destination set to: " + destination); + } + else { + player.sendMessage(ChatColor.RED + "Could not set your destination."); + } + } + } + + /** + * Gets a player's destination. + * + * @param player The player to get the destination for. + * @return Returns the player's destination, which will never be null. + */ + public static String getDestination(Player player) { + return NullCoalescing.chain(() -> destSetting.getValue(player), ""); + } + +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchListener.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchListener.java new file mode 100644 index 000000000..69aa8eff1 --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchListener.java @@ -0,0 +1,121 @@ +package sh.okx.railswitch.switches; + +import java.util.Arrays; +import org.bukkit.Material; +import org.bukkit.Tag; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.Sign; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Minecart; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockRedstoneEvent; +import sh.okx.railswitch.glue.CitadelGlue; +import sh.okx.railswitch.settings.SettingsManager; +import vg.civcraft.mc.civmodcore.api.BlockAPI; +import vg.civcraft.mc.civmodcore.api.EntityAPI; + +/** + * Switch listener that implements switch functionality. + */ +public class SwitchListener implements Listener { + + public static final String WILDCARD = "*"; + + public static final CitadelGlue CITADEL_GLUE = new CitadelGlue(); + + /** + * Event handler for rail switches. Will determine if a switch exists at the target location, and if so will process + * it accordingly, allowing it to trigger or not trigger depending on the rider's set destination, the listed + * destinations on the switch, and the switch type. + * + * @param event The block redstone event to base the switch's existence on. + */ + @EventHandler + public void onSwitchTrigger(BlockRedstoneEvent event) { + Block block = event.getBlock(); + // Block must be a detector rail being triggered + if (!BlockAPI.isValidBlock(block) + || block.getType() != Material.DETECTOR_RAIL + || event.getNewCurrent() != 15) { + return; + } + // Check that the block above the rail is a sign + Block above = block.getRelative(BlockFace.UP); + if (!Tag.SIGNS.isTagged(above.getType()) + || !(above.getState() instanceof Sign)) { + return; + } + // Check that the sign has a valid switch type + String[] lines = ((Sign) above.getState()).getLines(); + SwitchType type = SwitchType.find(lines[0]); + if (type == null) { + return; + } + // Check that a player is triggering the switch + // NOTE: The event doesn't provide the information and so the next best thing is searching for a + // player who is nearby and riding a minecart. + Player player = null; { + double searchDistance = Double.MAX_VALUE; + for (Entity entity : block.getWorld().getNearbyEntities(block.getLocation(), 3, 3, 3)) { + if (!EntityAPI.isPlayer(entity)) { + continue; + } + Entity vehicle = entity.getVehicle(); + // TODO: This should be abstracted into CivModCore + if (vehicle == null + || vehicle.getType() != EntityType.MINECART + || !(vehicle instanceof Minecart)) { + continue; + } + double distance = block.getLocation().distanceSquared(entity.getLocation()); + if (distance < searchDistance) { + searchDistance = distance; + player = (Player) entity; + } + } + } + if (player == null) { + return; + } + // If Citadel is enabled, check that the sign and the rail are on the same group + if (CITADEL_GLUE.isEnabled()) { + if (!CITADEL_GLUE.doSignAndRailHaveSameReinforcement(above, block)) { + return; + } + } + // Determine whether a player has a destination that matches one of the destinations + // listed on the switch signs, or match if there's a wildcard. + boolean matched = false; + String[] playerDestinations = SettingsManager.getDestination(player).split(" "); + if (playerDestinations.length > 0) { + String[] switchDestinations = Arrays.copyOfRange(lines, 1, lines.length); + matcher: + for (String playerDestination : playerDestinations) { + if (playerDestination.equals(WILDCARD)) { + matched = true; + break; + } + for (String switchDestination : switchDestinations) { + if (switchDestination.equals(WILDCARD) + || playerDestination.equalsIgnoreCase(switchDestination)) { + matched = true; + break matcher; + } + } + } + } + switch (type) { + case NORMAL: + event.setNewCurrent(matched ? 15 : 0); + break; + case INVERTED: + event.setNewCurrent(matched ? 0 : 15); + break; + } + } + +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchType.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchType.java new file mode 100644 index 000000000..fea0fa1e5 --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchType.java @@ -0,0 +1,43 @@ +package sh.okx.railswitch.switches; + +import vg.civcraft.mc.civmodcore.util.TextUtil; + +/** + * Switch type matcher, will match a type to a tag. + */ +public enum SwitchType { + + NORMAL("[destination]"), + INVERTED("[!destination]"); + + private final String tag; + + /** + * Defines a new switch type by its tag. Should there be a duplicate tag, the {@link SwitchType#find(String) find()} + * function will return the first value found. + * + * @param tag The tag to associate with this type. + */ + SwitchType(String tag) { + this.tag = tag; + } + + /** + * Attempts to match a switch type to a switch tag. + * + * @param tag The tag to match with a type. + * @return Returns a match switch type, or null if none are found. + */ + public static SwitchType find(String tag) { + if (tag == null || tag.isEmpty()) { + return null; + } + for (SwitchType type : values()) { + if (TextUtil.stringEqualsIgnoreCase(tag, type.tag)) { + return type; + } + } + return null; + } + +} diff --git a/plugins/railswitch-paper/src/main/resources/config.yml b/plugins/railswitch-paper/src/main/resources/config.yml deleted file mode 100644 index d789c4f78..000000000 --- a/plugins/railswitch-paper/src/main/resources/config.yml +++ /dev/null @@ -1,13 +0,0 @@ -# can be mysql or sqlite -database-type: mysql - -mysql: - username: '' - host: localhost - password: '' - database: minecraft - port: 3306 -sqlite: - file-name: data.db - -timings: false \ No newline at end of file diff --git a/plugins/railswitch-paper/src/main/resources/plugin.yml b/plugins/railswitch-paper/src/main/resources/plugin.yml index 4a8b023c4..49553cd99 100644 --- a/plugins/railswitch-paper/src/main/resources/plugin.yml +++ b/plugins/railswitch-paper/src/main/resources/plugin.yml @@ -1,12 +1,10 @@ -name: RailSwitch -version: '1.3.1' -main: sh.okx.railswitch.RailSwitch -author: Okx -api-version: '1.14' -description: Easily make rail switches for automated destination selection +name: ${project.artifactId} +version: ${project.version} +main: ${plugin.main} +depend: [CivModCore] softdepend: [Citadel, NameLayer] +api-version: ${minecraft.version} +authors: [Okx, Protonull] +description: Easily make rail switches for automated destination selection commands: - setdestination: - usage: / - description: Set your rail destination - aliases: [dest, destination, switch, setswitch, setsw] \ No newline at end of file + dest: {} \ No newline at end of file From b6f326e52287609f3820ce8fc62aa8f241023b15 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 25 Apr 2020 14:28:41 +0100 Subject: [PATCH 53/78] Bug fix - Will no longer confirm the reset of your destination twice --- .../main/java/sh/okx/railswitch/settings/SettingsManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/SettingsManager.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/SettingsManager.java index 459ec99bb..ccd8d07d0 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/SettingsManager.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/SettingsManager.java @@ -55,7 +55,7 @@ public final class SettingsManager { if (Strings.isNullOrEmpty(destination)) { if (resetSetting != null) { resetSetting.resetPlayerDestination(player); - player.sendMessage(ChatColor.GREEN + "Your destination has been reset."); + // Do not put a message here since the message is sent in the method above. } else { player.sendMessage(ChatColor.RED + "Could not reset your destination."); From 36563de594e9c5b53a26b86a4ac7b788570221bb Mon Sep 17 00:00:00 2001 From: Maxopoly Date: Sat, 2 May 2020 05:11:26 +0200 Subject: [PATCH 54/78] Merge Ori rewrite in --- .../sh/okx/railswitch/RailSwitchPlugin.java | 93 ++++------- .../okx/railswitch/SetDestinationCommand.java | 84 ---------- .../commands/DestinationCommand.java | 23 +++ .../railswitch/database/ConnectionPool.java | 19 --- .../database/MySQLConnectionPool.java | 35 ---- .../database/RailSwitchDatabase.java | 150 ------------------ .../database/SQLiteConnectionPool.java | 31 ---- .../sh/okx/railswitch/glue/CitadelGlue.java | 67 ++++++++ .../settings/DestinationSetting.java | 27 ++++ .../railswitch/settings/RailSwitchMenu.java | 18 +++ .../okx/railswitch/settings/ResetSetting.java | 50 ++++++ .../railswitch/settings/SettingsManager.java | 85 ++++++++++ .../SwitchListener.java} | 89 +++-------- .../railswitch/{ => switches}/SwitchType.java | 8 +- .../src/main/resources/config.yml | 13 -- .../src/main/resources/plugin.yml | 14 +- 16 files changed, 333 insertions(+), 473 deletions(-) delete mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java create mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/commands/DestinationCommand.java delete mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/ConnectionPool.java delete mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/MySQLConnectionPool.java delete mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java delete mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/SQLiteConnectionPool.java create mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/glue/CitadelGlue.java create mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/DestinationSetting.java create mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/RailSwitchMenu.java create mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/ResetSetting.java create mode 100644 plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/SettingsManager.java rename plugins/railswitch-paper/src/main/java/sh/okx/railswitch/{DetectorRailActivateListener.java => switches/SwitchListener.java} (60%) rename plugins/railswitch-paper/src/main/java/sh/okx/railswitch/{ => switches}/SwitchType.java (83%) delete mode 100644 plugins/railswitch-paper/src/main/resources/config.yml diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java index c47ff5e66..eba21afa8 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java @@ -1,80 +1,43 @@ package sh.okx.railswitch; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.event.HandlerList; -import org.bukkit.plugin.java.JavaPlugin; -import sh.okx.railswitch.database.ConnectionPool; -import sh.okx.railswitch.database.MySQLConnectionPool; -import sh.okx.railswitch.database.RailSwitchDatabase; -import sh.okx.railswitch.database.SQLiteConnectionPool; +import org.bukkit.event.Listener; +import sh.okx.railswitch.commands.DestinationCommand; +import sh.okx.railswitch.glue.CitadelGlue; +import sh.okx.railswitch.settings.SettingsManager; +import sh.okx.railswitch.switches.SwitchListener; +import vg.civcraft.mc.civmodcore.ACivMod; +import vg.civcraft.mc.civmodcore.command.AikarCommandManager; /** * The Rail Switch plugin class */ -public class RailSwitchPlugin extends JavaPlugin { - - private boolean debug; - - private RailSwitchDatabase database; - +public final class RailSwitchPlugin extends ACivMod implements Listener { + + private static AikarCommandManager commands; + @Override public void onEnable() { + useNewCommandHandler = false; super.onEnable(); - saveDefaultConfig(); - loadDatabase(); - this.debug = getConfig().getBoolean("debug", getConfig().getBoolean("timings", false)); - getServer().getPluginManager().registerEvents(new DetectorRailActivateListener(this), this); - getCommand("setdestination").setExecutor(new SetDestinationCommand(this)); + SettingsManager.init(this); + registerListener(new CitadelGlue()); + registerListener(new SwitchListener()); + commands = new AikarCommandManager(this) { + @Override + public void registerCommands() { + registerCommand(new DestinationCommand()); + } + }; } - + @Override public void onDisable() { + SettingsManager.reset(); + if (commands != null) { + commands.reset(); + commands = null; + } super.onDisable(); - HandlerList.unregisterAll(this); } - - /** - * Retrieves the database connection setup that was created by {@link RailSwitchPlugin#loadDatabase()}. - * - * @return Returns the database connection setup. - */ - public RailSwitchDatabase getDatabase() { - return database; - } - - /** - * Create a database connection setup by the configuration. - */ - private void loadDatabase() { - ConfigurationSection config = getConfig(); - ConnectionPool pool; - String type = config.getString("database-type"); - if (type.equalsIgnoreCase("mysql")) { - pool = new MySQLConnectionPool( - config.getString("mysql.host"), - config.getInt("mysql.port"), - config.getString("mysql.database"), - config.getString("mysql.username"), - config.getString("mysql.password")); - } - else if (type.equalsIgnoreCase("sqlite")) { - pool = new SQLiteConnectionPool( - getDataFolder(), - config.getString("sqlite.file-name")); - } - else { - throw new RuntimeException("Invalid database-type in config.yml. Disabling plugin."); - } - this.database = new RailSwitchDatabase(pool, getLogger()); - } - - /** - * Determines whether the plugin is in debug mode. - * - * @return Returns whether the plugin is in debug mode. - */ - public boolean isDebug() { - return this.debug; - } - + } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java deleted file mode 100644 index 75e3cd0d4..000000000 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SetDestinationCommand.java +++ /dev/null @@ -1,84 +0,0 @@ -package sh.okx.railswitch; - -import com.google.common.base.Strings; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Pattern; -import org.bukkit.ChatColor; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - -/** - * The Set Destination command handler. - */ -public class SetDestinationCommand implements CommandExecutor { - - private static final Pattern validDestinationPattern = Pattern.compile( - "^[\\w!\"#$%&'()*+,-./;:<=>?@\\[\\]^`{|}~]{1,40}$"); - - private final RailSwitchPlugin plugin; - - /** - * Creates a new set destination command handler, passing through the current RailSwitch plugin instance. - * - * @param plugin The plugin to pass through to this command handler. - */ - public SetDestinationCommand(RailSwitchPlugin plugin) { - this.plugin = plugin; - } - - @Override - public boolean onCommand(CommandSender sender, Command command, String label, String[] arguments) { - if (!(sender instanceof Player)) { - return false; - } - - Player player = (Player) sender; - - // Convert - List destinations = new ArrayList<>(); - checker: - for (String argument : arguments) { - // If the argument is null or empty, it's likely from a double space, just ignore it - if (Strings.isNullOrEmpty(argument)) { - continue; - } - if (!validDestinationPattern.matcher(argument).matches()) { - player.sendMessage(ChatColor.RED + "Destinations can not be more than 40 characters and may only " + - "use alphanumerical characters, and ASCII symbols."); - return true; - } - for (String destination : destinations) { - if (argument.equalsIgnoreCase(destination)) { - continue checker; - } - } - destinations.add(argument); - } - - if (destinations.isEmpty()) { - player.sendMessage(ChatColor.GREEN + "Unset rail destination (use /" + label + - " to set your destination)."); - plugin.getDatabase().removePlayerDestination(player); - return true; - } - - String destination = String.join(" ", destinations); - - if (destination.length() > 40) { - player.sendMessage(ChatColor.RED + "Destinations as a whole cannot have more than 40 characters."); - return true; - } - - long start = System.currentTimeMillis(); - plugin.getDatabase().setPlayerDestination(player, destination); - if (plugin.isDebug()) { - plugin.getLogger().info("Set destination took " + (System.currentTimeMillis() - start) + "ms"); - } - player.sendMessage(ChatColor.GREEN + "Set your rail destination to: " + destination); - return true; - } - -} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/commands/DestinationCommand.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/commands/DestinationCommand.java new file mode 100644 index 000000000..38771161b --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/commands/DestinationCommand.java @@ -0,0 +1,23 @@ +package sh.okx.railswitch.commands; + +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Syntax; +import org.bukkit.entity.Player; +import sh.okx.railswitch.settings.SettingsManager; +import vg.civcraft.mc.civmodcore.command.AikarCommand; + +/** + * Continued support for setting and resetting your destination via a command. + */ +public final class DestinationCommand extends AikarCommand { + + @CommandAlias("dest|destination|setdestination|switch|setswitch|setsw") + @Description("Set your rail destination(s)") + @Syntax("[destination]") + public void onSetDestination(Player player, @Optional String destination) { + SettingsManager.setDestination(player, destination); + } + +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/ConnectionPool.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/ConnectionPool.java deleted file mode 100644 index 27189c467..000000000 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/ConnectionPool.java +++ /dev/null @@ -1,19 +0,0 @@ -package sh.okx.railswitch.database; - -import java.sql.Connection; -import java.sql.SQLException; - -/** - * Interface for creating connections to databases for {@link RailSwitchDatabase} - */ -public interface ConnectionPool { - - /** - * Gets the connection for this setup. - * - * @return Returns the connection. - * @throws SQLException Throws an exception if there's an error during the connection. - */ - Connection getConnection() throws SQLException; - -} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/MySQLConnectionPool.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/MySQLConnectionPool.java deleted file mode 100644 index c700bed7b..000000000 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/MySQLConnectionPool.java +++ /dev/null @@ -1,35 +0,0 @@ -package sh.okx.railswitch.database; - -import com.zaxxer.hikari.HikariDataSource; -import java.sql.Connection; -import java.sql.SQLException; - -/** - * Database connection implementation for MySQL databases. - */ -public class MySQLConnectionPool implements ConnectionPool { - - private final HikariDataSource hikariDataSource; - - /** - * Sets up a connection to a MySQL database. - * - * @param host The connection address - * @param port The connection port - * @param database The inner-database to use - * @param username The username to login with - * @param password The password to authenticate the login - */ - public MySQLConnectionPool(String host, int port, String database, String username, String password) { - hikariDataSource = new HikariDataSource(); - hikariDataSource.setJdbcUrl("jdbc:mysql://" + host + ":" + port + "/" + database); - hikariDataSource.setUsername(username); - hikariDataSource.setPassword(password); - } - - @Override - public Connection getConnection() throws SQLException { - return hikariDataSource.getConnection(); - } - -} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java deleted file mode 100644 index 3ba6a6182..000000000 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/RailSwitchDatabase.java +++ /dev/null @@ -1,150 +0,0 @@ -package sh.okx.railswitch.database; - -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.Optional; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.logging.Level; -import java.util.logging.Logger; -import org.bukkit.entity.Player; - -/** - * RailSwitch database class - */ -public class RailSwitchDatabase { - - private static final String DESTINATION_TABLE = "rsw_dest"; - - private ConnectionPool pool; - - private Logger logger; - - private String setPlayerDestination; - - private String getPlayerDestination; - - private String removePlayerDestination; - - private ExecutorService service = Executors.newSingleThreadExecutor(); - - private final LoadingCache> destinations = CacheBuilder.newBuilder(). - maximumSize(1000). - build(new CacheLoader>() { - @Override - public Optional load(Player key) throws Exception { - try (Connection connection = pool.getConnection()) { - PreparedStatement statement = connection.prepareStatement(getPlayerDestination); - statement.setString(1, key.getUniqueId().toString()); - ResultSet resultSet = statement.executeQuery(); - if (resultSet.next()) { - return Optional.of(resultSet.getString("dest")); - } - else { - return Optional.empty(); - } - } - } - }); - - /** - * Creates a new Rail Switch database manager - * - * @param pool The database connection to manage - * @param logger The logger to log to - */ - public RailSwitchDatabase(ConnectionPool pool, Logger logger) { - this.logger = logger; - this.pool = pool; - createTable(); - loadStatements(); - } - - private void createTable() { - try (Connection connection = this.pool.getConnection()) { - connection.createStatement().executeUpdate( - "CREATE TABLE IF NOT EXISTS `" + DESTINATION_TABLE + "` (" + - "`uuid` VARCHAR(36) NOT NULL," + - "`dest` VARCHAR(40) NOT NULL," + - "PRIMARY KEY (`uuid`))"); - } - catch (SQLException e) { - this.logger.log(Level.SEVERE, "Could not create tables", e); - } - } - - private void loadStatements() { - this.setPlayerDestination = String.format( - "REPLACE INTO `%1$s` VALUES (?, ?)", - DESTINATION_TABLE); - this.getPlayerDestination = String.format( - "SELECT `dest` FROM `%1$s` WHERE `uuid`=?", - DESTINATION_TABLE); - this.removePlayerDestination = String.format( - "DELETE FROM `%1$s` WHERE `uuid`=?", - DESTINATION_TABLE); - } - - /** - * Sets a player's destination. - * - * @param player The player to set the destination for - * @param destination The destination to set - */ - public void setPlayerDestination(Player player, String destination) { - this.destinations.put(player, Optional.of(destination)); - this.service.execute(() -> { - try (Connection connection = this.pool.getConnection()) { - PreparedStatement statement = connection.prepareStatement(this.setPlayerDestination); - statement.setString(1, player.getUniqueId().toString()); - statement.setString(2, destination); - statement.executeUpdate(); - } - catch (SQLException e) { - this.logger.log(Level.SEVERE, "Could not set destination", e); - } - }); - } - - /** - * Resetting a player's destination by removing their entry - * - * @param player The player to reset - */ - public void removePlayerDestination(Player player) { - this.destinations.put(player, Optional.empty()); - this.service.execute(() -> { - try (Connection connection = this.pool.getConnection()) { - PreparedStatement statement = connection.prepareStatement(this.removePlayerDestination); - statement.setString(1, player.getUniqueId().toString()); - statement.executeUpdate(); - } - catch (SQLException e) { - this.logger.log(Level.SEVERE, "Could not remove destination", e); - } - }); - } - - /** - * Retrieves a player's set destination - * - * @param player The player to get the destination of - * @return Returns an the player's set destinations in a optional string form - */ - public Optional getPlayerDestination(Player player) { - try { - return this.destinations.get(player); - } - catch (ExecutionException exception) { - this.logger.log(Level.SEVERE, "Could not get destination", exception); - return Optional.empty(); - } - } - -} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/SQLiteConnectionPool.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/SQLiteConnectionPool.java deleted file mode 100644 index baaae64bb..000000000 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/database/SQLiteConnectionPool.java +++ /dev/null @@ -1,31 +0,0 @@ -package sh.okx.railswitch.database; - -import java.io.File; -import java.nio.file.Path; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; - -/** - * Database connection implementation for SQLite databases. - */ -public class SQLiteConnectionPool implements ConnectionPool { - - private Path file; - - /** - * Sets up a connection to an SQLite database. - * - * @param folder The folder that the database is located in - * @param file The database file itself - */ - public SQLiteConnectionPool(File folder, String file) { - this.file = folder.toPath().resolve(file); - } - - @Override - public Connection getConnection() throws SQLException { - return DriverManager.getConnection("jdbc:sqlite:" + file); - } - -} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/glue/CitadelGlue.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/glue/CitadelGlue.java new file mode 100644 index 000000000..f348ef10c --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/glue/CitadelGlue.java @@ -0,0 +1,67 @@ +package sh.okx.railswitch.glue; + +import org.bukkit.block.Block; +import vg.civcraft.mc.citadel.Citadel; +import vg.civcraft.mc.citadel.ReinforcementManager; +import vg.civcraft.mc.citadel.model.Reinforcement; +import vg.civcraft.mc.civmodcore.api.BlockAPI; +import vg.civcraft.mc.civmodcore.util.DependencyGlue; +import vg.civcraft.mc.civmodcore.util.NullCoalescing; + +/** + * Glue for Citadel. + */ +public final class CitadelGlue extends DependencyGlue { + + private ReinforcementManager manager; + + public CitadelGlue() { + super("Citadel"); + } + + @Override + public boolean isSafeToUse() { + if (!super.isSafeToUse()) { + return false; + } + if (this.manager == null) { + return false; + } + return true; + } + + @Override + protected void onGlueEnabled() { + this.manager = Citadel.getInstance().getReinforcementManager(); + } + + @Override + protected void onGlueDisabled() { + this.manager = null; + } + + /** + * Do two blocks, representing the sign and the rail, have the same reinforcement group? + * + * @param sign The block representing the sign. + * @param rail The block representing the rail. + * @return Returns true both blocks share the same reinforcement group, or if both are un-reinforced. + */ + public boolean doSignAndRailHaveSameReinforcement(Block sign, Block rail) { + if (!isSafeToUse() || !BlockAPI.isValidBlock(sign) || !BlockAPI.isValidBlock(rail)) { + return false; + } + Reinforcement signReinforcement = this.manager.getReinforcement(sign); + Reinforcement railReinforcement = this.manager.getReinforcement(rail); + if (signReinforcement == null && railReinforcement == null) { + return true; + } + if (signReinforcement == null || railReinforcement == null) { + return false; + } + return NullCoalescing.equalsNotNull( + signReinforcement.getGroup(), + railReinforcement.getGroup()); + } + +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/DestinationSetting.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/DestinationSetting.java new file mode 100644 index 000000000..b5f0dc43f --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/DestinationSetting.java @@ -0,0 +1,27 @@ +package sh.okx.railswitch.settings; + +import com.google.common.base.Strings; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.java.JavaPlugin; +import vg.civcraft.mc.civmodcore.playersettings.impl.StringSetting; + +/** + * Setting representing a player's destination. + */ +public final class DestinationSetting extends StringSetting { + + public DestinationSetting(JavaPlugin plugin) { + super(plugin, "", "Destination", "dest", new ItemStack(Material.MINECART), + "The destination(s) that will be used to route you at rail junctions."); + } + + @Override + public String toText(String value) { + if (Strings.isNullOrEmpty(value)) { + return ""; + } + return value; + } + +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/RailSwitchMenu.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/RailSwitchMenu.java new file mode 100644 index 000000000..0708d63af --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/RailSwitchMenu.java @@ -0,0 +1,18 @@ +package sh.okx.railswitch.settings; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.playersettings.PlayerSettingAPI; +import vg.civcraft.mc.civmodcore.playersettings.gui.MenuSection; + +/** + * The menu for RailSwitch. This is what all RailSwitch settings will be registered to. + */ +public final class RailSwitchMenu extends MenuSection { + + public RailSwitchMenu() { + super("RailSwitch", "Settings relating to RailSwitch", PlayerSettingAPI.getMainMenu(), + new ItemStack(Material.RAIL)); + } + +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/ResetSetting.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/ResetSetting.java new file mode 100644 index 000000000..8bff68c78 --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/ResetSetting.java @@ -0,0 +1,50 @@ +package sh.okx.railswitch.settings; + +import java.util.UUID; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.java.JavaPlugin; +import vg.civcraft.mc.civmodcore.playersettings.gui.MenuSection; +import vg.civcraft.mc.civmodcore.playersettings.impl.StringSetting; + +/** + * ResetSetting, a setting that functions more like a GUI button than an actual setting. + */ +public final class ResetSetting extends StringSetting { + + private final DestinationSetting destinationSetting; + + public ResetSetting(JavaPlugin plugin, DestinationSetting destinationSetting) { + super(plugin, "", "Clear Destination", "dest", new ItemStack(Material.BARRIER), "Clears your destination."); + this.destinationSetting = destinationSetting; + } + + @Override + public void handleMenuClick(Player player, MenuSection menu) { + resetPlayerDestination(player); + menu.showScreen(player); + } + + /** + * Resets a player's destination value. + * + * @param player The player to reset the destination for. + */ + public void resetPlayerDestination(Player player) { + this.destinationSetting.setValue(player, ""); + player.sendMessage(ChatColor.GREEN + "Your destination has been reset."); + } + + @Override + public String getValue(UUID player) { + return this.destinationSetting.getValue(player); + } + + @Override + public String toText(String value) { + return this.destinationSetting.toText(value); + } + +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/SettingsManager.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/SettingsManager.java new file mode 100644 index 000000000..ccd8d07d0 --- /dev/null +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/SettingsManager.java @@ -0,0 +1,85 @@ +package sh.okx.railswitch.settings; + +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import sh.okx.railswitch.RailSwitchPlugin; +import vg.civcraft.mc.civmodcore.util.NullCoalescing; + +/** + * Manages the initialisation and registration of menu settings. + */ +public final class SettingsManager { + + private static RailSwitchMenu menu; + + private static DestinationSetting destSetting; + + private static ResetSetting resetSetting; + + /** + * Initialise the settings manager. This should only be called within RailSwitch onEnable(). + * + * @param plugin The enabled RailSwitch plugin instance. + */ + public static void init(RailSwitchPlugin plugin) { + // Create the menu elements + menu = new RailSwitchMenu(); + destSetting = new DestinationSetting(plugin); + resetSetting = new ResetSetting(plugin, destSetting); + // Register those elements + menu.registerToParentMenu(); + menu.registerSetting(destSetting); + menu.registerSetting(resetSetting); + } + + /** + * Gracefully resets the settings manager. This should only be called within RailSwitch onDisable(). + */ + public static void reset() { + // TODO: Deregister and unload all the menu elements once PlayerSettingAPI becomes reload safe + menu = null; + destSetting = null; + resetSetting = null; + } + + /** + * Sets a player's destination. This is for when the player settings don't themselves provide a way to do so. + * + * @param player The player to set the destination to. + * @param destination The destination to set. + */ + public static void setDestination(Player player, String destination) { + Preconditions.checkArgument(player != null); + if (Strings.isNullOrEmpty(destination)) { + if (resetSetting != null) { + resetSetting.resetPlayerDestination(player); + // Do not put a message here since the message is sent in the method above. + } + else { + player.sendMessage(ChatColor.RED + "Could not reset your destination."); + } + } + else { + if (destSetting != null) { + destSetting.setValue(player, destination); + player.sendMessage(ChatColor.GREEN + "Destination set to: " + destination); + } + else { + player.sendMessage(ChatColor.RED + "Could not set your destination."); + } + } + } + + /** + * Gets a player's destination. + * + * @param player The player to get the destination for. + * @return Returns the player's destination, which will never be null. + */ + public static String getDestination(Player player) { + return NullCoalescing.chain(() -> destSetting.getValue(player), ""); + } + +} diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/DetectorRailActivateListener.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchListener.java similarity index 60% rename from plugins/railswitch-paper/src/main/java/sh/okx/railswitch/DetectorRailActivateListener.java rename to plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchListener.java index 4e410165d..69aa8eff1 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/DetectorRailActivateListener.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchListener.java @@ -1,9 +1,8 @@ -package sh.okx.railswitch; +package sh.okx.railswitch.switches; import java.util.Arrays; -import java.util.Objects; -import org.bukkit.Bukkit; import org.bukkit.Material; +import org.bukkit.Tag; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.Sign; @@ -14,29 +13,20 @@ import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.block.BlockRedstoneEvent; -import vg.civcraft.mc.citadel.Citadel; -import vg.civcraft.mc.citadel.ReinforcementManager; -import vg.civcraft.mc.citadel.reinforcement.PlayerReinforcement; -import vg.civcraft.mc.citadel.reinforcement.Reinforcement; +import sh.okx.railswitch.glue.CitadelGlue; +import sh.okx.railswitch.settings.SettingsManager; +import vg.civcraft.mc.civmodcore.api.BlockAPI; +import vg.civcraft.mc.civmodcore.api.EntityAPI; /** - * Event listener to enact the functionality of rail switches. + * Switch listener that implements switch functionality. */ -public class DetectorRailActivateListener implements Listener { - - private static final String WILDCARD = "*"; - - private final RailSwitchPlugin plugin; - - /** - * Creates a new event listener, passing through the current RailSwitch plugin instance. - * - * @param plugin The current RailSwitch plugin instance. - */ - public DetectorRailActivateListener(RailSwitchPlugin plugin) { - this.plugin = plugin; - } - +public class SwitchListener implements Listener { + + public static final String WILDCARD = "*"; + + public static final CitadelGlue CITADEL_GLUE = new CitadelGlue(); + /** * Event handler for rail switches. Will determine if a switch exists at the target location, and if so will process * it accordingly, allowing it to trigger or not trigger depending on the rider's set destination, the listed @@ -45,42 +35,37 @@ public class DetectorRailActivateListener implements Listener { * @param event The block redstone event to base the switch's existence on. */ @EventHandler - public void onRailSwitch(BlockRedstoneEvent event) { + public void onSwitchTrigger(BlockRedstoneEvent event) { Block block = event.getBlock(); - // Block must be a detector rail being triggered - if (block == null + if (!BlockAPI.isValidBlock(block) || block.getType() != Material.DETECTOR_RAIL || event.getNewCurrent() != 15) { return; } - // Check that the block above the rail is a sign Block above = block.getRelative(BlockFace.UP); - if (above == null - || above.getType() != Material.WALL_SIGN + if (!Tag.SIGNS.isTagged(above.getType()) || !(above.getState() instanceof Sign)) { return; } - // Check that the sign has a valid switch type String[] lines = ((Sign) above.getState()).getLines(); SwitchType type = SwitchType.find(lines[0]); if (type == null) { return; } - // Check that a player is triggering the switch // NOTE: The event doesn't provide the information and so the next best thing is searching for a // player who is nearby and riding a minecart. Player player = null; { double searchDistance = Double.MAX_VALUE; for (Entity entity : block.getWorld().getNearbyEntities(block.getLocation(), 3, 3, 3)) { - if (entity.getType() != EntityType.PLAYER - || !(entity instanceof Player)) { + if (!EntityAPI.isPlayer(entity)) { continue; } Entity vehicle = entity.getVehicle(); + // TODO: This should be abstracted into CivModCore if (vehicle == null || vehicle.getType() != EntityType.MINECART || !(vehicle instanceof Minecart)) { @@ -96,38 +81,17 @@ public class DetectorRailActivateListener implements Listener { if (player == null) { return; } - // If Citadel is enabled, check that the sign and the rail are on the same group - if (Bukkit.getPluginManager().isPluginEnabled("Citadel")) { - ReinforcementManager rein = Citadel.getReinforcementManager(); - Reinforcement railReinforcement = rein.getReinforcement(block); - Reinforcement signReinforcement = rein.getReinforcement(above); - // Allow the switch to work if the rail and the sign are BOTH unreinforced - // otherwise servers with established rail networks will suddenly not work - // if Citadel is added. - if (railReinforcement != null || signReinforcement != null) { - if (!(railReinforcement instanceof PlayerReinforcement)) { - return; - } - if (!(signReinforcement instanceof PlayerReinforcement)) { - return; - } - if (!Objects.equals( - ((PlayerReinforcement) railReinforcement).getGroup(), - ((PlayerReinforcement) signReinforcement).getGroup())) { - return; - } + if (CITADEL_GLUE.isEnabled()) { + if (!CITADEL_GLUE.doSignAndRailHaveSameReinforcement(above, block)) { + return; } } - // Determine whether a player has a destination that matches one of the destinations // listed on the switch signs, or match if there's a wildcard. boolean matched = false; - String[] playerDestinations = plugin.getDatabase(). - getPlayerDestination(player). - map(dest -> dest.split(" ")). - orElse(null); - if (playerDestinations != null && playerDestinations.length > 0) { + String[] playerDestinations = SettingsManager.getDestination(player).split(" "); + if (playerDestinations.length > 0) { String[] switchDestinations = Arrays.copyOfRange(lines, 1, lines.length); matcher: for (String playerDestination : playerDestinations) { @@ -144,7 +108,6 @@ public class DetectorRailActivateListener implements Listener { } } } - switch (type) { case NORMAL: event.setNewCurrent(matched ? 15 : 0); @@ -152,11 +115,7 @@ public class DetectorRailActivateListener implements Listener { case INVERTED: event.setNewCurrent(matched ? 0 : 15); break; - // Gotta have this or the Devoted style checker will screech - default: - break; } - } - + } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SwitchType.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchType.java similarity index 83% rename from plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SwitchType.java rename to plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchType.java index eb3cf99d3..fea0fa1e5 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/SwitchType.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchType.java @@ -1,4 +1,6 @@ -package sh.okx.railswitch; +package sh.okx.railswitch.switches; + +import vg.civcraft.mc.civmodcore.util.TextUtil; /** * Switch type matcher, will match a type to a tag. @@ -8,7 +10,7 @@ public enum SwitchType { NORMAL("[destination]"), INVERTED("[!destination]"); - private String tag; + private final String tag; /** * Defines a new switch type by its tag. Should there be a duplicate tag, the {@link SwitchType#find(String) find()} @@ -31,7 +33,7 @@ public enum SwitchType { return null; } for (SwitchType type : values()) { - if (tag.equalsIgnoreCase(type.tag)) { + if (TextUtil.stringEqualsIgnoreCase(tag, type.tag)) { return type; } } diff --git a/plugins/railswitch-paper/src/main/resources/config.yml b/plugins/railswitch-paper/src/main/resources/config.yml deleted file mode 100644 index adf25687e..000000000 --- a/plugins/railswitch-paper/src/main/resources/config.yml +++ /dev/null @@ -1,13 +0,0 @@ -debug: false - -# can be mysql or sqlite -database-type: mysql - -mysql: - username: '' - host: localhost - password: '' - database: minecraft - port: 3306 -sqlite: - file-name: data.db diff --git a/plugins/railswitch-paper/src/main/resources/plugin.yml b/plugins/railswitch-paper/src/main/resources/plugin.yml index 1549d3367..49553cd99 100644 --- a/plugins/railswitch-paper/src/main/resources/plugin.yml +++ b/plugins/railswitch-paper/src/main/resources/plugin.yml @@ -1,12 +1,10 @@ name: ${project.artifactId} version: ${project.version} -main: ${project.groupId}.${project.artifactId}Plugin -author: Okx -api-version: '1.12' -description: Easily make rail switches for automated destination selection +main: ${plugin.main} +depend: [CivModCore] softdepend: [Citadel, NameLayer] +api-version: ${minecraft.version} +authors: [Okx, Protonull] +description: Easily make rail switches for automated destination selection commands: - setdestination: - usage: / - description: Set your rail destination - aliases: [dest, destination, switch, setswitch, setsw] + dest: {} \ No newline at end of file From bf8f6eb56513ae86e323b1fe9eedcdfd31b574fb Mon Sep 17 00:00:00 2001 From: Maxopoly Date: Sat, 2 May 2020 05:12:26 +0200 Subject: [PATCH 55/78] Fix pom --- plugins/railswitch-paper/pom.xml | 226 ++++++++++++++++++------------- 1 file changed, 132 insertions(+), 94 deletions(-) diff --git a/plugins/railswitch-paper/pom.xml b/plugins/railswitch-paper/pom.xml index 3c0ba1786..332fe998d 100644 --- a/plugins/railswitch-paper/pom.xml +++ b/plugins/railswitch-paper/pom.xml @@ -1,100 +1,138 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 + http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + sh.okx.railswitch + RailSwitch + jar + 1.4.0 + RailSwitch + https://github.com/civclassic/railswitch - sh.okx.railswitch - RailSwitch - jar - 1.3.5 + + 1.8 + UTF-8 + UTF-8 + ${project.groupId}.${project.artifactId}Plugin + 1.14 + - RailSwitch - https://github.com/civclassic/railswitch + + ${project.artifactId}-${project.version}-${minecraft.version} + + + ${basedir} + + LICENSE.txt + README.md + + + + src/main/resources/ + true + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.compile.version} + ${java.compile.version} + + + + org.apache.maven.plugins + maven-source-plugin + 3.1.0 + + + attach-sources + verify + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + false + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.1 + + + package + + shade + + + false + + + + Maven + + + + + + + + + - - 1.8 - 1.8 - UTF-8 - UTF-8 - - - - - - ${basedir}/src/main/resources - - *.yml - - true - - - ${basedir} - - LICENSE.txt - - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.1.0 - - - https://build.devotedmc.com/job/Style-guide-master/lastSuccessfulBuild/artifact/src/main/resources/devoted_checks.xml - - true - false - - - - checkstyle - - checkstyle - - prepare-package - - - - - - - - - org.spigotmc - spigot-api - 1.12.2-R0.1-SNAPSHOT - provided - - - vg.civcraft.mc.civmodcore - CivModCore - 1.6.2 - provided - - - vg.civcraft.mc.namelayer - NameLayer - 2.11.2 - provided - - - vg.civcraft.mc.citadel - Citadel - 3.11.4 - provided - - - - - - spigot-repo - https://hub.spigotmc.org/nexus/content/repositories/snapshots/ - - - devoted-repo - http://build.devotedmc.com/plugin/repository/everything/ - - + + + org.spigotmc + spigot-api + 1.14.4-R0.1-SNAPSHOT + provided + + + vg.civcraft.mc.civmodcore + CivModCore + 1.7.8 + provided + + + vg.civcraft.mc.namelayer + NameLayer + 2.13.1 + provided + + + vg.civcraft.mc.citadel + Citadel + 4.0.1 + provided + + + + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + devoted-repo + http://build.devotedmc.com/plugin/repository/everything/ + + + From c02e8faedd7821265db5af4cc19fe2615090a722 Mon Sep 17 00:00:00 2001 From: Maxopoly Date: Fri, 29 May 2020 06:31:12 +0200 Subject: [PATCH 56/78] Add jenkins stuff --- plugins/railswitch-paper/Jenkinsfile | 21 ++++++ plugins/railswitch-paper/pom.xml | 103 +++------------------------ 2 files changed, 32 insertions(+), 92 deletions(-) create mode 100644 plugins/railswitch-paper/Jenkinsfile diff --git a/plugins/railswitch-paper/Jenkinsfile b/plugins/railswitch-paper/Jenkinsfile new file mode 100644 index 000000000..f5080232d --- /dev/null +++ b/plugins/railswitch-paper/Jenkinsfile @@ -0,0 +1,21 @@ +pipeline { + agent any + tools { + maven 'Maven 3.6.3' + jdk 'Java 8' + } + stages { + stage ('Build') { + steps { + sh 'mvn -U clean install deploy -P civ-jenkins' + } + } + } + post { + always { + withCredentials([string(credentialsId: 'civclassic-discord-webhook', variable: 'DISCORD_WEBHOOK')]) { + discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}](${env.BUILD_URL})\n", footer: 'Civclassic Jenkins', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), title: "${env.JOB_NAME} #${currentBuild.id}", webhookURL: DISCORD_WEBHOOK + } + } + } +} diff --git a/plugins/railswitch-paper/pom.xml b/plugins/railswitch-paper/pom.xml index 332fe998d..364cb87d6 100644 --- a/plugins/railswitch-paper/pom.xml +++ b/plugins/railswitch-paper/pom.xml @@ -2,6 +2,13 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + + + com.github.civclassic + civclassic-parent + 1.0.0 + + sh.okx.railswitch RailSwitch jar @@ -9,94 +16,6 @@ RailSwitch https://github.com/civclassic/railswitch - - 1.8 - UTF-8 - UTF-8 - ${project.groupId}.${project.artifactId}Plugin - 1.14 - - - - ${project.artifactId}-${project.version}-${minecraft.version} - - - ${basedir} - - LICENSE.txt - README.md - - - - src/main/resources/ - true - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - ${java.compile.version} - ${java.compile.version} - - - - org.apache.maven.plugins - maven-source-plugin - 3.1.0 - - - attach-sources - verify - - jar-no-fork - - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.2.0 - - - false - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.22.2 - - - org.apache.maven.plugins - maven-shade-plugin - 3.2.1 - - - package - - shade - - - false - - - - Maven - - - - - - - - - - org.spigotmc @@ -107,7 +26,7 @@ vg.civcraft.mc.civmodcore CivModCore - 1.7.8 + 1.7.9 provided @@ -119,7 +38,7 @@ vg.civcraft.mc.citadel Citadel - 4.0.1 + 4.0.2 provided @@ -130,8 +49,8 @@ https://hub.spigotmc.org/nexus/content/repositories/snapshots/ - devoted-repo - http://build.devotedmc.com/plugin/repository/everything/ + civ-jenkins + https://repo.civclassic.com/repository/internal/ From 2d1bf0e6273345f143f9e00a1a67d02a44e20254 Mon Sep 17 00:00:00 2001 From: Maxopoly Date: Fri, 29 May 2020 06:41:39 +0200 Subject: [PATCH 57/78] Remove spigot repo --- plugins/railswitch-paper/pom.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plugins/railswitch-paper/pom.xml b/plugins/railswitch-paper/pom.xml index 364cb87d6..948bb3525 100644 --- a/plugins/railswitch-paper/pom.xml +++ b/plugins/railswitch-paper/pom.xml @@ -44,10 +44,6 @@ - - spigot-repo - https://hub.spigotmc.org/nexus/content/repositories/snapshots/ - civ-jenkins https://repo.civclassic.com/repository/internal/ From 0f7095ff7bb35ef1e9181ebf4de449b62eddcf48 Mon Sep 17 00:00:00 2001 From: Maxopoly Date: Thu, 11 Jun 2020 23:39:49 +0200 Subject: [PATCH 58/78] Update Jenkinsfile --- plugins/railswitch-paper/Jenkinsfile | 33 ++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/plugins/railswitch-paper/Jenkinsfile b/plugins/railswitch-paper/Jenkinsfile index f5080232d..b8823b479 100644 --- a/plugins/railswitch-paper/Jenkinsfile +++ b/plugins/railswitch-paper/Jenkinsfile @@ -1,21 +1,46 @@ +@Library('civ_pipeline_lib')_ + pipeline { agent any tools { maven 'Maven 3.6.3' jdk 'Java 8' + } + environment { + civ_dependent_plugins = "" } stages { stage ('Build') { steps { - sh 'mvn -U clean install deploy -P civ-jenkins' + civ_build_plugin() + } + } + stage ('Archive binaries') { + steps { + civ_archive_artifacts() + } + } + stage ('Archive javadoc') { + steps { + civ_archive_javadoc() + } + } + stage ('Aggregate reports') { + steps { + civ_aggregate_reports() } } } + post { always { - withCredentials([string(credentialsId: 'civclassic-discord-webhook', variable: 'DISCORD_WEBHOOK')]) { - discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}](${env.BUILD_URL})\n", footer: 'Civclassic Jenkins', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), title: "${env.JOB_NAME} #${currentBuild.id}", webhookURL: DISCORD_WEBHOOK - } + civ_post_always() + } + success { + civ_post_success() + } + failure { + civ_post_failure() } } } From e4b4507d473d5932a8acc46a25c0098def5191be Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 23 Jun 2020 17:55:10 +0100 Subject: [PATCH 59/78] GetDestination fix chain() doesn't ensure a non-null value, but rather ensures that *a* value is returned, so if the chained statement returns null then that's accepted --- .../java/sh/okx/railswitch/settings/SettingsManager.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/SettingsManager.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/SettingsManager.java index ccd8d07d0..d50c6bcb0 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/SettingsManager.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/SettingsManager.java @@ -5,7 +5,6 @@ import com.google.common.base.Strings; import org.bukkit.ChatColor; import org.bukkit.entity.Player; import sh.okx.railswitch.RailSwitchPlugin; -import vg.civcraft.mc.civmodcore.util.NullCoalescing; /** * Manages the initialisation and registration of menu settings. @@ -79,7 +78,11 @@ public final class SettingsManager { * @return Returns the player's destination, which will never be null. */ public static String getDestination(Player player) { - return NullCoalescing.chain(() -> destSetting.getValue(player), ""); + String value = destSetting.getValue(player); + if (value == null) { + return ""; + } + return value; } } From 72a1f67e3a7a42fc9bb411d69e77e13322129142 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 26 Jun 2020 23:12:27 +0100 Subject: [PATCH 60/78] plugin.yml :: plugin enable fix When the pom.xml was transitions over to civclassic's parent pom, the properties that set the plugin path and the api version were removed. --- plugins/railswitch-paper/src/main/resources/plugin.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/railswitch-paper/src/main/resources/plugin.yml b/plugins/railswitch-paper/src/main/resources/plugin.yml index 49553cd99..bf0f942ac 100644 --- a/plugins/railswitch-paper/src/main/resources/plugin.yml +++ b/plugins/railswitch-paper/src/main/resources/plugin.yml @@ -1,9 +1,9 @@ name: ${project.artifactId} version: ${project.version} -main: ${plugin.main} +main: sh.okx.railswitch.RailSwitchPlugin depend: [CivModCore] softdepend: [Citadel, NameLayer] -api-version: ${minecraft.version} +api-version: 1.14 authors: [Okx, Protonull] description: Easily make rail switches for automated destination selection commands: From 40505700b9d750f504d447e831cf1dc6d1d027cb Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 28 Jun 2020 00:49:30 +0100 Subject: [PATCH 61/78] SwitchListener :: okay really this time okay, so there were two bugs that caused the same problem and empty strings matching with the empty lines of a sign, ugh --- .../sh/okx/railswitch/switches/SwitchListener.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchListener.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchListener.java index 69aa8eff1..363058dab 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchListener.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchListener.java @@ -1,5 +1,6 @@ package sh.okx.railswitch.switches; +import com.google.common.base.Strings; import java.util.Arrays; import org.bukkit.Material; import org.bukkit.Tag; @@ -90,16 +91,23 @@ public class SwitchListener implements Listener { // Determine whether a player has a destination that matches one of the destinations // listed on the switch signs, or match if there's a wildcard. boolean matched = false; - String[] playerDestinations = SettingsManager.getDestination(player).split(" "); - if (playerDestinations.length > 0) { + String setDest = SettingsManager.getDestination(player); + if (!Strings.isNullOrEmpty(setDest)) { + String[] playerDestinations = setDest.split(" "); String[] switchDestinations = Arrays.copyOfRange(lines, 1, lines.length); matcher: for (String playerDestination : playerDestinations) { + if (Strings.isNullOrEmpty(playerDestination)) { + continue; + } if (playerDestination.equals(WILDCARD)) { matched = true; break; } for (String switchDestination : switchDestinations) { + if (Strings.isNullOrEmpty(switchDestination)) { + continue; + } if (switchDestination.equals(WILDCARD) || playerDestination.equalsIgnoreCase(switchDestination)) { matched = true; From 1dc66fd4006f41f979cda700a0e965b259cdaf04 Mon Sep 17 00:00:00 2001 From: Maxopoly Date: Tue, 28 Jul 2020 04:56:29 +0200 Subject: [PATCH 62/78] Update to 1.16 --- plugins/railswitch-paper/pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/railswitch-paper/pom.xml b/plugins/railswitch-paper/pom.xml index 948bb3525..63693de60 100644 --- a/plugins/railswitch-paper/pom.xml +++ b/plugins/railswitch-paper/pom.xml @@ -12,7 +12,7 @@ sh.okx.railswitch RailSwitch jar - 1.4.0 + 1.5.0 RailSwitch https://github.com/civclassic/railswitch @@ -20,25 +20,25 @@ org.spigotmc spigot-api - 1.14.4-R0.1-SNAPSHOT + 1.16.1-R0.1-SNAPSHOT provided vg.civcraft.mc.civmodcore CivModCore - 1.7.9 + 1.8.0 provided vg.civcraft.mc.namelayer NameLayer - 2.13.1 + 2.14.0 provided vg.civcraft.mc.citadel Citadel - 4.0.2 + 4.1.0 provided From aad00b5e667558baed3c37b1bd73918a89e44174 Mon Sep 17 00:00:00 2001 From: Maxopoly Date: Tue, 28 Jul 2020 04:56:29 +0200 Subject: [PATCH 63/78] Update to 1.16 --- plugins/railswitch-paper/pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/railswitch-paper/pom.xml b/plugins/railswitch-paper/pom.xml index 948bb3525..63693de60 100644 --- a/plugins/railswitch-paper/pom.xml +++ b/plugins/railswitch-paper/pom.xml @@ -12,7 +12,7 @@ sh.okx.railswitch RailSwitch jar - 1.4.0 + 1.5.0 RailSwitch https://github.com/civclassic/railswitch @@ -20,25 +20,25 @@ org.spigotmc spigot-api - 1.14.4-R0.1-SNAPSHOT + 1.16.1-R0.1-SNAPSHOT provided vg.civcraft.mc.civmodcore CivModCore - 1.7.9 + 1.8.0 provided vg.civcraft.mc.namelayer NameLayer - 2.13.1 + 2.14.0 provided vg.civcraft.mc.citadel Citadel - 4.0.2 + 4.1.0 provided From e49aab4dc142fac849367386e72ec8aea74749c0 Mon Sep 17 00:00:00 2001 From: Maxopoly Date: Tue, 29 Dec 2020 02:51:04 +0100 Subject: [PATCH 64/78] Update to 1.16.4 --- .../railswitch-paper/{LICENSE.md => LICENSE.txt} | 0 plugins/railswitch-paper/pom.xml | 16 ++++++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) rename plugins/railswitch-paper/{LICENSE.md => LICENSE.txt} (100%) diff --git a/plugins/railswitch-paper/LICENSE.md b/plugins/railswitch-paper/LICENSE.txt similarity index 100% rename from plugins/railswitch-paper/LICENSE.md rename to plugins/railswitch-paper/LICENSE.txt diff --git a/plugins/railswitch-paper/pom.xml b/plugins/railswitch-paper/pom.xml index 63693de60..3bf8e7f54 100644 --- a/plugins/railswitch-paper/pom.xml +++ b/plugins/railswitch-paper/pom.xml @@ -12,33 +12,33 @@ sh.okx.railswitch RailSwitch jar - 1.5.0 + 1.5.1 RailSwitch https://github.com/civclassic/railswitch - org.spigotmc - spigot-api - 1.16.1-R0.1-SNAPSHOT + com.destroystokyo.paper + paper-api + 1.16.4-R0.1-SNAPSHOT provided vg.civcraft.mc.civmodcore CivModCore - 1.8.0 + 1.8.1 provided vg.civcraft.mc.namelayer NameLayer - 2.14.0 + 2.14.1 provided vg.civcraft.mc.citadel Citadel - 4.1.0 + 4.1.1 provided @@ -46,7 +46,7 @@ civ-jenkins - https://repo.civclassic.com/repository/internal/ + https://build.devotedmc.com/plugin/repository/everything/ From 348261713084def6191a8afbec0dd634c51479ab Mon Sep 17 00:00:00 2001 From: Maxopoly Date: Fri, 1 Jan 2021 02:18:47 +0100 Subject: [PATCH 65/78] Update repo to new github one --- plugins/railswitch-paper/pom.xml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/plugins/railswitch-paper/pom.xml b/plugins/railswitch-paper/pom.xml index 3bf8e7f54..3b22f808f 100644 --- a/plugins/railswitch-paper/pom.xml +++ b/plugins/railswitch-paper/pom.xml @@ -1,14 +1,15 @@ - 4.0.0 - - - com.github.civclassic - civclassic-parent - 1.0.0 - - + + + com.github.civclassic + civclassic-parent + 1.0.0 + + sh.okx.railswitch RailSwitch jar @@ -45,8 +46,8 @@ - civ-jenkins - https://build.devotedmc.com/plugin/repository/everything/ + civ-github-repo + https://raw.githubusercontent.com/CivClassic/artifacts/master/ From 3df8d3b17f9c2d7420e39d7ce7bf6d0c2e92d8c3 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 22 Jan 2021 21:40:17 +0000 Subject: [PATCH 66/78] Update to CivModCore 1.8.2 --- plugins/railswitch-paper/pom.xml | 2 +- .../src/main/java/sh/okx/railswitch/glue/CitadelGlue.java | 8 ++++---- .../java/sh/okx/railswitch/switches/SwitchListener.java | 8 ++++---- .../main/java/sh/okx/railswitch/switches/SwitchType.java | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/plugins/railswitch-paper/pom.xml b/plugins/railswitch-paper/pom.xml index 3b22f808f..21257be7b 100644 --- a/plugins/railswitch-paper/pom.xml +++ b/plugins/railswitch-paper/pom.xml @@ -27,7 +27,7 @@ vg.civcraft.mc.civmodcore CivModCore - 1.8.1 + 1.8.2 provided diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/glue/CitadelGlue.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/glue/CitadelGlue.java index f348ef10c..be403d6e1 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/glue/CitadelGlue.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/glue/CitadelGlue.java @@ -4,9 +4,9 @@ import org.bukkit.block.Block; import vg.civcraft.mc.citadel.Citadel; import vg.civcraft.mc.citadel.ReinforcementManager; import vg.civcraft.mc.citadel.model.Reinforcement; -import vg.civcraft.mc.civmodcore.api.BlockAPI; import vg.civcraft.mc.civmodcore.util.DependencyGlue; -import vg.civcraft.mc.civmodcore.util.NullCoalescing; +import vg.civcraft.mc.civmodcore.util.NullUtils; +import vg.civcraft.mc.civmodcore.world.WorldUtils; /** * Glue for Citadel. @@ -48,7 +48,7 @@ public final class CitadelGlue extends DependencyGlue { * @return Returns true both blocks share the same reinforcement group, or if both are un-reinforced. */ public boolean doSignAndRailHaveSameReinforcement(Block sign, Block rail) { - if (!isSafeToUse() || !BlockAPI.isValidBlock(sign) || !BlockAPI.isValidBlock(rail)) { + if (!isSafeToUse() || !WorldUtils.isValidBlock(sign) || !WorldUtils.isValidBlock(rail)) { return false; } Reinforcement signReinforcement = this.manager.getReinforcement(sign); @@ -59,7 +59,7 @@ public final class CitadelGlue extends DependencyGlue { if (signReinforcement == null || railReinforcement == null) { return false; } - return NullCoalescing.equalsNotNull( + return NullUtils.equalsNotNull( signReinforcement.getGroup(), railReinforcement.getGroup()); } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchListener.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchListener.java index 363058dab..074d8b690 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchListener.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchListener.java @@ -16,8 +16,8 @@ import org.bukkit.event.Listener; import org.bukkit.event.block.BlockRedstoneEvent; import sh.okx.railswitch.glue.CitadelGlue; import sh.okx.railswitch.settings.SettingsManager; -import vg.civcraft.mc.civmodcore.api.BlockAPI; -import vg.civcraft.mc.civmodcore.api.EntityAPI; +import vg.civcraft.mc.civmodcore.entities.EntityUtils; +import vg.civcraft.mc.civmodcore.world.WorldUtils; /** * Switch listener that implements switch functionality. @@ -39,7 +39,7 @@ public class SwitchListener implements Listener { public void onSwitchTrigger(BlockRedstoneEvent event) { Block block = event.getBlock(); // Block must be a detector rail being triggered - if (!BlockAPI.isValidBlock(block) + if (!WorldUtils.isValidBlock(block) || block.getType() != Material.DETECTOR_RAIL || event.getNewCurrent() != 15) { return; @@ -62,7 +62,7 @@ public class SwitchListener implements Listener { Player player = null; { double searchDistance = Double.MAX_VALUE; for (Entity entity : block.getWorld().getNearbyEntities(block.getLocation(), 3, 3, 3)) { - if (!EntityAPI.isPlayer(entity)) { + if (!EntityUtils.isPlayer(entity)) { continue; } Entity vehicle = entity.getVehicle(); diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchType.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchType.java index fea0fa1e5..e5dfeb668 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchType.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchType.java @@ -1,6 +1,6 @@ package sh.okx.railswitch.switches; -import vg.civcraft.mc.civmodcore.util.TextUtil; +import org.apache.commons.lang3.StringUtils; /** * Switch type matcher, will match a type to a tag. @@ -33,7 +33,7 @@ public enum SwitchType { return null; } for (SwitchType type : values()) { - if (TextUtil.stringEqualsIgnoreCase(tag, type.tag)) { + if (StringUtils.equalsIgnoreCase(tag, type.tag)) { return type; } } From c3b8577eb5c329ea36e305c212997b48d9f11f03 Mon Sep 17 00:00:00 2001 From: Cola Date: Wed, 30 Jun 2021 20:19:43 +0100 Subject: [PATCH 67/78] v1.6.0 - 1.17 & Java 16 --- plugins/railswitch-paper/pom.xml | 31 ++++++++++--------- .../sh/okx/railswitch/RailSwitchPlugin.java | 27 ++++++++-------- .../commands/DestinationCommand.java | 4 +-- .../sh/okx/railswitch/glue/CitadelGlue.java | 31 +++++++++---------- .../settings/DestinationSetting.java | 2 +- .../railswitch/settings/RailSwitchMenu.java | 4 +-- .../okx/railswitch/settings/ResetSetting.java | 4 +-- .../railswitch/switches/SwitchListener.java | 8 ++--- .../src/main/resources/plugin.yml | 4 +-- 9 files changed, 58 insertions(+), 57 deletions(-) diff --git a/plugins/railswitch-paper/pom.xml b/plugins/railswitch-paper/pom.xml index 21257be7b..40d5d50f9 100644 --- a/plugins/railswitch-paper/pom.xml +++ b/plugins/railswitch-paper/pom.xml @@ -10,36 +10,35 @@ 1.0.0 - sh.okx.railswitch RailSwitch jar - 1.5.1 + 1.6.0 RailSwitch https://github.com/civclassic/railswitch + + io.papermc.paper + paper + 1.17-R0.1-SNAPSHOT + provided + - com.destroystokyo.paper - paper-api - 1.16.4-R0.1-SNAPSHOT - provided - - - vg.civcraft.mc.civmodcore + com.github.civclassic CivModCore - 1.8.2 + 1.9.0 provided - vg.civcraft.mc.namelayer + com.github.civclassic NameLayer - 2.14.1 + 2.15.0 provided - vg.civcraft.mc.citadel + com.github.civclassic Citadel - 4.1.1 + 4.2.0 provided @@ -49,6 +48,10 @@ civ-github-repo https://raw.githubusercontent.com/CivClassic/artifacts/master/ + + papermc + https://papermc.io/repo/repository/maven-public/ + diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java index eba21afa8..c3aba1b58 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java @@ -1,41 +1,40 @@ package sh.okx.railswitch; import org.bukkit.event.Listener; -import sh.okx.railswitch.commands.DestinationCommand; import sh.okx.railswitch.glue.CitadelGlue; import sh.okx.railswitch.settings.SettingsManager; import sh.okx.railswitch.switches.SwitchListener; import vg.civcraft.mc.civmodcore.ACivMod; -import vg.civcraft.mc.civmodcore.command.AikarCommandManager; +import vg.civcraft.mc.civmodcore.commands.CommandManager; /** * The Rail Switch plugin class */ public final class RailSwitchPlugin extends ACivMod implements Listener { - private static AikarCommandManager commands; + private CommandManager commandManager; @Override public void onEnable() { - useNewCommandHandler = false; super.onEnable(); SettingsManager.init(this); - registerListener(new CitadelGlue()); + registerListener(new CitadelGlue(this)); registerListener(new SwitchListener()); - commands = new AikarCommandManager(this) { - @Override - public void registerCommands() { - registerCommand(new DestinationCommand()); - } - }; + commandManager = new CommandManager(this); + commandManager.init(); + registerCommands(); + } + + private void registerCommands() { + } @Override public void onDisable() { SettingsManager.reset(); - if (commands != null) { - commands.reset(); - commands = null; + if (commandManager != null) { + commandManager.reset(); + commandManager = null; } super.onDisable(); } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/commands/DestinationCommand.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/commands/DestinationCommand.java index 38771161b..766c3bd96 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/commands/DestinationCommand.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/commands/DestinationCommand.java @@ -1,17 +1,17 @@ package sh.okx.railswitch.commands; +import co.aikar.commands.BaseCommand; import co.aikar.commands.annotation.CommandAlias; import co.aikar.commands.annotation.Description; import co.aikar.commands.annotation.Optional; import co.aikar.commands.annotation.Syntax; import org.bukkit.entity.Player; import sh.okx.railswitch.settings.SettingsManager; -import vg.civcraft.mc.civmodcore.command.AikarCommand; /** * Continued support for setting and resetting your destination via a command. */ -public final class DestinationCommand extends AikarCommand { +public final class DestinationCommand extends BaseCommand { @CommandAlias("dest|destination|setdestination|switch|setswitch|setsw") @Description("Set your rail destination(s)") diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/glue/CitadelGlue.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/glue/CitadelGlue.java index be403d6e1..dfea913d1 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/glue/CitadelGlue.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/glue/CitadelGlue.java @@ -1,11 +1,12 @@ package sh.okx.railswitch.glue; import org.bukkit.block.Block; +import org.bukkit.plugin.Plugin; import vg.civcraft.mc.citadel.Citadel; import vg.civcraft.mc.citadel.ReinforcementManager; import vg.civcraft.mc.citadel.model.Reinforcement; -import vg.civcraft.mc.civmodcore.util.DependencyGlue; -import vg.civcraft.mc.civmodcore.util.NullUtils; +import vg.civcraft.mc.civmodcore.utilities.DependencyGlue; +import vg.civcraft.mc.civmodcore.utilities.NullUtils; import vg.civcraft.mc.civmodcore.world.WorldUtils; /** @@ -15,13 +16,12 @@ public final class CitadelGlue extends DependencyGlue { private ReinforcementManager manager; - public CitadelGlue() { - super("Citadel"); + public CitadelGlue(Plugin plugin) { + super(plugin, "Citadel"); } - @Override public boolean isSafeToUse() { - if (!super.isSafeToUse()) { + if (!super.isDependencyEnabled()) { return false; } if (this.manager == null) { @@ -30,16 +30,6 @@ public final class CitadelGlue extends DependencyGlue { return true; } - @Override - protected void onGlueEnabled() { - this.manager = Citadel.getInstance().getReinforcementManager(); - } - - @Override - protected void onGlueDisabled() { - this.manager = null; - } - /** * Do two blocks, representing the sign and the rail, have the same reinforcement group? * @@ -64,4 +54,13 @@ public final class CitadelGlue extends DependencyGlue { railReinforcement.getGroup()); } + @Override + protected void onDependencyEnabled() { + this.manager = Citadel.getInstance().getReinforcementManager(); + } + + @Override + protected void onDependencyDisabled() { + this.manager = null; + } } diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/DestinationSetting.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/DestinationSetting.java index b5f0dc43f..095298bf8 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/DestinationSetting.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/DestinationSetting.java @@ -4,7 +4,7 @@ import com.google.common.base.Strings; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; -import vg.civcraft.mc.civmodcore.playersettings.impl.StringSetting; +import vg.civcraft.mc.civmodcore.players.settings.impl.StringSetting; /** * Setting representing a player's destination. diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/RailSwitchMenu.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/RailSwitchMenu.java index 0708d63af..3265958f8 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/RailSwitchMenu.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/RailSwitchMenu.java @@ -2,8 +2,8 @@ package sh.okx.railswitch.settings; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; -import vg.civcraft.mc.civmodcore.playersettings.PlayerSettingAPI; -import vg.civcraft.mc.civmodcore.playersettings.gui.MenuSection; +import vg.civcraft.mc.civmodcore.players.settings.PlayerSettingAPI; +import vg.civcraft.mc.civmodcore.players.settings.gui.MenuSection; /** * The menu for RailSwitch. This is what all RailSwitch settings will be registered to. diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/ResetSetting.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/ResetSetting.java index 8bff68c78..5389e002d 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/ResetSetting.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/ResetSetting.java @@ -6,8 +6,8 @@ import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; -import vg.civcraft.mc.civmodcore.playersettings.gui.MenuSection; -import vg.civcraft.mc.civmodcore.playersettings.impl.StringSetting; +import vg.civcraft.mc.civmodcore.players.settings.gui.MenuSection; +import vg.civcraft.mc.civmodcore.players.settings.impl.StringSetting; /** * ResetSetting, a setting that functions more like a GUI button than an actual setting. diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchListener.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchListener.java index 074d8b690..ea4d3868e 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchListener.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchListener.java @@ -14,9 +14,9 @@ import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.block.BlockRedstoneEvent; +import sh.okx.railswitch.RailSwitchPlugin; import sh.okx.railswitch.glue.CitadelGlue; import sh.okx.railswitch.settings.SettingsManager; -import vg.civcraft.mc.civmodcore.entities.EntityUtils; import vg.civcraft.mc.civmodcore.world.WorldUtils; /** @@ -26,7 +26,7 @@ public class SwitchListener implements Listener { public static final String WILDCARD = "*"; - public static final CitadelGlue CITADEL_GLUE = new CitadelGlue(); + public static final CitadelGlue CITADEL_GLUE = new CitadelGlue(RailSwitchPlugin.getPlugin(RailSwitchPlugin.class)); /** * Event handler for rail switches. Will determine if a switch exists at the target location, and if so will process @@ -62,7 +62,7 @@ public class SwitchListener implements Listener { Player player = null; { double searchDistance = Double.MAX_VALUE; for (Entity entity : block.getWorld().getNearbyEntities(block.getLocation(), 3, 3, 3)) { - if (!EntityUtils.isPlayer(entity)) { + if (!(entity instanceof Player)) { continue; } Entity vehicle = entity.getVehicle(); @@ -83,7 +83,7 @@ public class SwitchListener implements Listener { return; } // If Citadel is enabled, check that the sign and the rail are on the same group - if (CITADEL_GLUE.isEnabled()) { + if (CITADEL_GLUE.isSafeToUse()) { if (!CITADEL_GLUE.doSignAndRailHaveSameReinforcement(above, block)) { return; } diff --git a/plugins/railswitch-paper/src/main/resources/plugin.yml b/plugins/railswitch-paper/src/main/resources/plugin.yml index bf0f942ac..ed87e72cd 100644 --- a/plugins/railswitch-paper/src/main/resources/plugin.yml +++ b/plugins/railswitch-paper/src/main/resources/plugin.yml @@ -3,8 +3,8 @@ version: ${project.version} main: sh.okx.railswitch.RailSwitchPlugin depend: [CivModCore] softdepend: [Citadel, NameLayer] -api-version: 1.14 +api-version: 1.17 authors: [Okx, Protonull] description: Easily make rail switches for automated destination selection commands: - dest: {} \ No newline at end of file + dest: {} From dc6a068e10171c8c9bf676831137e6ced3f991a1 Mon Sep 17 00:00:00 2001 From: Cola Date: Wed, 30 Jun 2021 21:58:57 +0100 Subject: [PATCH 68/78] POM fix --- plugins/railswitch-paper/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/railswitch-paper/pom.xml b/plugins/railswitch-paper/pom.xml index 40d5d50f9..fd2ae40ea 100644 --- a/plugins/railswitch-paper/pom.xml +++ b/plugins/railswitch-paper/pom.xml @@ -7,7 +7,7 @@ com.github.civclassic civclassic-parent - 1.0.0 + 1.0.1 RailSwitch From 2af423c979b6e586ea92809cb33d7b3022a024fd Mon Sep 17 00:00:00 2001 From: Cola Date: Thu, 1 Jul 2021 19:44:15 +0100 Subject: [PATCH 69/78] Register dest command --- .../src/main/java/sh/okx/railswitch/RailSwitchPlugin.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java index c3aba1b58..597dfa6b9 100644 --- a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java +++ b/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java @@ -1,6 +1,7 @@ package sh.okx.railswitch; import org.bukkit.event.Listener; +import sh.okx.railswitch.commands.DestinationCommand; import sh.okx.railswitch.glue.CitadelGlue; import sh.okx.railswitch.settings.SettingsManager; import sh.okx.railswitch.switches.SwitchListener; @@ -22,11 +23,7 @@ public final class RailSwitchPlugin extends ACivMod implements Listener { registerListener(new SwitchListener()); commandManager = new CommandManager(this); commandManager.init(); - registerCommands(); - } - - private void registerCommands() { - + commandManager.registerCommand(new DestinationCommand()); } @Override From 9abf50191299afa86d7828c7771780f08dfe128d Mon Sep 17 00:00:00 2001 From: Cola Date: Mon, 19 Jul 2021 00:05:03 +0100 Subject: [PATCH 70/78] 1.17.1 --- plugins/railswitch-paper/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/railswitch-paper/pom.xml b/plugins/railswitch-paper/pom.xml index fd2ae40ea..04428201c 100644 --- a/plugins/railswitch-paper/pom.xml +++ b/plugins/railswitch-paper/pom.xml @@ -20,7 +20,7 @@ io.papermc.paper paper - 1.17-R0.1-SNAPSHOT + 1.17.1-R0.1-SNAPSHOT provided From ceeddea99c3190d39fd215bc28d9aeb13b5bb18b Mon Sep 17 00:00:00 2001 From: Cola Date: Fri, 17 Dec 2021 03:27:21 +0000 Subject: [PATCH 71/78] Gradlefication / 1.18 --- plugins/railswitch-paper/.gitignore | 2 + plugins/railswitch-paper/Jenkinsfile | 46 ---- plugins/railswitch-paper/build.gradle.kts | 84 +++++++ .../gradle/wrapper/gradle-wrapper.properties | 5 + plugins/railswitch-paper/gradlew | 234 ++++++++++++++++++ plugins/railswitch-paper/pom.xml | 57 ----- plugins/railswitch-paper/settings.gradle.kts | 12 + 7 files changed, 337 insertions(+), 103 deletions(-) delete mode 100644 plugins/railswitch-paper/Jenkinsfile create mode 100644 plugins/railswitch-paper/build.gradle.kts create mode 100644 plugins/railswitch-paper/gradle/wrapper/gradle-wrapper.properties create mode 100644 plugins/railswitch-paper/gradlew delete mode 100644 plugins/railswitch-paper/pom.xml create mode 100644 plugins/railswitch-paper/settings.gradle.kts diff --git a/plugins/railswitch-paper/.gitignore b/plugins/railswitch-paper/.gitignore index 391808759..1ad0b5ef4 100644 --- a/plugins/railswitch-paper/.gitignore +++ b/plugins/railswitch-paper/.gitignore @@ -34,6 +34,8 @@ # Gradle .idea/**/gradle.xml .idea/**/libraries +.gradle +build # Gradle and Maven with auto-import # When using Gradle or Maven with auto-import, you should exclude module files, diff --git a/plugins/railswitch-paper/Jenkinsfile b/plugins/railswitch-paper/Jenkinsfile deleted file mode 100644 index b8823b479..000000000 --- a/plugins/railswitch-paper/Jenkinsfile +++ /dev/null @@ -1,46 +0,0 @@ -@Library('civ_pipeline_lib')_ - -pipeline { - agent any - tools { - maven 'Maven 3.6.3' - jdk 'Java 8' - } - environment { - civ_dependent_plugins = "" - } - stages { - stage ('Build') { - steps { - civ_build_plugin() - } - } - stage ('Archive binaries') { - steps { - civ_archive_artifacts() - } - } - stage ('Archive javadoc') { - steps { - civ_archive_javadoc() - } - } - stage ('Aggregate reports') { - steps { - civ_aggregate_reports() - } - } - } - - post { - always { - civ_post_always() - } - success { - civ_post_success() - } - failure { - civ_post_failure() - } - } -} diff --git a/plugins/railswitch-paper/build.gradle.kts b/plugins/railswitch-paper/build.gradle.kts new file mode 100644 index 000000000..2e661c53f --- /dev/null +++ b/plugins/railswitch-paper/build.gradle.kts @@ -0,0 +1,84 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +plugins { + `java-library` + `maven-publish` + id("io.papermc.paperweight.userdev") version "1.3.1" +} + +group = "net.civmc" +version = "1.7.0-SNAPSHOT" +description = "RailSwitch" + +repositories { + fun civRepo(name: String) { + maven { + url = uri("https://maven.pkg.github.com/CivMC/${name}") + credentials { + // These need to be set in the user environment variables + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } + } + mavenCentral() + civRepo("CivModCore") + civRepo("NameLayer") + civRepo("Citadel") +} + +dependencies { + paperDevBundle("1.18-R0.1-SNAPSHOT") + + implementation("net.civmc:civmodcore:2.0.0-SNAPSHOT:dev-all") + implementation("net.civmc:namelayer-spigot:3.0.0-SNAPSHOT:dev") + implementation("net.civmc:citadel:5.0.0-SNAPSHOT:dev") +} + +java { + toolchain.languageVersion.set(JavaLanguageVersion.of(17)) +} + +tasks { + build { + dependsOn(reobfJar) + } + + compileJava { + options.encoding = Charsets.UTF_8.name() // We want UTF-8 for everything + + // Set the release flag. This configures what version bytecode the compiler will emit, as well as what JDK APIs are usable. + // See https://openjdk.java.net/jeps/247 for more information. + options.release.set(17) + } + javadoc { + options.encoding = Charsets.UTF_8.name() // We want UTF-8 for everything + } + processResources { + filteringCharset = Charsets.UTF_8.name() // We want UTF-8 for everything + } + + test { + useJUnitPlatform() + } +} + +publishing { + repositories { + maven { + name = "GitHubPackages" + url = uri("https://maven.pkg.github.com/CivMC/RailSwitch") + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } + } + publications { + register("gpr") { + from(components["java"]) + } + } +} diff --git a/plugins/railswitch-paper/gradle/wrapper/gradle-wrapper.properties b/plugins/railswitch-paper/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..84d1f85fd --- /dev/null +++ b/plugins/railswitch-paper/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/plugins/railswitch-paper/gradlew b/plugins/railswitch-paper/gradlew new file mode 100644 index 000000000..c53aefaa5 --- /dev/null +++ b/plugins/railswitch-paper/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/plugins/railswitch-paper/pom.xml b/plugins/railswitch-paper/pom.xml deleted file mode 100644 index 04428201c..000000000 --- a/plugins/railswitch-paper/pom.xml +++ /dev/null @@ -1,57 +0,0 @@ - - 4.0.0 - - - com.github.civclassic - civclassic-parent - 1.0.1 - - - RailSwitch - jar - 1.6.0 - RailSwitch - https://github.com/civclassic/railswitch - - - - io.papermc.paper - paper - 1.17.1-R0.1-SNAPSHOT - provided - - - com.github.civclassic - CivModCore - 1.9.0 - provided - - - com.github.civclassic - NameLayer - 2.15.0 - provided - - - com.github.civclassic - Citadel - 4.2.0 - provided - - - - - - civ-github-repo - https://raw.githubusercontent.com/CivClassic/artifacts/master/ - - - papermc - https://papermc.io/repo/repository/maven-public/ - - - - diff --git a/plugins/railswitch-paper/settings.gradle.kts b/plugins/railswitch-paper/settings.gradle.kts new file mode 100644 index 000000000..62a3bbdfe --- /dev/null +++ b/plugins/railswitch-paper/settings.gradle.kts @@ -0,0 +1,12 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +rootProject.name = "railswitch" + +pluginManagement { + repositories { + gradlePluginPortal() + maven("https://papermc.io/repo/repository/maven-public/") + } +} From edf4b2c5e9cf947d01476ad703772c8f86b87adc Mon Sep 17 00:00:00 2001 From: AngrySoundTech Date: Sun, 19 Dec 2021 12:59:22 -0500 Subject: [PATCH 72/78] Add workflow tasks --- .../.github/workflows/pull_request.yml | 7 ++ .../.github/workflows/release_created.yml | 8 ++ plugins/railswitch-paper/.gitignore | 3 + plugins/railswitch-paper/build.gradle.kts | 2 +- .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59536 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- plugins/railswitch-paper/gradlew | 10 +- plugins/railswitch-paper/gradlew.bat | 89 ++++++++++++++++++ 8 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 plugins/railswitch-paper/.github/workflows/pull_request.yml create mode 100644 plugins/railswitch-paper/.github/workflows/release_created.yml create mode 100644 plugins/railswitch-paper/gradle/wrapper/gradle-wrapper.jar mode change 100644 => 100755 plugins/railswitch-paper/gradlew create mode 100644 plugins/railswitch-paper/gradlew.bat diff --git a/plugins/railswitch-paper/.github/workflows/pull_request.yml b/plugins/railswitch-paper/.github/workflows/pull_request.yml new file mode 100644 index 000000000..042464808 --- /dev/null +++ b/plugins/railswitch-paper/.github/workflows/pull_request.yml @@ -0,0 +1,7 @@ +name: Build +on: + pull_request: + +jobs: + call_build: + uses: CivMC/.github/.github/workflows/gradle_build.yml@master diff --git a/plugins/railswitch-paper/.github/workflows/release_created.yml b/plugins/railswitch-paper/.github/workflows/release_created.yml new file mode 100644 index 000000000..5bbdc60db --- /dev/null +++ b/plugins/railswitch-paper/.github/workflows/release_created.yml @@ -0,0 +1,8 @@ +name: Publish +on: + release: + types: [created] + +jobs: + call_publish: + uses: CivMC/.github/.github/workflows/gradle_publish.yml@master diff --git a/plugins/railswitch-paper/.gitignore b/plugins/railswitch-paper/.gitignore index 1ad0b5ef4..3379ab4d6 100644 --- a/plugins/railswitch-paper/.gitignore +++ b/plugins/railswitch-paper/.gitignore @@ -209,3 +209,6 @@ dependency-reduced-pom.xml /target/ /bin/ + +!gradlew.bat +!gradle-wrapper.jar diff --git a/plugins/railswitch-paper/build.gradle.kts b/plugins/railswitch-paper/build.gradle.kts index 2e661c53f..fb148841d 100644 --- a/plugins/railswitch-paper/build.gradle.kts +++ b/plugins/railswitch-paper/build.gradle.kts @@ -9,7 +9,7 @@ plugins { } group = "net.civmc" -version = "1.7.0-SNAPSHOT" +version = "2.0.0-SNAPSHOT" description = "RailSwitch" repositories { diff --git a/plugins/railswitch-paper/gradle/wrapper/gradle-wrapper.jar b/plugins/railswitch-paper/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7454180f2ae8848c63b8b4dea2cb829da983f2fa GIT binary patch literal 59536 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL literal 0 HcmV?d00001 diff --git a/plugins/railswitch-paper/gradle/wrapper/gradle-wrapper.properties b/plugins/railswitch-paper/gradle/wrapper/gradle-wrapper.properties index 84d1f85fd..d2880ba80 100644 --- a/plugins/railswitch-paper/gradle/wrapper/gradle-wrapper.properties +++ b/plugins/railswitch-paper/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/plugins/railswitch-paper/gradlew b/plugins/railswitch-paper/gradlew old mode 100644 new mode 100755 index c53aefaa5..1b6c78733 --- a/plugins/railswitch-paper/gradlew +++ b/plugins/railswitch-paper/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -32,10 +32,10 @@ # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # diff --git a/plugins/railswitch-paper/gradlew.bat b/plugins/railswitch-paper/gradlew.bat new file mode 100644 index 000000000..107acd32c --- /dev/null +++ b/plugins/railswitch-paper/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From baf12b3bbceaf0c9e65e1f2e3e46e5d8888aa043 Mon Sep 17 00:00:00 2001 From: Cola Date: Thu, 30 Dec 2021 03:58:16 +0000 Subject: [PATCH 73/78] CivGradle / 1.18.1 --- .../src/main/java/sh/okx/railswitch/RailSwitchPlugin.java | 0 .../main/java/sh/okx/railswitch/commands/DestinationCommand.java | 0 .../src/main/java/sh/okx/railswitch/glue/CitadelGlue.java | 0 .../main/java/sh/okx/railswitch/settings/DestinationSetting.java | 0 .../src/main/java/sh/okx/railswitch/settings/RailSwitchMenu.java | 0 .../src/main/java/sh/okx/railswitch/settings/ResetSetting.java | 0 .../src/main/java/sh/okx/railswitch/settings/SettingsManager.java | 0 .../src/main/java/sh/okx/railswitch/switches/SwitchListener.java | 0 .../src/main/java/sh/okx/railswitch/switches/SwitchType.java | 0 .../railswitch-paper/{ => paper}/src/main/resources/plugin.yml | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename plugins/railswitch-paper/{ => paper}/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java (100%) rename plugins/railswitch-paper/{ => paper}/src/main/java/sh/okx/railswitch/commands/DestinationCommand.java (100%) rename plugins/railswitch-paper/{ => paper}/src/main/java/sh/okx/railswitch/glue/CitadelGlue.java (100%) rename plugins/railswitch-paper/{ => paper}/src/main/java/sh/okx/railswitch/settings/DestinationSetting.java (100%) rename plugins/railswitch-paper/{ => paper}/src/main/java/sh/okx/railswitch/settings/RailSwitchMenu.java (100%) rename plugins/railswitch-paper/{ => paper}/src/main/java/sh/okx/railswitch/settings/ResetSetting.java (100%) rename plugins/railswitch-paper/{ => paper}/src/main/java/sh/okx/railswitch/settings/SettingsManager.java (100%) rename plugins/railswitch-paper/{ => paper}/src/main/java/sh/okx/railswitch/switches/SwitchListener.java (100%) rename plugins/railswitch-paper/{ => paper}/src/main/java/sh/okx/railswitch/switches/SwitchType.java (100%) rename plugins/railswitch-paper/{ => paper}/src/main/resources/plugin.yml (100%) diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java b/plugins/railswitch-paper/paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java similarity index 100% rename from plugins/railswitch-paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java rename to plugins/railswitch-paper/paper/src/main/java/sh/okx/railswitch/RailSwitchPlugin.java diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/commands/DestinationCommand.java b/plugins/railswitch-paper/paper/src/main/java/sh/okx/railswitch/commands/DestinationCommand.java similarity index 100% rename from plugins/railswitch-paper/src/main/java/sh/okx/railswitch/commands/DestinationCommand.java rename to plugins/railswitch-paper/paper/src/main/java/sh/okx/railswitch/commands/DestinationCommand.java diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/glue/CitadelGlue.java b/plugins/railswitch-paper/paper/src/main/java/sh/okx/railswitch/glue/CitadelGlue.java similarity index 100% rename from plugins/railswitch-paper/src/main/java/sh/okx/railswitch/glue/CitadelGlue.java rename to plugins/railswitch-paper/paper/src/main/java/sh/okx/railswitch/glue/CitadelGlue.java diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/DestinationSetting.java b/plugins/railswitch-paper/paper/src/main/java/sh/okx/railswitch/settings/DestinationSetting.java similarity index 100% rename from plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/DestinationSetting.java rename to plugins/railswitch-paper/paper/src/main/java/sh/okx/railswitch/settings/DestinationSetting.java diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/RailSwitchMenu.java b/plugins/railswitch-paper/paper/src/main/java/sh/okx/railswitch/settings/RailSwitchMenu.java similarity index 100% rename from plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/RailSwitchMenu.java rename to plugins/railswitch-paper/paper/src/main/java/sh/okx/railswitch/settings/RailSwitchMenu.java diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/ResetSetting.java b/plugins/railswitch-paper/paper/src/main/java/sh/okx/railswitch/settings/ResetSetting.java similarity index 100% rename from plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/ResetSetting.java rename to plugins/railswitch-paper/paper/src/main/java/sh/okx/railswitch/settings/ResetSetting.java diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/SettingsManager.java b/plugins/railswitch-paper/paper/src/main/java/sh/okx/railswitch/settings/SettingsManager.java similarity index 100% rename from plugins/railswitch-paper/src/main/java/sh/okx/railswitch/settings/SettingsManager.java rename to plugins/railswitch-paper/paper/src/main/java/sh/okx/railswitch/settings/SettingsManager.java diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchListener.java b/plugins/railswitch-paper/paper/src/main/java/sh/okx/railswitch/switches/SwitchListener.java similarity index 100% rename from plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchListener.java rename to plugins/railswitch-paper/paper/src/main/java/sh/okx/railswitch/switches/SwitchListener.java diff --git a/plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchType.java b/plugins/railswitch-paper/paper/src/main/java/sh/okx/railswitch/switches/SwitchType.java similarity index 100% rename from plugins/railswitch-paper/src/main/java/sh/okx/railswitch/switches/SwitchType.java rename to plugins/railswitch-paper/paper/src/main/java/sh/okx/railswitch/switches/SwitchType.java diff --git a/plugins/railswitch-paper/src/main/resources/plugin.yml b/plugins/railswitch-paper/paper/src/main/resources/plugin.yml similarity index 100% rename from plugins/railswitch-paper/src/main/resources/plugin.yml rename to plugins/railswitch-paper/paper/src/main/resources/plugin.yml From 71c31a1af6c407ba35e3ca3a39d296cd2d0e96a8 Mon Sep 17 00:00:00 2001 From: Cola Date: Thu, 30 Dec 2021 03:58:27 +0000 Subject: [PATCH 74/78] Part 2 electric boogaloo --- plugins/railswitch-paper/build.gradle.kts | 104 +++++++----------- .../railswitch-paper/paper/build.gradle.kts | 19 ++++ .../paper/src/main/resources/plugin.yml | 4 +- plugins/railswitch-paper/settings.gradle.kts | 21 ++-- 4 files changed, 72 insertions(+), 76 deletions(-) create mode 100644 plugins/railswitch-paper/paper/build.gradle.kts diff --git a/plugins/railswitch-paper/build.gradle.kts b/plugins/railswitch-paper/build.gradle.kts index fb148841d..08a547c39 100644 --- a/plugins/railswitch-paper/build.gradle.kts +++ b/plugins/railswitch-paper/build.gradle.kts @@ -1,84 +1,56 @@ -/* - * This file was generated by the Gradle 'init' task. - */ +import net.civmc.civgradle.common.util.civRepo plugins { `java-library` `maven-publish` - id("io.papermc.paperweight.userdev") version "1.3.1" + id("net.civmc.civgradle.plugin") version "1.0.0-SNAPSHOT" } -group = "net.civmc" -version = "2.0.0-SNAPSHOT" -description = "RailSwitch" - -repositories { - fun civRepo(name: String) { - maven { - url = uri("https://maven.pkg.github.com/CivMC/${name}") - credentials { - // These need to be set in the user environment variables - username = System.getenv("GITHUB_ACTOR") - password = System.getenv("GITHUB_TOKEN") - } - } - } - mavenCentral() - civRepo("CivModCore") - civRepo("NameLayer") - civRepo("Citadel") +// Temporary hack: +// Remove the root build directory +gradle.buildFinished { + project.buildDir.deleteRecursively() } -dependencies { - paperDevBundle("1.18-R0.1-SNAPSHOT") - - implementation("net.civmc:civmodcore:2.0.0-SNAPSHOT:dev-all") - implementation("net.civmc:namelayer-spigot:3.0.0-SNAPSHOT:dev") - implementation("net.civmc:citadel:5.0.0-SNAPSHOT:dev") +allprojects { + group = "net.civmc.civmodcore" + version = "2.0.0-SNAPSHOT" + description = "RailSwitch" } -java { - toolchain.languageVersion.set(JavaLanguageVersion.of(17)) -} +subprojects { + apply(plugin = "net.civmc.civgradle.plugin") + apply(plugin = "java-library") + apply(plugin = "maven-publish") -tasks { - build { - dependsOn(reobfJar) + java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } } - compileJava { - options.encoding = Charsets.UTF_8.name() // We want UTF-8 for everything - - // Set the release flag. This configures what version bytecode the compiler will emit, as well as what JDK APIs are usable. - // See https://openjdk.java.net/jeps/247 for more information. - options.release.set(17) - } - javadoc { - options.encoding = Charsets.UTF_8.name() // We want UTF-8 for everything - } - processResources { - filteringCharset = Charsets.UTF_8.name() // We want UTF-8 for everything - } - - test { - useJUnitPlatform() - } -} - -publishing { repositories { - maven { - name = "GitHubPackages" - url = uri("https://maven.pkg.github.com/CivMC/RailSwitch") - credentials { - username = System.getenv("GITHUB_ACTOR") - password = System.getenv("GITHUB_TOKEN") + mavenCentral() + civRepo("CivMC/CivModCore") + civRepo("CivMC/NameLayer") + civRepo("CivMC/Citadel") + } + + publishing { + repositories { + maven { + name = "GitHubPackages" + url = uri("https://maven.pkg.github.com/CivMC/RailSwitch") + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } + } + publications { + register("gpr") { + from(components["java"]) } } } - publications { - register("gpr") { - from(components["java"]) - } - } } diff --git a/plugins/railswitch-paper/paper/build.gradle.kts b/plugins/railswitch-paper/paper/build.gradle.kts new file mode 100644 index 000000000..25331947a --- /dev/null +++ b/plugins/railswitch-paper/paper/build.gradle.kts @@ -0,0 +1,19 @@ +plugins { + `java-library` + id("net.civmc.civgradle.plugin") + id("io.papermc.paperweight.userdev") version "1.3.1" +} + +civGradle { + paper { + pluginName = "RailSwitch" + } +} + +dependencies { + paperDevBundle("1.18.1-R0.1-SNAPSHOT") + + implementation("net.civmc:civmodcore:2.0.0-SNAPSHOT:dev-all") + implementation("net.civmc:namelayer-spigot:3.0.0-SNAPSHOT:dev") + implementation("net.civmc:citadel:5.0.0-SNAPSHOT:dev") +} diff --git a/plugins/railswitch-paper/paper/src/main/resources/plugin.yml b/plugins/railswitch-paper/paper/src/main/resources/plugin.yml index ed87e72cd..d17f4746d 100644 --- a/plugins/railswitch-paper/paper/src/main/resources/plugin.yml +++ b/plugins/railswitch-paper/paper/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ -name: ${project.artifactId} -version: ${project.version} +name: ${name} +version: ${version} main: sh.okx.railswitch.RailSwitchPlugin depend: [CivModCore] softdepend: [Citadel, NameLayer] diff --git a/plugins/railswitch-paper/settings.gradle.kts b/plugins/railswitch-paper/settings.gradle.kts index 62a3bbdfe..42a1c8d29 100644 --- a/plugins/railswitch-paper/settings.gradle.kts +++ b/plugins/railswitch-paper/settings.gradle.kts @@ -1,12 +1,17 @@ -/* - * This file was generated by the Gradle 'init' task. - */ - rootProject.name = "railswitch" pluginManagement { - repositories { - gradlePluginPortal() - maven("https://papermc.io/repo/repository/maven-public/") - } + repositories { + gradlePluginPortal() + maven("https://papermc.io/repo/repository/maven-public/") + maven { + url = uri("https://maven.pkg.github.com/CivMC/CivGradle") + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } + } } + +include(":paper") From 19bee87d156f035fb595a2d65882e9302d28e31e Mon Sep 17 00:00:00 2001 From: Cola Date: Thu, 30 Dec 2021 04:09:06 +0000 Subject: [PATCH 75/78] Always one --- plugins/railswitch-paper/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/railswitch-paper/build.gradle.kts b/plugins/railswitch-paper/build.gradle.kts index 08a547c39..a0a11ac3b 100644 --- a/plugins/railswitch-paper/build.gradle.kts +++ b/plugins/railswitch-paper/build.gradle.kts @@ -13,7 +13,7 @@ gradle.buildFinished { } allprojects { - group = "net.civmc.civmodcore" + group = "net.civmc.railswitch" version = "2.0.0-SNAPSHOT" description = "RailSwitch" } From db51aa33cbdb44062b6cf575769e57e5c4e8af7b Mon Sep 17 00:00:00 2001 From: AngrySoundTech Date: Sun, 2 Jan 2022 10:32:17 -0500 Subject: [PATCH 76/78] Use new maven repo location --- plugins/railswitch-paper/paper/build.gradle.kts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/railswitch-paper/paper/build.gradle.kts b/plugins/railswitch-paper/paper/build.gradle.kts index 25331947a..67f103241 100644 --- a/plugins/railswitch-paper/paper/build.gradle.kts +++ b/plugins/railswitch-paper/paper/build.gradle.kts @@ -1,7 +1,7 @@ plugins { `java-library` id("net.civmc.civgradle.plugin") - id("io.papermc.paperweight.userdev") version "1.3.1" + id("io.papermc.paperweight.userdev") version "1.3.3" } civGradle { @@ -13,7 +13,7 @@ civGradle { dependencies { paperDevBundle("1.18.1-R0.1-SNAPSHOT") - implementation("net.civmc:civmodcore:2.0.0-SNAPSHOT:dev-all") - implementation("net.civmc:namelayer-spigot:3.0.0-SNAPSHOT:dev") - implementation("net.civmc:citadel:5.0.0-SNAPSHOT:dev") + implementation("net.civmc.civmodcore:paper:2.0.0-SNAPSHOT:dev-all") + implementation("net.civmc.namelayer:paper:3.0.0-SNAPSHOT:dev") + implementation("net.civmc.citadel:paper:5.0.0-SNAPSHOT:dev") } From 041e16c19666aa1cbf8352085ee00bcfbe7c3901 Mon Sep 17 00:00:00 2001 From: Cola Date: Sat, 14 May 2022 23:49:10 +0100 Subject: [PATCH 77/78] 1.18.2 --- plugins/railswitch-paper/paper/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/railswitch-paper/paper/build.gradle.kts b/plugins/railswitch-paper/paper/build.gradle.kts index 67f103241..590706321 100644 --- a/plugins/railswitch-paper/paper/build.gradle.kts +++ b/plugins/railswitch-paper/paper/build.gradle.kts @@ -11,7 +11,7 @@ civGradle { } dependencies { - paperDevBundle("1.18.1-R0.1-SNAPSHOT") + paperDevBundle("1.18.2-R0.1-SNAPSHOT") implementation("net.civmc.civmodcore:paper:2.0.0-SNAPSHOT:dev-all") implementation("net.civmc.namelayer:paper:3.0.0-SNAPSHOT:dev") From 7f39781578ce8bb9c115dd2e1323b251a5c22a3c Mon Sep 17 00:00:00 2001 From: Cola Date: Wed, 18 May 2022 01:08:29 +0100 Subject: [PATCH 78/78] Downgrade paperweight to get jar --- plugins/railswitch-paper/paper/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/railswitch-paper/paper/build.gradle.kts b/plugins/railswitch-paper/paper/build.gradle.kts index 590706321..850c1ffd0 100644 --- a/plugins/railswitch-paper/paper/build.gradle.kts +++ b/plugins/railswitch-paper/paper/build.gradle.kts @@ -1,7 +1,7 @@ plugins { `java-library` id("net.civmc.civgradle.plugin") - id("io.papermc.paperweight.userdev") version "1.3.3" + id("io.papermc.paperweight.userdev") version "1.3.1" } civGradle {