431 lines
13 KiB
Java
431 lines
13 KiB
Java
package org.moparscape.msc.gs.core;
|
|
|
|
import java.io.IOException;
|
|
import java.io.OutputStream;
|
|
import java.io.PrintStream;
|
|
import java.io.PrintWriter;
|
|
import java.io.StringWriter;
|
|
import java.util.Iterator;
|
|
import java.util.TreeMap;
|
|
|
|
import org.apache.mina.common.IoSession;
|
|
import org.moparscape.msc.config.Config;
|
|
import org.moparscape.msc.gs.Instance;
|
|
import org.moparscape.msc.gs.connection.PacketQueue;
|
|
import org.moparscape.msc.gs.connection.RSCPacket;
|
|
import org.moparscape.msc.gs.connection.filter.IPBanManager;
|
|
import org.moparscape.msc.gs.event.DelayedEvent;
|
|
import org.moparscape.msc.gs.model.ActiveTile;
|
|
import org.moparscape.msc.gs.model.Npc;
|
|
import org.moparscape.msc.gs.model.Player;
|
|
import org.moparscape.msc.gs.model.Shop;
|
|
import org.moparscape.msc.gs.model.World;
|
|
import org.moparscape.msc.gs.model.snapshot.Snapshot;
|
|
import org.moparscape.msc.gs.phandler.PacketHandler;
|
|
import org.moparscape.msc.gs.phandler.PacketHandlerDef;
|
|
import org.moparscape.msc.gs.plugins.dependencies.NpcAI;
|
|
import org.moparscape.msc.gs.tools.Captcha;
|
|
import org.moparscape.msc.gs.util.Logger;
|
|
import org.moparscape.msc.gs.util.PersistenceManager;
|
|
|
|
/**
|
|
* The central motor of the game. This class is responsible for the primary
|
|
* operation of the entire game.
|
|
*/
|
|
public final class GameEngine extends Thread {
|
|
|
|
private static Captcha captcha;
|
|
/**
|
|
* World instance
|
|
*/
|
|
private static final World world = Instance.getWorld();
|
|
|
|
public static Captcha getCaptcha() {
|
|
return captcha;
|
|
}
|
|
|
|
/**
|
|
* Responsible for updating all connected clients
|
|
*/
|
|
private ClientUpdater clientUpdater = new ClientUpdater();
|
|
/**
|
|
* Handles delayed events rather than events to be ran every iteration
|
|
*/
|
|
private DelayedEventHandler eventHandler = new DelayedEventHandler();
|
|
/**
|
|
* When the update loop was last ran, required for throttle
|
|
*/
|
|
private long lastSentClientUpdate = GameEngine.getTime();
|
|
private long lastSentClientUpdateFast = GameEngine.getTime();
|
|
private long lastCleanedChatlogs = 0;
|
|
private int lastCleanedChatlogsOutput = 0;
|
|
/**
|
|
* The mapping of packet IDs to their handler
|
|
*/
|
|
private TreeMap<Integer, PacketHandler> packetHandlers = new TreeMap<Integer, PacketHandler>();
|
|
/**
|
|
* The packet queue to be processed
|
|
*/
|
|
private PacketQueue<RSCPacket> packetQueue;
|
|
/**
|
|
* Whether the engine's thread is running
|
|
*/
|
|
private boolean running = true;
|
|
private static volatile long time = 0;
|
|
|
|
/**
|
|
* Use this instead of System.currentTimeIllis, as each call does a system
|
|
* call, and potentially a hardware poll...<br>
|
|
* Also, you don't generally need the time to be updated more often than
|
|
* each part in the main loop.
|
|
*
|
|
* @return The current time.
|
|
*/
|
|
public static long getTime() {
|
|
return time;
|
|
}
|
|
|
|
/**
|
|
* Constructs a new game engine with an empty packet queue.
|
|
*/
|
|
public GameEngine() {
|
|
captcha = new Captcha();
|
|
captcha.init();
|
|
packetQueue = new PacketQueue<RSCPacket>();
|
|
loadPacketHandlers();
|
|
for (Shop shop : world.getShops()) {
|
|
shop.initRestock();
|
|
}
|
|
redirectSystemStreams();
|
|
}
|
|
|
|
public void emptyWorld() {
|
|
for (Player p : world.getPlayers()) {
|
|
p.save();
|
|
p.getActionSender().sendLogout();
|
|
}
|
|
Instance.getServer().getLoginConnector().getActionSender()
|
|
.saveProfiles();
|
|
}
|
|
|
|
/**
|
|
* Ban dummy packet flood private Map<InetAddress, Long> clients; private
|
|
* Map<InetAddress, Integer> counts; private Map<InetAddress, Integer>
|
|
* written; public void flagSession(IoSession session) { InetAddress addr =
|
|
* getAddress(session); String ip = addr.toString(); ip =
|
|
* ip.replaceAll("/",""); long now = System.currentTimeMillis(); int c = 0;
|
|
* if(counts.containsKey(addr) && clients.containsKey(addr)) { try { c =
|
|
* counts.get(addr); } catch(Exception e) { Logging.debug("Error: " + e); }
|
|
* if(c >= 10) { if(!written.containsKey(addr)) { try {
|
|
* Logging.debug("Dummy packet flooder IP: " + ip); BufferedWriter bf2 = new
|
|
* BufferedWriter(new FileWriter("dummy.log", true));
|
|
* bf2.write("sudo /sbin/route add " + addr.getHostAddress() +
|
|
* " gw 127.0.0.1"); bf2.newLine(); bf2.close(); written.put(addr, 1); }
|
|
* catch(Exception e) { System.err.println(e);} } } } if
|
|
* (clients.containsKey(addr)) { long lastConnTime = clients.get(addr); if
|
|
* (now - lastConnTime < 2000) { if(!counts.containsKey(addr)) {
|
|
* counts.put(addr, 0); } else c = counts.get(addr) + 1; counts.put(addr,
|
|
* c); } else { clients.put(addr, now); } } else { clients.put(addr, now); }
|
|
* }
|
|
*
|
|
* private InetAddress getAddress(IoSession io) { return
|
|
* ((InetSocketAddress) io.getRemoteAddress()).getAddress(); }
|
|
*/
|
|
/**
|
|
* Returns the current packet queue.
|
|
*
|
|
* @return A <code>PacketQueue</code>
|
|
*/
|
|
public PacketQueue<RSCPacket> getPacketQueue() {
|
|
return packetQueue;
|
|
}
|
|
|
|
public void kill() {
|
|
Logger.println("Terminating GameEngine");
|
|
running = false;
|
|
}
|
|
|
|
/**
|
|
* Loads the packet handling classes from the persistence manager.
|
|
*/
|
|
protected void loadPacketHandlers() {
|
|
PacketHandlerDef[] handlerDefs = (PacketHandlerDef[]) PersistenceManager
|
|
.load("PacketHandlers.xml");
|
|
for (PacketHandlerDef handlerDef : handlerDefs) {
|
|
try {
|
|
String className = handlerDef.getClassName();
|
|
Class<?> c = Class.forName(className);
|
|
if (c != null) {
|
|
PacketHandler handler = (PacketHandler) c.newInstance();
|
|
for (int packetID : handlerDef.getAssociatedPackets()) {
|
|
packetHandlers.put(packetID, handler);
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
Logger.error(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void processClients() {
|
|
clientUpdater.sendQueuedPackets();
|
|
long now = GameEngine.getTime();
|
|
if (now - lastSentClientUpdate >= 600) {
|
|
if (now - lastSentClientUpdate >= 1000) {
|
|
// Logger.println("MAJOR UPDATE DELAYED: " + (now -
|
|
// lastSentClientUpdate));
|
|
}
|
|
lastSentClientUpdate = now;
|
|
clientUpdater.doMajor();
|
|
}
|
|
if (now - lastSentClientUpdateFast >= 104) {
|
|
if (now - lastSentClientUpdateFast >= 6000) {
|
|
// Logger.println("MINOR UPDATE DELAYED: " + (now -
|
|
// lastSentClientUpdateFast));
|
|
}
|
|
lastSentClientUpdateFast = now;
|
|
clientUpdater.doMinor();
|
|
}
|
|
}
|
|
|
|
private void processEvents() {
|
|
eventHandler.doEvents();
|
|
}
|
|
|
|
public DelayedEventHandler getEventHandler() {
|
|
return eventHandler;
|
|
}
|
|
|
|
/**
|
|
* Redirects system err
|
|
*/
|
|
public static void redirectSystemStreams() {
|
|
OutputStream out = new OutputStream() {
|
|
@Override
|
|
public void write(int b) throws IOException {
|
|
String line = String.valueOf((char) b);
|
|
Logger.systemerr(line);
|
|
}
|
|
|
|
@Override
|
|
public void write(byte[] b, int off, int len) throws IOException {
|
|
String line = new String(b, off, len);
|
|
Logger.systemerr(line);
|
|
}
|
|
|
|
@Override
|
|
public void write(byte[] b) throws IOException {
|
|
write(b, 0, b.length);
|
|
}
|
|
};
|
|
System.setErr(new PrintStream(out, true));
|
|
}
|
|
|
|
private void processIncomingPackets() {
|
|
for (RSCPacket p : packetQueue.getPackets()) {
|
|
IoSession session = p.getSession();
|
|
Player player = (Player) session.getAttachment();
|
|
if (player.getUsername() == null && p.getID() != 32
|
|
&& p.getID() != 77 && p.getID() != 0) {
|
|
final String ip = player.getCurrentIP();
|
|
IPBanManager.throttle(ip);
|
|
continue;
|
|
}
|
|
PacketHandler handler = packetHandlers.get(p.getID());
|
|
player.ping();
|
|
if (handler != null) {
|
|
try {
|
|
handler.handlePacket(p, session);
|
|
try {
|
|
if (p.getID() != 5) {
|
|
// String s = "[PACKET] " +
|
|
// session.getRemoteAddress().toString().replace("/",
|
|
// "") + " : " + p.getID()+
|
|
// " ["+handler.getClass().toString()+"]" + " : "+
|
|
// player.getUsername() + " : ";
|
|
// for(Byte b : p.getData())
|
|
// s += b;
|
|
// Logger.println(s);
|
|
}
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
String s;
|
|
StringWriter sw = new StringWriter();
|
|
PrintWriter pw = new PrintWriter(sw, true);
|
|
e.printStackTrace(pw);
|
|
pw.flush();
|
|
sw.flush();
|
|
s = sw.toString();
|
|
Logger.error("Exception with p[" + p.getID() + "] from "
|
|
+ player.getUsername() + " ["
|
|
+ player.getCurrentIP() + "]: " + s);
|
|
player.getActionSender().sendLogout();
|
|
player.destroy(false);
|
|
}
|
|
} else {
|
|
Logger.error("Unhandled packet from " + player.getCurrentIP()
|
|
+ ": " + p.getID() + "len: " + p.getLength());
|
|
}
|
|
}
|
|
}
|
|
|
|
public void processLoginServer() {
|
|
LoginConnector connector = Instance.getServer().getLoginConnector();
|
|
if (connector != null) {
|
|
connector.processIncomingPackets();
|
|
connector.sendQueuedPackets();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The thread execution process.
|
|
*/
|
|
public void run() {
|
|
Logger.println("GameEngine now running");
|
|
// Captcha.loadCharacters();
|
|
for (Npc n : Instance.getWorld().getNpcs()) {
|
|
for (NpcAI ai : Instance.getPluginHandler().getNpcAI()) {
|
|
if (n.getID() == ai.getID()) {
|
|
n.setScripted(true);
|
|
}
|
|
}
|
|
}
|
|
time = System.currentTimeMillis();
|
|
|
|
eventHandler
|
|
.add(new DelayedEvent(null, Config.GARBAGE_COLLECT_INTERVAL) { // Ran
|
|
// every
|
|
// 50*2
|
|
// minutes
|
|
@Override
|
|
public void run() {
|
|
new Thread(new Runnable() {
|
|
public void run() {
|
|
garbageCollect();
|
|
}
|
|
}).start();
|
|
}
|
|
});
|
|
eventHandler.add(new DelayedEvent(null, Config.SAVE_INTERVAL) { // 5 min
|
|
public void run() {
|
|
world.dbKeepAlive();
|
|
long now = GameEngine.getTime();
|
|
for (Player p : world.getPlayers()) {
|
|
if (now - p.getLastSaveTime() >= Config.SAVE_INTERVAL) {
|
|
p.save();
|
|
p.setLastSaveTime(now);
|
|
}
|
|
}
|
|
Instance.getServer().getLoginConnector()
|
|
.getActionSender().saveProfiles();
|
|
}
|
|
});
|
|
while (running) {
|
|
try {
|
|
Thread.sleep(50);
|
|
} catch (InterruptedException ie) {
|
|
}
|
|
time = System.currentTimeMillis();
|
|
long Delay;
|
|
processLoginServer();
|
|
Delay = System.currentTimeMillis() - time;
|
|
if (Delay >= 1000)
|
|
Logger.println("processLoginServer is taking longer than it should, exactly "
|
|
+ Delay + "ms");
|
|
time = System.currentTimeMillis();
|
|
processIncomingPackets();
|
|
Delay = System.currentTimeMillis() - time;
|
|
if (Delay >= 1000)
|
|
Logger.println("processIncomingPackets is taking longer than it should, exactly "
|
|
+ Delay + "ms");
|
|
time = System.currentTimeMillis();
|
|
processEvents();
|
|
Delay = System.currentTimeMillis() - time;
|
|
if (Delay >= 1000)
|
|
Logger.println("processEvents is taking longer than it should, exactly "
|
|
+ Delay + "ms");
|
|
time = System.currentTimeMillis();
|
|
processClients();
|
|
Delay = System.currentTimeMillis() - time;
|
|
if (Delay >= 1000)
|
|
Logger.println("processClients is taking longer than it should, exactly "
|
|
+ Delay + "ms");
|
|
time = System.currentTimeMillis();
|
|
cleanSnapshotDeque();
|
|
Delay = System.currentTimeMillis() - time;
|
|
if (Delay >= 1000)
|
|
Logger.println("processSnapshotDeque is taking longer than it should, exactly "
|
|
+ Delay + "ms");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cleans snapshots of entries over 60 seconds old (executed every second)
|
|
*/
|
|
public void cleanSnapshotDeque() {
|
|
long curTime = GameEngine.getTime();
|
|
if (curTime - lastCleanedChatlogs > 1000) {
|
|
lastCleanedChatlogs = curTime;
|
|
lastCleanedChatlogsOutput++;
|
|
if (lastCleanedChatlogsOutput > 60 * 5) {
|
|
Logger.println("----------------------------------------------");
|
|
Logger.println(world.getSnapshots().size() + " items on deque");
|
|
}
|
|
Iterator<Snapshot> i = world.getSnapshots().descendingIterator();
|
|
Snapshot s = null;
|
|
while (i.hasNext()) {
|
|
s = i.next();
|
|
if (curTime - s.getTimestamp() > 60000) {
|
|
i.remove();
|
|
s = null;
|
|
} else {
|
|
s = null;
|
|
}
|
|
}
|
|
i = null;
|
|
if (lastCleanedChatlogsOutput > 60 * 5) {
|
|
Logger.println(world.getSnapshots().size()
|
|
+ " items on deque AFTER CLEANUP");
|
|
Logger.println("----------------------------------------------");
|
|
lastCleanedChatlogsOutput = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cleans garbage (Tilecleanup)
|
|
*/
|
|
public synchronized void garbageCollect() {
|
|
long startTime = System.currentTimeMillis();
|
|
int curMemory = (int) (Runtime.getRuntime().totalMemory() - Runtime
|
|
.getRuntime().freeMemory()) / 1000;
|
|
int tileObjs = 0;
|
|
int cleaned = 0;
|
|
for (int i = 0; i < Instance.getWorld().tiles.length; i++) {
|
|
for (int in = 0; in < Instance.getWorld().tiles[i].length; in++) {
|
|
ActiveTile tile = Instance.getWorld().tiles[i][in];
|
|
if (tile != null) {
|
|
tileObjs++;
|
|
if (!tile.hasGameObject() && !tile.hasItems()
|
|
&& !tile.hasNpcs() && !tile.hasPlayers()) {
|
|
Instance.getWorld().tiles[i][in] = null;
|
|
cleaned++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Runtime.getRuntime().gc();
|
|
int newMemory = (int) (Runtime.getRuntime().totalMemory() - Runtime
|
|
.getRuntime().freeMemory()) / 1000;
|
|
Logger.println("GARBAGE COLLECT | Executing Memory Cleanup");
|
|
Logger.println("GARBAGE COLLECT | Memory before: " + curMemory + "kb"
|
|
+ " Memory after: " + newMemory + " (Freed: "
|
|
+ (curMemory - newMemory) + "kb)");
|
|
Logger.println("GARBAGE COLLECT | Cleanup took "
|
|
+ (System.currentTimeMillis() - startTime) + "ms");
|
|
}
|
|
}
|