package net.scapeemulator.game;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.internal.logging.InternalLoggerFactory;
import io.netty.util.internal.logging.Slf4JLoggerFactory;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.sql.SQLException;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.script.ScriptException;
import net.scapeemulator.cache.Cache;
import net.scapeemulator.cache.ChecksumTable;
import net.scapeemulator.cache.FileStore;
import net.scapeemulator.game.cache.MapLoader;
import net.scapeemulator.game.content.Content;
import net.scapeemulator.game.io.DummyPlayerSerializer;
import net.scapeemulator.game.io.JdbcSerializer;
import net.scapeemulator.game.io.Serializer;
import net.scapeemulator.game.model.Ladders;
import net.scapeemulator.game.model.World;
import net.scapeemulator.game.model.definition.ItemDefinitions;
import net.scapeemulator.game.model.definition.NPCDefinitions;
import net.scapeemulator.game.model.definition.ObjectDefinitions;
import net.scapeemulator.game.model.definition.VarbitDefinitions;
import net.scapeemulator.game.model.definition.WidgetDefinitions;
import net.scapeemulator.game.model.object.GroundObjectPopulator;
import net.scapeemulator.game.model.pathfinding.MapDataListener;
import net.scapeemulator.game.model.player.EquipmentDefinition;
import net.scapeemulator.game.model.player.bank.BankerAction;
import net.scapeemulator.game.model.player.consumable.Consumables;
import net.scapeemulator.game.model.player.skills.construction.Construction;
import net.scapeemulator.game.model.player.skills.cooking.Cooking;
import net.scapeemulator.game.model.player.skills.farming.Farming;
import net.scapeemulator.game.model.player.skills.firemaking.Firemaking;
import net.scapeemulator.game.model.player.skills.fishing.Fishing;
import net.scapeemulator.game.model.player.skills.herblore.Herblore;
import net.scapeemulator.game.model.player.skills.magic.Magic;
import net.scapeemulator.game.model.player.skills.mining.Mining;
import net.scapeemulator.game.model.player.skills.prayer.PrayerSkill;
import net.scapeemulator.game.model.player.skills.runecrafting.Runecrafting;
import net.scapeemulator.game.model.player.skills.woodcutting.Woodcutting;
import net.scapeemulator.game.msg.CodecRepository;
import net.scapeemulator.game.msg.MessageDispatcher;
import net.scapeemulator.game.net.HttpChannelInitializer;
import net.scapeemulator.game.net.RsChannelInitializer;
import net.scapeemulator.game.net.login.LoginService;
import net.scapeemulator.game.net.update.UpdateService;
import net.scapeemulator.game.plugin.PluginLoader;
import net.scapeemulator.game.plugin.ScriptContext;
import net.scapeemulator.game.util.LandscapeKeyTable;
import net.scapeemulator.util.NetworkConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class GameServer {
private static final Logger logger = LoggerFactory.getLogger(GameServer.class);
private static GameServer server;
private static final boolean HTTP_SERVER_ENABLED = false;
private static final boolean LOAD_MAPS_ON_STARTUP = false;
public static void main(String[] args) {
try {
InternalLoggerFactory.setDefaultFactory(new Slf4JLoggerFactory());
GameServer instance = getInstance();
instance.load();
if (HTTP_SERVER_ENABLED) {
try {
instance.httpBind(new InetSocketAddress(NetworkConstants.HTTP_PORT));
} catch (Throwable t) {
logger.warn("Failed to bind to HTTP port.", t);
}
instance.httpBind(new InetSocketAddress(NetworkConstants.HTTP_ALT_PORT));
}
try {
instance.serviceBind(new InetSocketAddress(NetworkConstants.SSL_PORT));
} catch (Throwable t) {
logger.warn("Failed to bind to SSL port.", t);
}
instance.serviceBind(new InetSocketAddress(NetworkConstants.GAME_PORT));
instance.start();
} catch (Throwable t) {
logger.error("Failed to start server.", t);
}
}
private final World world = World.getWorld();
private final ExecutorService executor = Executors.newCachedThreadPool();
private final EventLoopGroup loopGroup = new NioEventLoopGroup();
private final ServerBootstrap serviceBootstrap = new ServerBootstrap();
private final ServerBootstrap httpBootstrap = new ServerBootstrap();
private final UpdateService updateService = new UpdateService();
private final PluginLoader pluginLoader = new PluginLoader();
private final ScriptContext scriptContext = new ScriptContext();
private MapLoader mapLoader;
private int ticks = 0;
private final MessageDispatcher messageDispatcher = new MessageDispatcher();
private LoginService loginService;
private Serializer serializer;
private Cache cache;
private ChecksumTable checksumTable;
private LandscapeKeyTable landscapeKeyTable;
private CodecRepository codecRepository;
private final int version = 530;
private GameServer() {
}
private Serializer createPlayerSerializer() throws IOException, SQLException {
Properties properties = new Properties();
try (InputStream is = new FileInputStream("data/game/config/serializer.conf")) {
properties.load(is);
}
String type = (String) properties.get("type");
switch (type) {
case "dummy":
return new DummyPlayerSerializer();
case "jdbc":
String url = (String) properties.get("url");
String username = (String) properties.get("username");
String password = (String) properties.get("password");
return new JdbcSerializer(url, username, password);
default:
throw new IOException("unknown serializer type");
}
}
public void httpBind(SocketAddress address) throws InterruptedException {
logger.info("Binding to HTTP address: " + address + "...");
httpBootstrap.localAddress(address).bind().sync();
}
public void serviceBind(SocketAddress address) throws InterruptedException {
logger.info("Binding to service address: " + address + "...");
serviceBootstrap.localAddress(address).bind().sync();
}
public void load() throws IOException, ScriptException, SQLException {
logger.info("Starting game server...");
/* load landscape keys */
landscapeKeyTable = LandscapeKeyTable.open("data/game/landscape-keys");
/* load game cache */
cache = new Cache(FileStore.open("data/game/cache"));
checksumTable = cache.createChecksumTable();
ItemDefinitions.init(cache);
ObjectDefinitions.init(cache);
WidgetDefinitions.init(cache);
VarbitDefinitions.init(cache);
NPCDefinitions.init(cache);
EquipmentDefinition.init();
/* load player serializer from config file */
serializer = createPlayerSerializer();
logger.info("Using serializer: " + serializer + ".");
loginService = new LoginService(serializer);
serializer.loadNPCDrops();
serializer.loadNPCDefinitions();
serializer.loadNPCSpawns();
serializer.loadShops();
/* load all the maps into memory */
mapLoader = new MapLoader(cache, landscapeKeyTable);
mapLoader.addListener(new GroundObjectPopulator(world.getGroundObjects()));
mapLoader.addListener(new MapDataListener(world.getTraversalMap()));
mapLoader.load(LOAD_MAPS_ON_STARTUP);
/* load message codecs and dispatcher */
logger.info("Populating codecs...");
codecRepository = new CodecRepository(landscapeKeyTable);
/* load the server pluginLoader */
logger.info("Loading plugins...");
pluginLoader.setContext(scriptContext);
pluginLoader.load("./data/game/plugins/");
/* decorate each of the dispatchers */
messageDispatcher.decorateDispatchers(scriptContext);
Ladders.init();
/* bind the non-script skill handlers to the dispatchers */
Fishing.initialize();
Magic.initialize();
Cooking.initialize();
Mining.initialize();
Firemaking.initialize();
PrayerSkill.initialize();
Herblore.initialize();
Runecrafting.initialize();
Woodcutting.initialize();
Construction.initialize();
Farming.initialize();
/* bind other content */
Consumables.initialize();
messageDispatcher.getNpcDispatcher().bind(new BankerAction());
Content.init();
/* start netty */
httpBootstrap.group(loopGroup);
httpBootstrap.channel(NioServerSocketChannel.class);
httpBootstrap.childHandler(new HttpChannelInitializer());
serviceBootstrap.group(loopGroup);
serviceBootstrap.channel(NioServerSocketChannel.class);
serviceBootstrap.childHandler(new RsChannelInitializer(this));
}
public void start() {
logger.info("Ready for connections.");
/* start login and update services */
executor.submit(loginService);
executor.submit(updateService);
/* main game tick loop */
for (;;) {
long start = System.currentTimeMillis();
tick();
long elapsed = (System.currentTimeMillis() - start);
long waitFor = 600 - elapsed;
if (waitFor > 0) {
try {
Thread.sleep(waitFor);
} catch (InterruptedException e) {
/* ignore */
}
}
}
}
private void tick() {
/* As the MobList class is not thread-safe, players must be registered within the game logic processing code. */
ticks++;
loginService.registerNewPlayers(world);
world.tick();
}
public World getWorld() {
return world;
}
public int getTickTimestamp() {
return ticks;
}
public LoginService getLoginService() {
return loginService;
}
public UpdateService getUpdateService() {
return updateService;
}
public int getVersion() {
return version;
}
public Cache getCache() {
return cache;
}
public Serializer getSerializer() {
return serializer;
}
public MapLoader getMapLoader() {
return mapLoader;
}
public ChecksumTable getChecksumTable() {
return checksumTable;
}
public CodecRepository getCodecRepository() {
return codecRepository;
}
public MessageDispatcher getMessageDispatcher() {
return messageDispatcher;
}
public LandscapeKeyTable getLandscapeKeyTable() {
return landscapeKeyTable;
}
public static GameServer getInstance() {
if (server == null) {
server = new GameServer();
}
return server;
}
}