From ac520665e69b0a58739006c7f6ec979b7ed85808 Mon Sep 17 00:00:00 2001 From: Steven Scott Date: Sat, 24 Nov 2012 15:42:57 -0800 Subject: [PATCH] [Version 0.7.9.0] - Added BungeeCord multi-server support (Requires Stargate-Bungee for BungeeCord) - Updated Spanish language file - Added basic plugin metrics via http://mcstats.org/ --- README | 4 + src/net/TheDgtl/Stargate/MetricsLite.java | 357 ++++++++++++++++++++++ src/net/TheDgtl/Stargate/Portal.java | 188 ++++++++---- src/net/TheDgtl/Stargate/Stargate.java | 45 ++- src/net/TheDgtl/Stargate/pmListener.java | 25 ++ src/net/TheDgtl/Stargate/resources/en.txt | 7 +- src/net/TheDgtl/Stargate/resources/es.txt | 12 +- src/plugin.yml | 2 +- 8 files changed, 575 insertions(+), 65 deletions(-) create mode 100644 src/net/TheDgtl/Stargate/MetricsLite.java create mode 100644 src/net/TheDgtl/Stargate/pmListener.java diff --git a/README b/README index 0df0fd4..8b29ef1 100644 --- a/README +++ b/README @@ -212,6 +212,10 @@ Client randomly crashes on teleport. ============= Changes ============= +[Version 0.7.9.0] + - Added BungeeCord multi-server support (Requires Stargate-Bungee for BungeeCord) + - Updated Spanish language file + - Added basic plugin metrics via http://mcstats.org/ [Version 0.7.8.1] - Resolve issue of language file being overwritten as ANSI instead of UTF8 [Version 0.7.8.0] diff --git a/src/net/TheDgtl/Stargate/MetricsLite.java b/src/net/TheDgtl/Stargate/MetricsLite.java new file mode 100644 index 0000000..65fad05 --- /dev/null +++ b/src/net/TheDgtl/Stargate/MetricsLite.java @@ -0,0 +1,357 @@ +/* + * Copyright 2011 Tyler Blair. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and contributors and should not be interpreted as representing official policies, + * either expressed or implied, of anybody else. + */ + +package net.TheDgtl.Stargate; + +import org.bukkit.Bukkit; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginDescriptionFile; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; +import java.util.UUID; +import java.util.logging.Level; + +public class MetricsLite { + + /** + * The current revision number + */ + private final static int REVISION = 5; + + /** + * The base url of the metrics domain + */ + private static final String BASE_URL = "http://mcstats.org"; + + /** + * The url used to report a server's status + */ + private static final String REPORT_URL = "/report/%s"; + + /** + * Interval of time to ping (in minutes) + */ + private final static int PING_INTERVAL = 10; + + /** + * The plugin this metrics submits for + */ + private final Plugin plugin; + + /** + * The plugin configuration file + */ + private final YamlConfiguration configuration; + + /** + * The plugin configuration file + */ + private final File configurationFile; + + /** + * Unique server id + */ + private final String guid; + + /** + * Lock for synchronization + */ + private final Object optOutLock = new Object(); + + /** + * Id of the scheduled task + */ + private volatile int taskId = -1; + + public MetricsLite(Plugin plugin) throws IOException { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null"); + } + + this.plugin = plugin; + + // load the config + configurationFile = getConfigFile(); + configuration = YamlConfiguration.loadConfiguration(configurationFile); + + // add some defaults + configuration.addDefault("opt-out", false); + configuration.addDefault("guid", UUID.randomUUID().toString()); + + // Do we need to create the file? + if (configuration.get("guid", null) == null) { + configuration.options().header("http://mcstats.org").copyDefaults(true); + configuration.save(configurationFile); + } + + // Load the guid then + guid = configuration.getString("guid"); + } + + /** + * Start measuring statistics. This will immediately create an async repeating task as the plugin and send + * the initial data to the metrics backend, and then after that it will post in increments of + * PING_INTERVAL * 1200 ticks. + * + * @return True if statistics measuring is running, otherwise false. + */ + public boolean start() { + synchronized (optOutLock) { + // Did we opt out? + if (isOptOut()) { + return false; + } + + // Is metrics already running? + if (taskId >= 0) { + return true; + } + + // Begin hitting the server with glorious data + taskId = plugin.getServer().getScheduler().scheduleAsyncRepeatingTask(plugin, new Runnable() { + + private boolean firstPost = true; + + public void run() { + try { + // This has to be synchronized or it can collide with the disable method. + synchronized (optOutLock) { + // Disable Task, if it is running and the server owner decided to opt-out + if (isOptOut() && taskId > 0) { + plugin.getServer().getScheduler().cancelTask(taskId); + taskId = -1; + } + } + + // We use the inverse of firstPost because if it is the first time we are posting, + // it is not a interval ping, so it evaluates to FALSE + // Each time thereafter it will evaluate to TRUE, i.e PING! + postPlugin(!firstPost); + + // After the first post we set firstPost to false + // Each post thereafter will be a ping + firstPost = false; + } catch (IOException e) { + Bukkit.getLogger().log(Level.INFO, "[Metrics] " + e.getMessage()); + } + } + }, 0, PING_INTERVAL * 1200); + + return true; + } + } + + /** + * Has the server owner denied plugin metrics? + * + * @return true if metrics should be opted out of it + */ + public boolean isOptOut() { + synchronized(optOutLock) { + try { + // Reload the metrics file + configuration.load(getConfigFile()); + } catch (IOException ex) { + Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage()); + return true; + } catch (InvalidConfigurationException ex) { + Bukkit.getLogger().log(Level.INFO, "[Metrics] " + ex.getMessage()); + return true; + } + return configuration.getBoolean("opt-out", false); + } + } + + /** + * Enables metrics for the server by setting "opt-out" to false in the config file and starting the metrics task. + * + * @throws IOException + */ + public void enable() throws IOException { + // This has to be synchronized or it can collide with the check in the task. + synchronized (optOutLock) { + // Check if the server owner has already set opt-out, if not, set it. + if (isOptOut()) { + configuration.set("opt-out", false); + configuration.save(configurationFile); + } + + // Enable Task, if it is not running + if (taskId < 0) { + start(); + } + } + } + + /** + * Disables metrics for the server by setting "opt-out" to true in the config file and canceling the metrics task. + * + * @throws IOException + */ + public void disable() throws IOException { + // This has to be synchronized or it can collide with the check in the task. + synchronized (optOutLock) { + // Check if the server owner has already set opt-out, if not, set it. + if (!isOptOut()) { + configuration.set("opt-out", true); + configuration.save(configurationFile); + } + + // Disable Task, if it is running + if (taskId > 0) { + this.plugin.getServer().getScheduler().cancelTask(taskId); + taskId = -1; + } + } + } + + /** + * Gets the File object of the config file that should be used to store data such as the GUID and opt-out status + * + * @return the File object for the config file + */ + public File getConfigFile() { + // I believe the easiest way to get the base folder (e.g craftbukkit set via -P) for plugins to use + // is to abuse the plugin object we already have + // plugin.getDataFolder() => base/plugins/PluginA/ + // pluginsFolder => base/plugins/ + // The base is not necessarily relative to the startup directory. + File pluginsFolder = plugin.getDataFolder().getParentFile(); + + // return => base/plugins/PluginMetrics/config.yml + return new File(new File(pluginsFolder, "PluginMetrics"), "config.yml"); + } + + /** + * Generic method that posts a plugin to the metrics website + */ + private void postPlugin(boolean isPing) throws IOException { + // The plugin's description file containg all of the plugin data such as name, version, author, etc + final PluginDescriptionFile description = plugin.getDescription(); + + // Construct the post data + final StringBuilder data = new StringBuilder(); + data.append(encode("guid")).append('=').append(encode(guid)); + encodeDataPair(data, "version", description.getVersion()); + encodeDataPair(data, "server", Bukkit.getVersion()); + encodeDataPair(data, "players", Integer.toString(Bukkit.getServer().getOnlinePlayers().length)); + encodeDataPair(data, "revision", String.valueOf(REVISION)); + + // If we're pinging, append it + if (isPing) { + encodeDataPair(data, "ping", "true"); + } + + // Create the url + URL url = new URL(BASE_URL + String.format(REPORT_URL, encode(plugin.getDescription().getName()))); + + // Connect to the website + URLConnection connection; + + // Mineshafter creates a socks proxy, so we can safely bypass it + // It does not reroute POST requests so we need to go around it + if (isMineshafterPresent()) { + connection = url.openConnection(Proxy.NO_PROXY); + } else { + connection = url.openConnection(); + } + + connection.setDoOutput(true); + + // Write the data + final OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream()); + writer.write(data.toString()); + writer.flush(); + + // Now read the response + final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + final String response = reader.readLine(); + + // close resources + writer.close(); + reader.close(); + + if (response == null || response.startsWith("ERR")) { + throw new IOException(response); //Throw the exception + } + //if (response.startsWith("OK")) - We should get "OK" followed by an optional description if everything goes right + } + + /** + * Check if mineshafter is present. If it is, we need to bypass it to send POST requests + * + * @return true if mineshafter is installed on the server + */ + private boolean isMineshafterPresent() { + try { + Class.forName("mineshafter.MineServer"); + return true; + } catch (Exception e) { + return false; + } + } + + /** + *

Encode a key/value data pair to be used in a HTTP post request. This INCLUDES a & so the first + * key/value pair MUST be included manually, e.g:

+ * + * StringBuffer data = new StringBuffer(); + * data.append(encode("guid")).append('=').append(encode(guid)); + * encodeDataPair(data, "version", description.getVersion()); + * + * + * @param buffer the stringbuilder to append the data pair onto + * @param key the key value + * @param value the value + */ + private static void encodeDataPair(final StringBuilder buffer, final String key, final String value) throws UnsupportedEncodingException { + buffer.append('&').append(encode(key)).append('=').append(encode(value)); + } + + /** + * Encode text as UTF-8 + * + * @param text the text to encode + * @return the encoded text, as UTF-8 + */ + private static String encode(final String text) throws UnsupportedEncodingException { + return URLEncoder.encode(text, "UTF-8"); + } + +} \ No newline at end of file diff --git a/src/net/TheDgtl/Stargate/Portal.java b/src/net/TheDgtl/Stargate/Portal.java index bb70646..2375948 100644 --- a/src/net/TheDgtl/Stargate/Portal.java +++ b/src/net/TheDgtl/Stargate/Portal.java @@ -63,6 +63,9 @@ public class Portal { private static final HashMap> allPortalsNet = new HashMap>(); private static final HashMap> lookupNamesNet = new HashMap>(); + // A list of Bungee gates + private static final HashMap bungeePortals = new HashMap(); + // Gate location block info private Blox topLeft; private int modX; @@ -95,6 +98,7 @@ public class Portal { private boolean show = false; private boolean noNetwork = false; private boolean random = false; + private boolean bungee = false; // In-use information private Player player; @@ -107,7 +111,7 @@ public class Portal { float rotX, Blox id, Blox button, String dest, String name, boolean verified, String network, Gate gate, String owner, - boolean hidden, boolean alwaysOn, boolean priv, boolean free, boolean backwards, boolean show, boolean noNetwork, boolean random) { + boolean hidden, boolean alwaysOn, boolean priv, boolean free, boolean backwards, boolean show, boolean noNetwork, boolean random, boolean bungee) { this.topLeft = topLeft; this.modX = modX; this.modZ = modZ; @@ -128,8 +132,9 @@ public class Portal { this.show = show; this.noNetwork = noNetwork; this.random = random; + this.bungee = bungee; this.world = topLeft.getWorld(); - this.fixed = dest.length() > 0 || this.random; + this.fixed = dest.length() > 0 || this.random || this.bungee; if (this.isAlwaysOn() && !this.isFixed()) { this.alwaysOn = false; @@ -185,6 +190,10 @@ public class Portal { return random; } + public boolean isBungee() { + return bungee; + } + public void setAlwaysOn(boolean alwaysOn) { this.alwaysOn = alwaysOn; } @@ -466,8 +475,14 @@ public class Portal { exit = pEvent.getExit(); } - // The new method to teleport in a move event is set the "to" field. - event.setTo(exit); + // If no event is passed in, assume it's a teleport, and act as such + if (event == null) { + exit.setYaw(this.getRotation()); + player.teleport(exit); + } else { + // The new method to teleport in a move event is set the "to" field. + event.setTo(exit); + } } public void teleport(final Vehicle vehicle) { @@ -530,8 +545,8 @@ public class Portal { } else { Stargate.log.log(Level.WARNING, "[Stargate] Missing destination point in .gate file " + gate.getFilename()); } + if (loc != null) { - if (getWorld().getBlockTypeIdAt(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()) == Material.STEP.getId()) { loc.setY(loc.getY() + 0.5); } @@ -691,7 +706,12 @@ public class Portal { Stargate.setLine(sign, ++done, "(" + network + ")"); } } else { - if (isFixed()) { + // Awesome new logic for Bungee gates + if (isBungee()) { + Stargate.setLine(sign, ++done, Stargate.getString("bungeeSign")); + Stargate.setLine(sign, ++done, ">" + destination + "<"); + Stargate.setLine(sign, ++done, "[" + network + "]"); + } else if (isFixed()) { if (isRandom()) { Stargate.setLine(sign, ++done, "> " + Stargate.getString("signRandom") + " <"); } else { @@ -768,7 +788,6 @@ public class Portal { public void unregister(boolean removeAll) { Stargate.debug("Unregister", "Unregistering gate " + getName()); close(true); - lookupNamesNet.get(getNetwork().toLowerCase()).remove(getName().toLowerCase()); for (Blox block : getFrame()) { lookupBlocks.remove(block); @@ -790,7 +809,21 @@ public class Portal { if (removeAll) allPortals.remove(this); - allPortalsNet.get(getNetwork().toLowerCase()).remove(getName().toLowerCase()); + if (bungee) { + bungeePortals.remove(getName().toLowerCase()); + } else { + lookupNamesNet.get(getNetwork().toLowerCase()).remove(getName().toLowerCase()); + allPortalsNet.get(getNetwork().toLowerCase()).remove(getName().toLowerCase()); + + for (String originName : allPortalsNet.get(getNetwork().toLowerCase())) { + Portal origin = Portal.getByName(originName, getNetwork()); + if (origin == null) continue; + if (!origin.getDestinationName().equalsIgnoreCase(getName())) continue; + if (!origin.isVerified()) continue; + if (origin.isFixed()) origin.drawSign(); + if (origin.isAlwaysOn()) origin.close(true); + } + } if (id.getBlock().getType() == Material.WALL_SIGN && id.getBlock().getState() instanceof Sign) { Sign sign = (Sign)id.getBlock().getState(); @@ -801,15 +834,6 @@ public class Portal { sign.update(); } - for (String originName : allPortalsNet.get(getNetwork().toLowerCase())) { - Portal origin = Portal.getByName(originName, getNetwork()); - if (origin == null) continue; - if (!origin.getDestinationName().equalsIgnoreCase(getName())) continue; - if (!origin.isVerified()) continue; - if (origin.isFixed()) origin.drawSign(); - if (origin.isAlwaysOn()) origin.close(true); - } - saveAllGates(getWorld()); } @@ -818,12 +842,26 @@ public class Portal { } private void register() { - fixed = destination.length() > 0 || random; - if (!lookupNamesNet.containsKey(getNetwork().toLowerCase())) { - Stargate.debug("register", "Network not in lookupNamesNet, adding"); - lookupNamesNet.put(getNetwork().toLowerCase(), new HashMap()); + fixed = destination.length() > 0 || random || bungee; + + // Bungee gates are stored in their own list + if (isBungee()) { + bungeePortals.put(getName().toLowerCase(), this); + } else { + // Check if network exists in our network list + if (!lookupNamesNet.containsKey(getNetwork().toLowerCase())) { + Stargate.debug("register", "Network not in lookupNamesNet, adding"); + lookupNamesNet.put(getNetwork().toLowerCase(), new HashMap()); + } + lookupNamesNet.get(getNetwork().toLowerCase()).put(getName().toLowerCase(), this); + + // Check if this network exists + if (!allPortalsNet.containsKey(getNetwork().toLowerCase())) { + Stargate.debug("register", "Network not in allPortalsNet, adding"); + allPortalsNet.put(getNetwork().toLowerCase(), new ArrayList()); + } + allPortalsNet.get(getNetwork().toLowerCase()).add(getName().toLowerCase()); } - lookupNamesNet.get(getNetwork().toLowerCase()).put(getName().toLowerCase(), this); for (Blox block : getFrame()) { lookupBlocks.put(block, this); @@ -843,12 +881,6 @@ public class Portal { } allPortals.add(this); - // Check if this network exists - if (!allPortalsNet.containsKey(getNetwork().toLowerCase())) { - Stargate.debug("register", "Network not in allPortalsNet, adding"); - allPortalsNet.put(getNetwork().toLowerCase(), new ArrayList()); - } - allPortalsNet.get(getNetwork().toLowerCase()).add(getName().toLowerCase()); } public static Portal createPortal(SignChangeEvent event, Player player) { @@ -879,6 +911,7 @@ public class Portal { boolean show = (options.indexOf('s') != -1); boolean noNetwork = (options.indexOf('n') != -1); boolean random = (options.indexOf('r') != -1); + boolean bungee = (options.indexOf('u') != -1); // Check permissions for options. if (hidden && !Stargate.canOption(player, "hidden")) hidden = false; @@ -890,6 +923,20 @@ public class Portal { if (noNetwork && !Stargate.canOption(player, "nonetwork")) noNetwork = false; if (random && !Stargate.canOption(player, "random")) random = false; + // If the player is trying to create a Bungee gate without permissions, drop out here + if (bungee) { + if (!Stargate.enableBungee) { + Stargate.sendMessage(player, Stargate.getString("bungeeDisabled")); + return null; + } else if (!Stargate.hasPerm(player, "stargate.admin.bungee")) { + Stargate.sendMessage(player, Stargate.getString("bungeeDeny")); + return null; + } else if (destName.isEmpty() || network.isEmpty()) { + Stargate.sendMessage(player, Stargate.getString("bungeeEmpty")); + return null; + } + } + // Can not create a non-fixed always-on gate. if (alwaysOn && destName.length() == 0) { alwaysOn = false; @@ -906,6 +953,12 @@ public class Portal { show = false; } + // Bungee gates are always on and don't support Random + if (bungee) { + alwaysOn = true; + random = false; + } + // Moved the layout check so as to avoid invalid messages when not making a gate int modX = 0; int modZ = 0; @@ -966,9 +1019,9 @@ public class Portal { } // Debug - Stargate.debug("createPortal", "h = " + hidden + " a = " + alwaysOn + " p = " + priv + " f = " + free + " b = " + backwards + " s = " + show + " n = " + noNetwork + " r = " + random); + Stargate.debug("createPortal", "h = " + hidden + " a = " + alwaysOn + " p = " + priv + " f = " + free + " b = " + backwards + " s = " + show + " n = " + noNetwork + " r = " + random + " u = " + bungee); - if ((network.length() < 1) || (network.length() > 11)) { + if (!bungee && (network.length() < 1 || network.length() > 11)) { network = Stargate.getDefaultNetwork(); } @@ -976,7 +1029,7 @@ public class Portal { String denyMsg = ""; // Check if the player can create gates on this network - if (!Stargate.canCreate(player, network)) { + if (!bungee && !Stargate.canCreate(player, network)) { Stargate.debug("createPortal", "Player doesn't have create permissions on network. Trying personal"); if (Stargate.canCreatePersonal(player)) { network = player.getName(); @@ -1001,7 +1054,7 @@ public class Portal { } // Check if the user can create gates to this world. - if (!deny && destName.length() > 0) { + if (!bungee && !deny && destName.length() > 0) { Portal p = Portal.getByName(destName, network); if (p != null) { String world = p.getWorld().getName(); @@ -1025,7 +1078,7 @@ public class Portal { Blox button = null; Portal portal = null; - portal = new Portal(topleft, modX, modZ, rotX, id, button, destName, name, false, network, gate, player.getName(), hidden, alwaysOn, priv, free, backwards, show, noNetwork, random); + portal = new Portal(topleft, modX, modZ, rotX, id, button, destName, name, false, network, gate, player.getName(), hidden, alwaysOn, priv, free, backwards, show, noNetwork, random, bungee); int cost = Stargate.getCreateCost(player, gate); @@ -1049,17 +1102,26 @@ public class Portal { return null; } - if (getByName(portal.getName(), portal.getNetwork()) != null) { - Stargate.debug("createPortal", "Name Error"); - Stargate.sendMessage(player, Stargate.getString("createExists")); - return null; - } - - // Check if there are too many gates in this network - ArrayList netList = allPortalsNet.get(portal.getNetwork().toLowerCase()); - if (Stargate.maxGates > 0 && netList != null && netList.size() >= Stargate.maxGates) { - Stargate.sendMessage(player, Stargate.getString("createFull")); - return null; + // Don't do network checks for bungee gates + if (portal.isBungee()) { + if (bungeePortals.get(portal.getName().toLowerCase()) != null) { + Stargate.debug("createPortal::Bungee", "Gate Exists"); + Stargate.sendMessage(player, Stargate.getString("createExists")); + return null; + } + } else { + if (getByName(portal.getName(), portal.getNetwork()) != null) { + Stargate.debug("createPortal", "Name Error"); + Stargate.sendMessage(player, Stargate.getString("createExists")); + return null; + } + + // Check if there are too many gates in this network + ArrayList netList = allPortalsNet.get(portal.getNetwork().toLowerCase()); + if (Stargate.maxGates > 0 && netList != null && netList.size() >= Stargate.maxGates) { + Stargate.sendMessage(player, Stargate.getString("createFull")); + return null; + } } if (cost > 0) { @@ -1086,7 +1148,7 @@ public class Portal { portal.register(); portal.drawSign(); // Open always on gate - if (portal.isRandom()) { + if (portal.isRandom() || portal.isBungee()) { portal.open(true); } else if (portal.isAlwaysOn()) { Portal dest = Portal.getByName(destName, portal.getNetwork()); @@ -1101,14 +1163,17 @@ public class Portal { } } - // Open any always on gate pointing at this gate - for (String originName : allPortalsNet.get(portal.getNetwork().toLowerCase())) { - Portal origin = Portal.getByName(originName, portal.getNetwork()); - if (origin == null) continue; - if (!origin.getDestinationName().equalsIgnoreCase(portal.getName())) continue; - if (!origin.isVerified()) continue; - if (origin.isFixed()) origin.drawSign(); - if (origin.isAlwaysOn()) origin.open(true); + // Don't do network stuff for bungee gates + if (!portal.isBungee()) { + // Open any always on gate pointing at this gate + for (String originName : allPortalsNet.get(portal.getNetwork().toLowerCase())) { + Portal origin = Portal.getByName(originName, portal.getNetwork()); + if (origin == null) continue; + if (!origin.getDestinationName().equalsIgnoreCase(portal.getName())) continue; + if (!origin.isVerified()) continue; + if (origin.isFixed()) origin.drawSign(); + if (origin.isAlwaysOn()) origin.open(true); + } } saveAllGates(portal.getWorld()); @@ -1137,6 +1202,10 @@ public class Portal { public static Portal getByBlock(Block block) { return lookupBlocks.get(new Blox(block)); } + + public static Portal getBungeeGate(String name) { + return bungeePortals.get(name); + } public static void saveAllGates(World world) { String loc = Stargate.getSaveLocation() + "/" + world.getName() + ".db"; @@ -1190,6 +1259,8 @@ public class Portal { builder.append(portal.isNoNetwork()); builder.append(':'); builder.append(portal.isRandom()); + builder.append(':'); + builder.append(portal.isBungee()); bw.append(builder.toString()); bw.newLine(); @@ -1260,8 +1331,9 @@ public class Portal { boolean show = (split.length > 17) ? split[17].equalsIgnoreCase("true") : false; boolean noNetwork = (split.length > 18) ? split[18].equalsIgnoreCase("true") : false; boolean random = (split.length > 19) ? split[19].equalsIgnoreCase("true") : false; + boolean bungee = (split.length > 20) ? split[20].equalsIgnoreCase("true") : false; - Portal portal = new Portal(topLeft, modX, modZ, rotX, sign, button, dest, name, false, network, gate, owner, hidden, alwaysOn, priv, free, backwards, show, noNetwork, random); + Portal portal = new Portal(topLeft, modX, modZ, rotX, sign, button, dest, name, false, network, gate, owner, hidden, alwaysOn, priv, free, backwards, show, noNetwork, random, bungee); portal.register(); portal.close(true); } @@ -1293,6 +1365,14 @@ public class Portal { } if (!portal.isFixed()) continue; + + if (portal.isBungee()) { + OpenCount++; + portal.open(true); + portal.drawSign(); + continue; + } + Portal dest = portal.getDestination(); if (dest != null) { if (portal.isAlwaysOn()) { diff --git a/src/net/TheDgtl/Stargate/Stargate.java b/src/net/TheDgtl/Stargate/Stargate.java index dd58552..fb3da5a 100644 --- a/src/net/TheDgtl/Stargate/Stargate.java +++ b/src/net/TheDgtl/Stargate/Stargate.java @@ -1,6 +1,7 @@ package net.TheDgtl.Stargate; import java.io.File; +import java.io.IOException; import java.util.Iterator; import java.util.LinkedList; import java.util.Queue; @@ -11,6 +12,7 @@ import java.util.logging.Logger; import net.TheDgtl.Stargate.event.StargateAccessEvent; import net.TheDgtl.Stargate.event.StargateDestroyEvent; +import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.GameMode; import org.bukkit.Location; @@ -98,6 +100,7 @@ public class Stargate extends JavaPlugin { public static boolean handleVehicles = true; public static boolean sortLists = false; public static boolean protectEntrance = false; + public static boolean enableBungee = true; public static ChatColor signColor; // Temp workaround for snowmen, don't check gate entrance @@ -145,6 +148,12 @@ public class Stargate extends JavaPlugin { this.loadConfig(); + // Enable the required channels for Bungee support + if (enableBungee) { + Bukkit.getMessenger().registerOutgoingPluginChannel(this, "SGBungee"); + Bukkit.getMessenger().registerIncomingPluginChannel(this, "SGBungee", new pmListener()); + } + // It is important to load languages here, as they are used during reloadGates() lang = new LangLoader(langFolder, Stargate.langName); @@ -166,6 +175,19 @@ public class Stargate extends JavaPlugin { getServer().getScheduler().scheduleSyncRepeatingTask(this, new SGThread(), 0L, 100L); getServer().getScheduler().scheduleSyncRepeatingTask(this, new BlockPopulatorThread(), 0L, 1L); + + // Enable Plugin Metrics + try { + MetricsLite ml = new MetricsLite(this); + if (!ml.isOptOut()) { + ml.start(); + log.info("[Stargate] Plugin metrics enabled."); + } else { + log.info("[Stargate] Plugin metrics not enabled."); + } + } catch (IOException ex) { + log.warning("[Stargate] Error enabling plugin metrics: " + ex); + } } public void loadConfig() { @@ -186,6 +208,7 @@ public class Stargate extends JavaPlugin { handleVehicles = newConfig.getBoolean("handleVehicles"); sortLists = newConfig.getBoolean("sortLists"); protectEntrance = newConfig.getBoolean("protectEntrance"); + enableBungee = newConfig.getBoolean("enableBungee"); // Sign color String sc = newConfig.getString("signColor"); try { @@ -567,7 +590,7 @@ public class Stargate extends JavaPlugin { // Portal is free if (src.isFree()) return 0; // Not charging for free destinations - if (!iConomyHandler.chargeFreeDestination && dest.isFree()) return 0; + if (dest != null && !iConomyHandler.chargeFreeDestination && dest.isFree()) return 0; // Cost is 0 if the player owns this gate and funds go to the owner if (src.getGate().getToOwner() && src.getOwner().equalsIgnoreCase(player.getName())) return 0; // Player gets free gate use @@ -637,6 +660,9 @@ public class Stargate extends JavaPlugin { Portal portal = Portal.getByEntrance(event.getTo()); if (portal == null || !portal.isOpen()) return; + // We don't support vehicles in Bungee portals + if (portal.isBungee()) return; + if (passenger instanceof Player) { Player player = (Player)passenger; if (!portal.isOpenFor(player)) { @@ -743,16 +769,17 @@ public class Stargate extends JavaPlugin { } Portal destination = portal.getDestination(player); - if (destination == null) return; + if (!portal.isBungee() && destination == null) return; boolean deny = false; // Check if player has access to this network + // For Bungee gates this will be the target server name if (!canAccessNetwork(player, portal.getNetwork())) { deny = true; } // Check if player has access to destination world - if (!canAccessWorld(player, destination.getWorld().getName())) { + if (!portal.isBungee() && !canAccessWorld(player, destination.getWorld().getName())) { deny = true; } @@ -786,6 +813,18 @@ public class Stargate extends JavaPlugin { } Stargate.sendMessage(player, Stargate.getString("teleportMsg"), false); + if (portal.isBungee()) { + portal.teleport(player, portal, event); + + // Teleport player via BungeeCord + String pMsg = portal.getNetwork() + "@#@" + portal.getDestinationName(); + player.sendPluginMessage(stargate, "SGBungee", pMsg.getBytes()); + + // Close portal if required (Should never be) + portal.close(false); + return; + } + destination.teleport(player, portal, event); portal.close(false); } diff --git a/src/net/TheDgtl/Stargate/pmListener.java b/src/net/TheDgtl/Stargate/pmListener.java new file mode 100644 index 0000000..c8f1c12 --- /dev/null +++ b/src/net/TheDgtl/Stargate/pmListener.java @@ -0,0 +1,25 @@ +package net.TheDgtl.Stargate; + +import org.bukkit.entity.Player; +import org.bukkit.plugin.messaging.PluginMessageListener; + +public class pmListener implements PluginMessageListener { + + @Override + public void onPluginMessageReceived(String channel, Player player, byte[] message) { + if (!channel.equals("SGBungee")) return; + + // Message should be destination gate name. + Portal dest = Portal.getBungeeGate(new String(message)); + + // Specified an invalid gate. For now we'll just let them connect at their current location + if (dest == null) { + return; + } + + // Teleport the player to their destination portal + dest.teleport(player, dest, null); + Stargate.debug("PML", "Recieved message: " + new String(message) + " Player: " + player.getName()); + } + +} diff --git a/src/net/TheDgtl/Stargate/resources/en.txt b/src/net/TheDgtl/Stargate/resources/en.txt index ed041f6..8b50bd0 100644 --- a/src/net/TheDgtl/Stargate/resources/en.txt +++ b/src/net/TheDgtl/Stargate/resources/en.txt @@ -24,4 +24,9 @@ createConflict=Gate conflicts with existing gate signRightClick=Right click signToUse=to use gate signRandom=Random -signDisconnected=Disconnected \ No newline at end of file +signDisconnected=Disconnected + +bungeeDisabled=BungeeCord support is disabled. +bungeeDeny=You do not have permission to create BungeeCord gates. +bungeeEmpty=BungeeCord gates require both a destination and network. +bungeeSign=Teleport to diff --git a/src/net/TheDgtl/Stargate/resources/es.txt b/src/net/TheDgtl/Stargate/resources/es.txt index 906b1ea..5fe853b 100644 --- a/src/net/TheDgtl/Stargate/resources/es.txt +++ b/src/net/TheDgtl/Stargate/resources/es.txt @@ -1,8 +1,8 @@ author=Manuestaire prefix=[Stargate] teleportMsg=Teletransportado -destroyMsg=Portal Destruido -invalidMsg=Destino Inválido +destroyMsg=Portal Destruído +invalidMsg=Elige Destino blockMsg=Destino Bloqueado destEmpty=La lista de destinos está vacía denyMsg=Acceso denegado @@ -22,7 +22,7 @@ createFull=Esta red est createWorldDeny=No tienes permisos para acceder a ese mundo createConflict=El portal entra en conflicto con un portal ya existente -signRightClick=Right click -signToUse=to use gate -signRandom=Random -signDisconnected=Disconnected \ No newline at end of file +signRightClick=Click derecho +signToUse=para usar +signRandom=Aleatorio +signDisconnected=Desconectado \ No newline at end of file diff --git a/src/plugin.yml b/src/plugin.yml index e78e17b..943dc4b 100644 --- a/src/plugin.yml +++ b/src/plugin.yml @@ -1,6 +1,6 @@ name: Stargate main: net.TheDgtl.Stargate.Stargate -version: 0.7.8.1 +version: 0.7.9.0 description: Stargate mod for Bukkit author: Drakia website: http://www.thedgtl.net