diff --git a/GameServer/conf/world.xml b/GameServer/conf/world.xml
index 4e7ed86..3b568c7 100644
--- a/GameServer/conf/world.xml
+++ b/GameServer/conf/world.xml
@@ -81,4 +81,12 @@
org.moparscape.msc.gs.persistence.impl.XMLUsingXStream
+
+
+ 15
+
+ true
+
+
+ 5
diff --git a/GameServer/src/org/moparscape/msc/config/Config.java b/GameServer/src/org/moparscape/msc/config/Config.java
index 066c94e..3e30c12 100644
--- a/GameServer/src/org/moparscape/msc/config/Config.java
+++ b/GameServer/src/org/moparscape/msc/config/Config.java
@@ -43,6 +43,9 @@ public class Config {
OS_LEVEL_THROTTLE_ALERT, OS_LEVEL_UNBLOCK_FAILED_ALERT,
CONGRATS_FOR_MAX_LEVEL;
public static String DATA_STORE;
+ public static int PACKET_PER_SECOND_THRESHOLD;
+ public static boolean PACKET_PER_SECOND_ALERT;
+ public static int AFK_TIMEOUT;
static {
loadEnv();
@@ -130,6 +133,11 @@ public class Config {
.getProperty("max-level-congrats"));
DATA_STORE = props.getProperty("data-store");
+
+ PACKET_PER_SECOND_THRESHOLD = Integer.parseInt(props.getProperty("packet-per-second-threshold"));
+ PACKET_PER_SECOND_ALERT = Boolean.parseBoolean(props.getProperty("packet-per-second-alert"));
+
+ AFK_TIMEOUT = Integer.parseInt(props.getProperty("afk-timeout"));
props.clear();
diff --git a/GameServer/src/org/moparscape/msc/gs/Server.java b/GameServer/src/org/moparscape/msc/gs/Server.java
index f78b934..4ae7bab 100644
--- a/GameServer/src/org/moparscape/msc/gs/Server.java
+++ b/GameServer/src/org/moparscape/msc/gs/Server.java
@@ -14,6 +14,7 @@ import org.apache.mina.transport.socket.nio.SocketSessionConfig;
import org.moparscape.msc.config.Config;
import org.moparscape.msc.gs.connection.RSCConnectionHandler;
import org.moparscape.msc.gs.connection.filter.ConnectionFilter;
+import org.moparscape.msc.gs.connection.filter.PacketThrottler;
import org.moparscape.msc.gs.core.GameEngine;
import org.moparscape.msc.gs.core.LoginConnector;
import org.moparscape.msc.gs.event.DelayedEvent;
@@ -46,7 +47,6 @@ public class Server {
displayConfigDefaulting(configFile);
}
-
Config.initConfig(configFile);
world = Instance.getWorld();
try {
@@ -156,6 +156,8 @@ public class Server {
acceptor = new SocketAcceptor(Runtime.getRuntime()
.availableProcessors() + 1, Executors.newCachedThreadPool());
+ acceptor.getFilterChain().addFirst("packetthrottler",
+ PacketThrottler.getInstance());
acceptor.getFilterChain().addFirst("connectionfilter",
new ConnectionFilter());
IoAcceptorConfig config = new SocketAcceptorConfig();
@@ -252,7 +254,7 @@ public class Server {
public static Server getServer() {
return server;
}
-
+
private static void displayConfigDefaulting(String file) {
System.out.println("Defaulting to use " + file);
}
diff --git a/GameServer/src/org/moparscape/msc/gs/connection/filter/PacketThrottler.java b/GameServer/src/org/moparscape/msc/gs/connection/filter/PacketThrottler.java
new file mode 100644
index 0000000..c2dad4e
--- /dev/null
+++ b/GameServer/src/org/moparscape/msc/gs/connection/filter/PacketThrottler.java
@@ -0,0 +1,113 @@
+package org.moparscape.msc.gs.connection.filter;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import net.jcip.annotations.ThreadSafe;
+
+import org.apache.mina.common.IoFilterAdapter;
+import org.apache.mina.common.IoSession;
+import org.moparscape.msc.config.Config;
+import org.moparscape.msc.gs.Instance;
+import org.moparscape.msc.gs.alert.AlertHandler;
+import org.moparscape.msc.gs.event.DelayedEvent;
+import org.moparscape.msc.gs.model.Player;
+import org.moparscape.msc.gs.util.annotation.Singleton;
+
+/**
+ *
+ * This filter will disconnect players that have exceeded the packet per second
+ * threshold, and if configured, send an alert.
+ *
+ * @author CodeForFame
+ *
+ */
+@ThreadSafe
+@Singleton
+public class PacketThrottler extends IoFilterAdapter {
+
+ /////////////////////////////////////////
+ ///////// Singleton Boilerplate /////////
+ /////////////////////////////////////////
+
+ private static final PacketThrottler instance = new PacketThrottler();
+
+ public static PacketThrottler getInstance() {
+ return instance;
+ }
+
+ /////////////////////////////////////////
+ /////// Singleton Boilerplate End ///////
+ /////////////////////////////////////////
+
+ /**
+ * The map of username hashes to packet per second.
+ */
+ private Map playerToPacketCount = new ConcurrentHashMap();
+
+ private PacketThrottler() {
+ // Clears the count every second, so we can check the packets per second.
+ Instance.getDelayedEventHandler().add(new DelayedEvent(null, 1000) {
+
+ @Override
+ public void run() {
+ playerToPacketCount.clear();
+ }
+
+ });
+ }
+
+ @Override
+ public void messageReceived(NextFilter nextFilter, IoSession session,
+ Object message) {
+
+ Player player = (Player) session.getAttachment();
+ if (session.isClosing() || player.destroyed()) {
+ return;
+ }
+
+ int count = incrementAndGet(player.getUsernameHash());
+
+ if (count > Config.PACKET_PER_SECOND_THRESHOLD) {
+
+ if (Config.PACKET_PER_SECOND_ALERT) {
+ // If the player is initialized, then use the username,
+ // otherwise use the IP.
+ String s = (player.isInitialized() ? player.getUsername()
+ : player.getCurrentIP())
+ + " has exceeded the packet per second threshold";
+ // Sends an alert with a priority of 2.
+ AlertHandler.sendAlert(s, 2);
+ }
+
+ // Destroys the user and discards the packet.
+ player.destroy(true);
+ return;
+ }
+
+ nextFilter.messageReceived(session, message);
+ }
+
+ /**
+ * Increments and returns the current count.
+ *
+ * @param hash
+ * - The hash of the player.
+ */
+ private int incrementAndGet(long hash) {
+ final int count;
+
+ // Even though operations are atomic, there are multiple, therefore it
+ // needs to be synchronized.
+ synchronized (playerToPacketCount) {
+
+ // If it is null, it will default to 0
+ count = playerToPacketCount.get(hash) + 1;
+
+ // Update/Create entry
+ playerToPacketCount.put(hash, count);
+ }
+
+ return count;
+ }
+}
diff --git a/GameServer/src/org/moparscape/msc/gs/core/ClientUpdater.java b/GameServer/src/org/moparscape/msc/gs/core/ClientUpdater.java
index 70a01c1..c3ce3b9 100644
--- a/GameServer/src/org/moparscape/msc/gs/core/ClientUpdater.java
+++ b/GameServer/src/org/moparscape/msc/gs/core/ClientUpdater.java
@@ -3,6 +3,7 @@ package org.moparscape.msc.gs.core;
import java.util.ArrayList;
import java.util.List;
+import org.moparscape.msc.config.Config;
import org.moparscape.msc.gs.Instance;
import org.moparscape.msc.gs.builders.GameObjectPositionPacketBuilder;
import org.moparscape.msc.gs.builders.ItemPositionPacketBuilder;
@@ -396,13 +397,17 @@ public final class ClientUpdater {
if (curTime - p.getLastPing() >= 30000) {
p.destroy(false);
} else if (p.warnedToMove()) {
- if (curTime - p.getLastMoved() >= 960000 && p.loggedIn()) {
+ if (curTime - p.getLastMoved() >= ((Config.AFK_TIMEOUT + 1) * 60000)
+ && p.loggedIn()) {
p.destroy(false);
}
- } else if (curTime - p.getLastMoved() >= 900000) {
+ } else if (curTime - p.getLastMoved() >= (Config.AFK_TIMEOUT * 60000)
+ && !p.warnedToMove()) {
p.getActionSender()
.sendMessage(
- "@cya@You have not moved for 15 mins, please move to a new area to avoid logout.");
+ "@cya@You have not moved for "
+ + Config.AFK_TIMEOUT
+ + " mins, please move to a new area to avoid logout.");
p.warnToMove();
}
}
diff --git a/GameServer/src/org/moparscape/msc/gs/util/annotation/Singleton.java b/GameServer/src/org/moparscape/msc/gs/util/annotation/Singleton.java
new file mode 100644
index 0000000..5ee4599
--- /dev/null
+++ b/GameServer/src/org/moparscape/msc/gs/util/annotation/Singleton.java
@@ -0,0 +1,19 @@
+package org.moparscape.msc.gs.util.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ *
+ * This annotation is used to denote that a Java class is a singleton. Scala
+ * objects do not need this annotation, because they are singletons by
+ * definition.
+ *
+ * @author CodeForFame
+ *
+ */
+@Documented
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.SOURCE)
+public @interface Singleton {
+
+}