Java tutorial
/** * Copyright (c) 2012 Daniele Pantaleone, Mathias Van Malderen * * 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. * * @author Daniele Pantaleone * @version 1.4 * @copyright Daniele Pantaleone, 10 February, 2013 * @package com.orion.console **/ package com.orion.console; import static com.google.common.base.Preconditions.checkNotNull; import java.net.UnknownHostException; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.logging.Log; import com.orion.command.Command; import com.orion.domain.Client; import com.orion.exception.RconException; import com.orion.misc.Message; import com.orion.misc.Rcon; import com.orion.urt.Color; import com.orion.urt.Cvar; import com.orion.urt.Team; public class UrT42Console { private static final int CHAT_DELAY = 1000; private static final int CENTER_SCREEN_DELAY = 2000; private static final int MAX_SAY_STRLEN = 62; private final Log log; private final Rcon rcon; private Map<String, Cvar> cvarList; /** * Object constructor * * @author Daniele Pantaleone * @param log Main logger object reference * @param rcon An initialized <tt>Rcon</tt> object * @param cvarList A <tt>Map</tt> of <tt>Cvar</tt> objects shared * by the Parser and the Console **/ public UrT42Console(Log log, Rcon rcon, Map<String, Cvar> cvarList) { this.log = log; this.rcon = rcon; this.cvarList = cvarList; this.log.debug("Urban Terror 4.2 console initialized"); } /** * Ban a <tt>Client</tt> from the server permanently * * @author Daniele Pantaleone * @param client The <tt>Client</tt> who is going to be banned * @throws RconException If the RCON command fails in being executed * @throws NullPointerException If the given <tt>Client</tt> is <tt>null</tt> **/ public void ban(Client client) throws RconException, NullPointerException { try { checkNotNull(this.authEnable, "auth_enable CVAR is null"); checkNotNull(this.authOwners, "auth_owners CVAR is null"); if (!this.authEnable.getBoolean()) throw new UnsupportedOperationException("auth system is disabled"); this.rcon.send("addip " + client.getIp().getHostAddress()); this.rcon.send("auth-ban " + client.getSlot() + " 0 0 0"); } catch (NullPointerException | UnsupportedOperationException e) { this.rcon.send("addip " + client.getIp().getHostAddress()); this.rcon.send("kick " + client.getSlot()); } } /** * Cycle the current map on the server * * @author Daniele Pantaleone * @throws RconException If the RCON commands fails in being executed **/ public void cyclemap() throws RconException { this.rcon.send("cyclemap"); } /* /** * Retrieve userinfo data for the specified <tt>Client</tt> slot number * * @author Daniele Pantaleone * @param slot The slot of the <tt>Client</tt> whose informations needs to be retrieved * @throws RconException If the <tt>Client</tt> informations couldn't be retrieved * @return A <tt>Map</tt> containing userinfo data or <tt>null</tt> * if the <tt>Client</tt> is not connected anymore **//* public Map<String, String> dumpuser(int slot) throws RconException { String result = this.rcon.send("dumpuser " + slot, true); // This is the string we expect from the /rcon dumpuser <slot> command. // We need to parse it and build an HashMap containing the client data. // // userinfo // -------- // ip 93.40.100.128:59685 // gear GZJATWA // rate 25000 // name [FS]Fenix // racered 2 Map<String, String> map = new LinkedHashMap<String, String>(); Pattern pattern = Pattern.compile("^\\s*(?<key>\\w+)\\s+(?<value>.*)$"); String[] lines = result.split("\n"); for (String line: lines) { Matcher m = pattern.matcher(line); if (m.matches()) map.put(m.group("key"), m.group("value")); } return map.size() > 0 ? map : null; } /** * Retrieve userinfo data for the specified <tt>Client</tt> * * @author Daniele Pantaleone * @param client The <tt>Client</tt> whose informations needs to be retrieved * @throws RconException If the <tt>Client</tt> informations couldn't be retrieved * @throws NullPointerException If the given <tt>Client</tt> is <tt>null</tt> * @return A <tt>Map</tt> containing userinfo data or <tt>null</tt> * if the <tt>Client</tt> is not connected anymore **//* public Map<String, String> dumpuser(Client client) throws RconException, NullPointerException { return this.dumpuser(checkNotNull(client).getSlot()); } */ /** * Force a <tt>Client</tt> in the specified team * * @author Daniele Pantaleone * @param client The <tt>Client</tt> who is going to be moved * @param team The <tt>Team</tt> where to force the player in * @throws RconException If the <tt>Client</tt> fails in being moved * @throws NullPointerException If the given <tt>Client</tt> is <tt>null</tt> **/ public void forceteam(Client client, Team team) throws RconException { this.write("forceteam " + client.getSlot() + " " + team.name().toLowerCase()); } /** * Retrieve a CVAR from the server * * @author Daniele Pantaleone * @param name The CVAR name * @throws RconException If the CVAR could not be retrieved form the server * @return The <tt>Cvar</tt> object associated to the given CVAR name or <tt>null</tt> * if such CVAR is not set on the server **/ public Cvar getCvar(String name) throws RconException { try { String result = this.write(name, true); Pattern pattern = Pattern.compile("\\s*\\\"[\\w+]*\\\"\\sis:\\\"(?<value>[\\w:\\.\\-\\\\/]*)\\\".*", Pattern.CASE_INSENSITIVE); Matcher matcher = pattern.matcher(result); if (matcher.matches()) { String value = matcher.group("value"); if (!value.trim().isEmpty()) { this.log.trace("Retrieved CVAR " + name + ": " + value); return new Cvar(name, value); } } } catch (RconException e) { // Catch and re-throw the same Exception but with more details throw new RconException("could not retrieve CVAR " + name, e); } return null; } /** * Return a <tt>Map</tt> with all the CVARs set on the server * * @author Daniele Pantaleone * @param match A pattern to be used to short the <tt>Cvar</tt> list * @throws RconException If the <tt>Cvar</tt> list couldn't be retrieved from the server * @return A <tt>Map</tt> with all the CVARs set on the server **/ public Map<String, Cvar> getCvarList(String match) throws RconException { // convert to empty if null given match = match != null ? match : ""; String result = this.write("cvarlist " + match, true); Map<String, Cvar> cvarList = new HashMap<String, Cvar>(); Pattern pattern = Pattern.compile("^.{7} (?<name>\\s*\\w+)\\s+\"(?<value>.*)\"$", Pattern.CASE_INSENSITIVE); Matcher matcher = pattern.matcher(result); while (matcher.find()) { String n = matcher.group("name").toLowerCase(); String v = matcher.group("value"); if (!n.trim().isEmpty() && !v.trim().isEmpty()) cvarList.put(n, new Cvar(n, v)); } // leave a trace in the log so we know how many CVARs we retrieved this.log.trace("Retrieved " + cvarList.size() + " CVARs from the server"); return cvarList; } /** * Return the current map name * * @author Daniele Pantaleone * @throws RconException If the current map name couldn't be retrieved * @return The current map name **/ public String getMap() throws RconException { return this.getCvar("mapname").getString(); } /** * Return a <tt>List</tt> of available maps * * @author Daniele Pantaleone * @throws RconException If the map list couldn't be retrieved * @return A <tt>List</tt> of all the maps available on the server **/ public List<String> getMapList() throws RconException { String result = this.write("fdir *.bsp", true); List<String> maplist = new LinkedList<String>(); Pattern pattern = Pattern.compile("^*maps/(?<mapname>.*).bsp$"); String[] lines = result.split("\n"); for (String line : lines) { Matcher matcher = pattern.matcher(line); if (matcher.matches()) maplist.add(matcher.group("mapname")); } return maplist; } /** * Return a <tt>List</tt> of maps matching the given search key * * @author Daniele Pantaleone * @param search The name of the map to search (or a part of it) * @throws RconException If the list of available maps couldn't be computed * @return A <tt>List</tt> of maps matching the given search key **/ public List<String> getMapSoundingLike(String search) throws RconException { List<String> collection = new LinkedList<String>(); List<String> maplist = this.getMapList(); search = search.toLowerCase().trim(); for (String map : maplist) { if (map.toLowerCase().contains(search.toLowerCase())) { collection.add(map); } } return collection; } /** * Return the name of the nextmap set on the server * * @author Daniele Pantaleone * @throws RconException If an RCON command fails in being executed **/ public String getNextMap() throws RconException { Cvar nextmap = null; if ((nextmap = this.getCvar("g_nextmap")) != null) { // Nextmap has been manually changed return nextmap.getString(); } // Return the nextmap set in the mapcycle file nextmap = this.getCvar("g_nextcyclemap"); return nextmap.getString(); } /* /** * Return a <tt>List</tt> containing the result of the <tt>/rcon players</tt> command * * @author Daniele Pantaleone * @throws RconException If we couldn't fetch informations from the server * @return A <tt>List</tt> containing players informations **//* public List<List<String>> getPlayers() throws RconException { String result = this.rcon.send("players", true); // This is the string we expect from the /rcon players command // We need to parse it and build an Array with players informations // // Map: ut4_casa // Players: 1 // Score: R:0 B:0 // 0: [FS]Fenix SPECTATOR k:0 d:0 ping:50 62.75.235.91:27960 List<List<String>> collection = new LinkedList<List<String>>(); Pattern pattern = Pattern.compile("^\\s*(?<slot>\\d+):\\s+(?<name>.*)\\s+(?<team>RED|BLUE|SPECTATOR|FREE)\\s+k:(?<kills>\\d+)\\s+d:(?<deaths>\\d+)\\s+ping:(?<ping>\\d+|CNCT|ZMBI)\\s*([?<address>\\d.]+):([?<port>\\d-]+)?$", Pattern.CASE_INSENSITIVE); String[] lines = result.split("\n"); for (String line: lines) { Matcher matcher = pattern.matcher(line); if (matcher.matches()) { List<String> x = new ArrayList<String>(); x.add(matcher.group("slot")); x.add(matcher.group("name")); x.add(matcher.group("team")); x.add(matcher.group("kills")); x.add(matcher.group("deaths")); x.add(matcher.group("ping")); x.add(matcher.group("address")); x.add(matcher.group("port")); collection.add(x); } } return collection; } */ /** * Return a <tt>List</tt> containing the result of the <tt>/rcon status</tt> command * * @author Daniele Pantaleone * @throws RconException If we couldn't fetch informations from the server * @return A <tt>List</tt> containing status informations **//* public List<List<String>> getStatus() throws RconException { String result = this.rcon.send("status", true); // This is the string we expect from the /rcon status command // We need to parse it and build an Array with players informations // // map: ut4_casa // num score ping name lastmsg address qport rate // --- ----- ---- --------------- ------- --------------------- ----- ----- // 1 19 33 [FS]Fenix 33 62.212.106.216:27960 5294 25000 List<List<String>> collection = new LinkedList<List<String>>(); Pattern pattern = Pattern.compile("^\\s*(?<slot>\\d+)\\s*(?<score>[\\d-]+)\\s*(?<ping>\\d+|CNCT|ZMBI)\\s*(?<name>.*?)\\s*(?<lastmsg>\\d+)\\s*(?<address>[\\d.]+|loopback|localhost):?(?<port>[\\d-]*)\\s*(?<qport>[\\d-]+)\\s*(?<rate>\\d+)$", Pattern.CASE_INSENSITIVE); String[] lines = result.split("\n"); for (String line: lines) { Matcher matcher = pattern.matcher(line); if (matcher.matches()) { List<String> x = new LinkedList<String>(); x.add(matcher.group("slot")); x.add(matcher.group("score")); x.add(matcher.group("ping")); x.add(matcher.group("name")); x.add(matcher.group("lastmsg")); x.add(matcher.group("address")); x.add(matcher.group("port")); x.add(matcher.group("qport")); x.add(matcher.group("rate")); collection.add(x); } } return collection; } */ /** * Kick the specified <tt>Client</tt> from the server * * @author Daniele Pantaleone * @param client The <tt>Client</tt> who is going to be kicked from the server * @throws RconException If the RCON command fails in being executed **/ public void kick(Client client) throws RconException { this.write("kick " + client.getSlot()); } /** * Kick the specified <tt>Client</tt> from the server by specifying a reason * * @author Daniele Pantaleone * @param client The <tt>Client</tt> who is going to be kicked from the server * @param reason The reason why the <tt>Client</tt> is going to be kicked * @throws RconException If the RCON command fails in being executed **/ public void kick(Client client, String reason) throws RconException { this.write("kick " + client.getSlot() + " \"" + reason + "\""); } /** * Instantly kill a <tt>Client</tt> * * @author Daniele Pantaleone * @param client The <tt>Client</tt> who is going to be killed * @throws RconException If the RCON command fails in being executed **/ public void kill(Client client) throws RconException { this.write("smite " + client.getSlot()); } /** * Spawn the server onto a new level * * @author Daniele Pantaleone * @param mapname The name of the level to load * @throws RconException If the RCON command fails in being executed **/ public void map(String mapname) throws RconException { this.write("map " + mapname); } /** * Mute a <tt>Client</tt> * * @author Daniele Pantaleone * @param client The <tt>Client</tt> who is going to be muted * @throws RconException If the <tt>Client</tt> couldn't be muted **/ public void mute(Client client) throws RconException { this.write("mute " + client.getSlot()); } /** * Mute a <tt>Client</tt> * * @author Daniele Pantaleone * @param client The <tt>Client</tt> who is going to be muted * @param seconds The amount of seconds the <tt>Client</tt> will be muted * @throws RconException If the <tt>Client</tt> couldn't be muted **/ public void mute(Client client, int seconds) throws RconException { this.write("mute " + client.getSlot() + " " + seconds); } /** * Nuke a <tt>Client</tt> * * @author Daniele Pantaleone * @param client The <tt>Client</tt> who is going to be nuked * @throws RconException If the <tt>Client</tt> couldn't be nuked **/ public void nuke(Client client) throws RconException { this.write("nuke " + client.getSlot()); } /** * Print a message in the game chat * * @author Daniele Pantaleone * @param message The message to be printed * @throws RconException If the RCON command fails in being executed **/ public void say(String message) throws RconException { if (message.length() > MAX_SAY_STRLEN) { // Splitting the message into multiple strings List<String> collection = Message.split(message, MAX_SAY_STRLEN); for (String sentence : collection) { // Sending the message sentence = sentence.trim(); this.write("say " + Color.WHITE + sentence); try { Thread.sleep(CHAT_DELAY); } catch (InterruptedException e) { } } } else { // No need to split here. Just send the command this.write("say " + Color.WHITE + message); } } /** * Write a bold message in the middle of the screen * * @author Daniele Pantaleone * @param message The message to be printed * @throws RconException If the RCON command fails in being executed **/ public void sayBig(String message) throws RconException { if (message.length() > MAX_SAY_STRLEN) { // Splitting the message into multiple strings List<String> collection = Message.split(message, MAX_SAY_STRLEN); for (String sentence : collection) { sentence = sentence.trim(); this.write("bigtext \"" + Color.WHITE + sentence + "\""); try { Thread.sleep(CENTER_SCREEN_DELAY); } catch (InterruptedException e) { } } } else { // No need to split here. Just send the command this.write("bigtext \"" + Color.WHITE + message + "\""); } } /** * Send a private message to a <tt>Client</tt> * * @author Daniele Pantaleone * @param client The <tt>Client</tt> who will receive the message * @param message The message to be sent * @throws RconException If the RCON command fails in being executed **/ public void sayPrivate(Client client, String message) throws RconException { if (message.length() > MAX_SAY_STRLEN) { // Splitting the message into multiple sentences // In this way it won't overflow the game chat and it will print nicer List<String> collection = Message.split(message, MAX_SAY_STRLEN); for (String sentence : collection) { // Sending the message sentence = sentence.trim(); this.write("tell " + client.getSlot() + " " + Color.WHITE + sentence); try { Thread.sleep(CENTER_SCREEN_DELAY); } catch (InterruptedException e) { } } } else { // No need to split here. Just send the command this.write("tell " + client.getSlot() + " " + Color.WHITE + message); } } /** * Print an in-game message with visibility regulated by the command object * * @author Daniele Pantaleone * @param command The command issued * @param message The message to be printed * @throws RconException If the RCON command fails in being executed **/ public void sayLoudOrPm(Command command, String message) throws RconException { switch (command.getPrefix()) { case NORMAL: this.sayPrivate(command.getClient(), message); break; case LOUD: this.say(message); break; case BIG: this.sayBig(message); break; } } /** * Set a CVAR value * * @author Daniele Pantaleone * @param name The name of the CVAR * @param value The value to assign to the CVAR * @throws RconException If the CVAR could not be set **/ public void setCvar(String name, Object value) throws RconException { this.write("set " + name + " \"" + String.valueOf(value) + "\""); } /** * Slap a <tt>Client</tt> * * @author Daniele Pantaleone * @param client The <tt>Client</tt> who is going to be slapped * @throws RconException If the <tt>Client</tt> couldn't be slapped **/ public void slap(Client client) throws RconException { this.write("slap " + client.getSlot()); } /** * Unban a <tt>Client</tt> from the server * * @author Daniele Pantaleone * @param client The <tt>Client</tt> whose IP address we want to unban * @throws RconException If the <tt>Client</tt> could not be unbanned **/ public void unban(Client client) throws RconException { this.write("removeip " + client.getIp().getHostAddress()); } /** * Write an RCON command in the remote * console without returning the server response * * @author Daniele Pantaleone * @param command The command to execute * @throws RconException If the RCON command fails in being executed **/ public void write(String command) throws RconException { this.rcon.send(command); } /** * Write an RCON command in the remote console. Will return * the server response if specified in the command execution, * otherwise it will return <tt>null</tt> * * @author Daniele Pantaleone * @param command The command to execute * @throws RconException If the RCON command fails in being executed * @return The server response to the RCON command if specified * in the command execution, otherwise <tt>null</tt> **/ public String write(String command, boolean read) throws RconException { if (!read) { this.write(command); return null; } return this.rcon.send(command, true); } }