|
|
|
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
|
|
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
|
|
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
|
|
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
|
|
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
|
|
|
// Magicbane Emulator Project © 2013 - 2022
|
|
|
|
// www.magicbane.com
|
|
|
|
|
|
|
|
|
|
|
|
package engine.server.world;
|
|
|
|
|
|
|
|
import engine.Enum;
|
|
|
|
import engine.Enum.DispatchChannel;
|
|
|
|
import engine.Enum.MinionType;
|
|
|
|
import engine.Enum.SupportMsgType;
|
|
|
|
import engine.InterestManagement.HeightMap;
|
|
|
|
import engine.InterestManagement.RealmMap;
|
|
|
|
import engine.InterestManagement.WorldGrid;
|
|
|
|
import engine.db.archive.DataWarehouse;
|
|
|
|
import engine.db.handlers.dbPowerHandler;
|
|
|
|
import engine.exception.MsgSendException;
|
|
|
|
import engine.gameManager.*;
|
|
|
|
import engine.job.JobContainer;
|
|
|
|
import engine.job.JobScheduler;
|
|
|
|
import engine.jobs.LogoutCharacterJob;
|
|
|
|
import engine.mobileAI.Threads.MobAIThread;
|
|
|
|
import engine.mobileAI.Threads.MobRespawnThread;
|
|
|
|
import engine.net.Dispatch;
|
|
|
|
import engine.net.DispatchMessage;
|
|
|
|
import engine.net.ItemProductionManager;
|
|
|
|
import engine.net.Network;
|
|
|
|
import engine.net.client.ClientConnection;
|
|
|
|
import engine.net.client.ClientConnectionManager;
|
|
|
|
import engine.net.client.ClientMessagePump;
|
|
|
|
import engine.net.client.Protocol;
|
|
|
|
import engine.net.client.msg.RefinerScreenMsg;
|
|
|
|
import engine.net.client.msg.TrainerInfoMsg;
|
|
|
|
import engine.net.client.msg.UpdateStateMsg;
|
|
|
|
import engine.net.client.msg.chat.ChatSystemMsg;
|
|
|
|
import engine.objects.*;
|
|
|
|
import engine.server.MBServerStatics;
|
|
|
|
import engine.util.ThreadUtils;
|
|
|
|
import engine.workthreads.DisconnectTrashTask;
|
|
|
|
import engine.workthreads.HourlyJobThread;
|
|
|
|
import engine.workthreads.PurgeOprhans;
|
|
|
|
import engine.workthreads.WarehousePushThread;
|
|
|
|
import org.pmw.tinylog.Configurator;
|
|
|
|
import org.pmw.tinylog.Level;
|
|
|
|
import org.pmw.tinylog.Logger;
|
|
|
|
import org.pmw.tinylog.labelers.TimestampLabeler;
|
|
|
|
import org.pmw.tinylog.policies.StartupPolicy;
|
|
|
|
import org.pmw.tinylog.writers.RollingFileWriter;
|
|
|
|
|
|
|
|
import java.io.*;
|
|
|
|
import java.net.DatagramSocket;
|
|
|
|
import java.net.InetAddress;
|
|
|
|
import java.net.URL;
|
|
|
|
import java.nio.file.Files;
|
|
|
|
import java.nio.file.Paths;
|
|
|
|
import java.time.Duration;
|
|
|
|
import java.time.LocalDateTime;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Timer;
|
|
|
|
|
|
|
|
import static engine.gameManager.SimulationManager.SERVERHEARTBEAT;
|
|
|
|
import static java.lang.System.exit;
|
|
|
|
|
|
|
|
public class WorldServer {
|
|
|
|
|
|
|
|
public static int worldMapID = Integer.parseInt(ConfigManager.MB_WORLD_MAPID.getValue());
|
|
|
|
public static int worldRealmMap = Integer.parseInt(ConfigManager.MB_WORLD_REALMMAP.getValue());
|
|
|
|
|
|
|
|
public static int worldUUID = 1; // Root object in database
|
|
|
|
public static Enum.AccountStatus worldAccessLevel = Enum.AccountStatus.valueOf(ConfigManager.MB_WORLD_ACCESS_LVL.getValue());
|
|
|
|
private static LocalDateTime bootTime = LocalDateTime.now();
|
|
|
|
public boolean isRunning = false;
|
|
|
|
|
|
|
|
// Member variable declaration
|
|
|
|
|
|
|
|
public WorldServer() {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
|
|
|
WorldServer worldServer;
|
|
|
|
|
|
|
|
// Configure TinyLogger
|
|
|
|
Configurator.defaultConfig()
|
|
|
|
.addWriter(new RollingFileWriter("logs/world/world.txt", 30, new TimestampLabeler(), new StartupPolicy()))
|
|
|
|
.level(Level.DEBUG)
|
|
|
|
.formatPattern("{level} {date:yyyy-MM-dd HH:mm:ss.SSS} [{thread}] {class}.{method}({line}) : {message}")
|
|
|
|
.writingThread("main", 2)
|
|
|
|
.activate();
|
|
|
|
|
|
|
|
if (ConfigManager.init() == false) {
|
|
|
|
Logger.error("ABORT! Missing config entry!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
worldServer = new WorldServer();
|
|
|
|
|
|
|
|
ConfigManager.serverType = Enum.ServerType.WORLDSERVER;
|
|
|
|
ConfigManager.worldServer = worldServer;
|
|
|
|
ConfigManager.handler = new ClientMessagePump(worldServer);
|
|
|
|
|
|
|
|
worldServer.init();
|
|
|
|
|
|
|
|
int retVal = worldServer.exec();
|
|
|
|
|
|
|
|
if (retVal != 0)
|
|
|
|
Logger.error(
|
|
|
|
".exec() returned value: '" + retVal);
|
|
|
|
exit(retVal);
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
Logger.error(e.getMessage());
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void trainerInfo(TrainerInfoMsg msg, ClientConnection origin) {
|
|
|
|
|
|
|
|
NPC npc = NPC.getFromCache(msg.getObjectID());
|
|
|
|
float sellPercent = 1;
|
|
|
|
|
|
|
|
if (npc != null) {
|
|
|
|
|
|
|
|
if (origin.getPlayerCharacter() != null)
|
|
|
|
sellPercent = npc.getSellPercent(origin.getPlayerCharacter());
|
|
|
|
else
|
|
|
|
sellPercent = npc.getSellPercent();
|
|
|
|
|
|
|
|
msg.setTrainPercent(sellPercent); //TrainMsg.getTrainPercent(npc));
|
|
|
|
}
|
|
|
|
|
|
|
|
Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), msg);
|
|
|
|
DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void refinerScreen(RefinerScreenMsg msg, ClientConnection origin)
|
|
|
|
throws MsgSendException {
|
|
|
|
|
|
|
|
NPC npc = NPC.getFromCache(msg.getNpcID());
|
|
|
|
|
|
|
|
if (npc != null)
|
|
|
|
msg.setUnknown02(0); //cost to refine?
|
|
|
|
|
|
|
|
Dispatch dispatch = Dispatch.borrow(origin.getPlayerCharacter(), msg);
|
|
|
|
DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static String getUptimeString() {
|
|
|
|
|
|
|
|
String outString = null;
|
|
|
|
java.time.Duration uptimeDuration;
|
|
|
|
String newLine = System.getProperty("line.separator");
|
|
|
|
|
|
|
|
try {
|
|
|
|
outString = "[LUA_UPTIME()]" + newLine;
|
|
|
|
uptimeDuration = java.time.Duration.between(LocalDateTime.now(), WorldServer.bootTime);
|
|
|
|
long uptimeSeconds = Math.abs(uptimeDuration.getSeconds());
|
|
|
|
String uptime = String.format("%d hours %02d minutes %02d seconds", uptimeSeconds / 3600, (uptimeSeconds % 3600) / 60, (uptimeSeconds % 60));
|
|
|
|
outString += "uptime: " + uptime;
|
|
|
|
outString += " pop: " + SessionManager.getActivePlayerCharacterCount() + " max pop: " + SessionManager._maxPopulation;
|
|
|
|
} catch (Exception e) {
|
|
|
|
Logger.error("Failed to build string");
|
|
|
|
}
|
|
|
|
return outString;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void writePopulationFile() {
|
|
|
|
|
|
|
|
int population = SessionManager.getActivePlayerCharacterCount();
|
|
|
|
try {
|
|
|
|
|
|
|
|
|
|
|
|
File populationFile = new File(ConfigManager.DEFAULT_DATA_DIR + ConfigManager.MB_WORLD_NAME.getValue().replaceAll("'", "") + ".pop");
|
|
|
|
FileWriter fileWriter;
|
|
|
|
|
|
|
|
try {
|
|
|
|
fileWriter = new FileWriter(populationFile, false);
|
|
|
|
fileWriter.write(Integer.toString(population));
|
|
|
|
fileWriter.close();
|
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
Logger.error(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private int exec() {
|
|
|
|
|
|
|
|
LocalDateTime nextHeartbeatTime = LocalDateTime.now();
|
|
|
|
LocalDateTime nextPopulationFileTime = LocalDateTime.now();
|
|
|
|
LocalDateTime nextFlashTrashCheckTime = LocalDateTime.now();
|
|
|
|
LocalDateTime nextHourlyJobTime = LocalDateTime.now().withMinute(0).withSecond(0).plusHours(1);
|
|
|
|
LocalDateTime nextWareHousePushTime = LocalDateTime.now();
|
|
|
|
|
|
|
|
// Begin execution of main game loop
|
|
|
|
|
|
|
|
this.isRunning = true;
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
|
|
if (LocalDateTime.now().isAfter(nextHeartbeatTime)) {
|
|
|
|
SERVERHEARTBEAT.tick();
|
|
|
|
nextHeartbeatTime = LocalDateTime.now().plusNanos(50000000);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (LocalDateTime.now().isAfter(nextPopulationFileTime)) {
|
|
|
|
writePopulationFile();
|
|
|
|
nextPopulationFileTime = LocalDateTime.now().plusMinutes(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (LocalDateTime.now().isAfter(nextFlashTrashCheckTime)) {
|
|
|
|
processFlashFile();
|
|
|
|
processTrashFile();
|
|
|
|
nextFlashTrashCheckTime = LocalDateTime.now().plusSeconds(15);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (LocalDateTime.now().isAfter(nextHourlyJobTime)) {
|
|
|
|
Thread hourlyJobThread = new Thread(new HourlyJobThread());
|
|
|
|
hourlyJobThread.setName("hourlyJob");
|
|
|
|
hourlyJobThread.start();
|
|
|
|
nextHourlyJobTime = LocalDateTime.now().withMinute(0).withSecond(0).plusHours(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (LocalDateTime.now().isAfter(nextWareHousePushTime)) {
|
|
|
|
Thread warehousePushThread = new Thread(new WarehousePushThread());
|
|
|
|
warehousePushThread.setName("warehousePush");
|
|
|
|
warehousePushThread.start();
|
|
|
|
nextWareHousePushTime = LocalDateTime.now().plusMinutes(15);
|
|
|
|
}
|
|
|
|
|
|
|
|
ThreadUtils.sleep(50);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void initClientConnectionManager() {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
String name = ConfigManager.MB_WORLD_NAME.getValue();
|
|
|
|
|
|
|
|
if (ConfigManager.MB_EXTERNAL_ADDR.getValue().equals("0.0.0.0")) {
|
|
|
|
|
|
|
|
// Autoconfigure External IP address. Only used in loginserver but useful
|
|
|
|
// here for bootstrap display
|
|
|
|
|
|
|
|
Logger.info("AUTOCONFIG EXTERNAL IP ADDRESS");
|
|
|
|
URL whatismyip = new URL("http://checkip.amazonaws.com");
|
|
|
|
|
|
|
|
BufferedReader in = new BufferedReader(new InputStreamReader(
|
|
|
|
whatismyip.openStream()));
|
|
|
|
ConfigManager.MB_EXTERNAL_ADDR.setValue(in.readLine());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ConfigManager.MB_BIND_ADDR.getValue().equals("0.0.0.0")) {
|
|
|
|
|
|
|
|
try (final DatagramSocket socket = new DatagramSocket()) {
|
|
|
|
socket.connect(InetAddress.getByName("8.8.8.8"), 10002);
|
|
|
|
ConfigManager.MB_BIND_ADDR.setValue(socket.getLocalAddress().getHostAddress());
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
Logger.info("External address: " + ConfigManager.MB_EXTERNAL_ADDR.getValue() + ":" + ConfigManager.MB_WORLD_PORT.getValue());
|
|
|
|
Logger.info("Internal address: " + ConfigManager.MB_BIND_ADDR.getValue() + ":" + ConfigManager.MB_WORLD_PORT.getValue());
|
|
|
|
|
|
|
|
InetAddress addy = InetAddress.getByName(ConfigManager.MB_BIND_ADDR.getValue());
|
|
|
|
int port = Integer.parseInt(ConfigManager.MB_WORLD_PORT.getValue());
|
|
|
|
|
|
|
|
ClientConnectionManager connectionManager = new ClientConnectionManager(name + ".ClientConnMan", addy,
|
|
|
|
port);
|
|
|
|
connectionManager.startup();
|
|
|
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
Logger.error("Exception while creating a ClientConnectionManager.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean init() {
|
|
|
|
|
|
|
|
Logger.info("MAGICBANE SERVER GREETING:");
|
|
|
|
Logger.info(ConfigManager.MB_WORLD_GREETING.getValue());
|
|
|
|
|
|
|
|
Logger.info("Initialize network protocol");
|
|
|
|
Protocol.initProtocolLookup();
|
|
|
|
|
|
|
|
Logger.info("Initialize database layer");
|
|
|
|
initDatabaselayer();
|
|
|
|
|
|
|
|
Logger.info("Starting network Dispatcher");
|
|
|
|
DispatchMessage.startMessagePump();
|
|
|
|
|
|
|
|
Logger.info("Setting cross server session behavior");
|
|
|
|
SessionManager.setCrossServerBehavior(1); // Sets cross server behavior
|
|
|
|
|
|
|
|
Logger.info("Starting Item Production thread");
|
|
|
|
ItemProductionManager.ITEMPRODUCTIONMANAGER.startMessagePump();
|
|
|
|
|
|
|
|
Logger.info("Initializing Errant Guild");
|
|
|
|
Guild.getErrantGuild();
|
|
|
|
|
|
|
|
Logger.info("Initializing PowersManager.");
|
|
|
|
PowersManager.initPowersManager(true);
|
|
|
|
|
|
|
|
Logger.info("Initializing granted Skills for Runes");
|
|
|
|
DbManager.SkillsBaseQueries.LOAD_ALL_RUNE_SKILLS();
|
|
|
|
|
|
|
|
Logger.info("Initializing Player Friends");
|
|
|
|
DbManager.PlayerCharacterQueries.LOAD_PLAYER_FRIENDS();
|
|
|
|
|
|
|
|
Logger.info("Initializing NPC Profits");
|
|
|
|
DbManager.NPCQueries.LOAD_NPC_PROFITS();
|
|
|
|
|
|
|
|
Logger.info("Initializing Petition Table");
|
|
|
|
DbManager.PetitionQueries.CREATE_PETITION_TABLE();
|
|
|
|
|
|
|
|
Logger.info("Initializing MeshBounds");
|
|
|
|
MeshBounds.InitializeBuildingBounds();
|
|
|
|
|
|
|
|
Logger.info("Loading ItemBases");
|
|
|
|
ItemBase.loadAllItemBases();
|
|
|
|
|
|
|
|
Logger.info("Loading PromotionClasses");
|
|
|
|
DbManager.PromotionQueries.GET_ALL_PROMOTIONS();
|
|
|
|
|
|
|
|
Logger.info("Loading NPC and Mob Rune Sets");
|
|
|
|
NPCManager.LoadAllRuneSets();
|
|
|
|
|
|
|
|
Logger.info("Loading Booty Sets");
|
|
|
|
LootManager._bootySetMap = DbManager.LootQueries.LOAD_BOOTY_TABLES();
|
|
|
|
|
|
|
|
// Load new loot system
|
|
|
|
Logger.info("Initializing Loot Manager");
|
|
|
|
LootManager.init();
|
|
|
|
|
|
|
|
RuneBaseAttribute.LoadAllAttributes();
|
|
|
|
RuneBase.LoadAllRuneBases();
|
|
|
|
BaseClass.LoadAllBaseClasses();
|
|
|
|
Race.loadAllRaces();
|
|
|
|
RuneBaseEffect.LoadRuneBaseEffects();
|
|
|
|
Logger.info("Loading MobBases.");
|
|
|
|
DbManager.MobBaseQueries.GET_ALL_MOBBASES();
|
|
|
|
|
|
|
|
Logger.info("Loading Mob Powers");
|
|
|
|
PowersManager.AllMobPowers = dbPowerHandler.LOAD_MOB_POWERS();
|
|
|
|
|
|
|
|
Logger.info("Loading item enchants");
|
|
|
|
DbManager.LootQueries.LOAD_ENCHANT_VALUES();
|
|
|
|
|
|
|
|
Logger.info("Loading zone extent cache");
|
|
|
|
DbManager.ZoneQueries.LOAD_ZONE_EXTENTS();
|
|
|
|
|
|
|
|
Logger.info("Loading Realms");
|
|
|
|
Realm.loadAllRealms();
|
|
|
|
|
|
|
|
Logger.info("Loading RealmMap");
|
|
|
|
RealmMap.loadRealmImageMap();
|
|
|
|
|
|
|
|
Logger.info("Loading Kits");
|
|
|
|
DbManager.KitQueries.GET_ALL_KITS();
|
|
|
|
|
|
|
|
Logger.info("Loading World Grid");
|
|
|
|
WorldGrid.InitializeGridObjects();
|
|
|
|
|
|
|
|
Logger.info("Starting InterestManager.");
|
|
|
|
WorldGrid.startLoadJob();
|
|
|
|
|
|
|
|
Logger.info("Loading blueprint data.");
|
|
|
|
StaticColliders.loadAllStaticColliders();
|
|
|
|
BuildingRegions.loadAllStaticColliders();
|
|
|
|
Blueprint.loadAllDoorNumbers();
|
|
|
|
Blueprint.loadAllBlueprints();
|
|
|
|
|
|
|
|
Logger.info("Initializing Heightmap data");
|
|
|
|
HeightMap.loadAlHeightMaps();
|
|
|
|
|
|
|
|
Logger.info("Loading Race data");
|
|
|
|
Enum.RaceType.initRaceTypeTables();
|
|
|
|
Race.loadAllRaces();
|
|
|
|
|
|
|
|
Logger.info("Loading building slot/stuck location data.");
|
|
|
|
BuildingLocation.loadBuildingLocations();
|
|
|
|
|
|
|
|
// Starting before loading of structures/guilds/characters
|
|
|
|
// so the database connections are available to write
|
|
|
|
// historical data.
|
|
|
|
|
|
|
|
Logger.info("Starting Data Warehouse");
|
|
|
|
DataWarehouse.bootStrap();
|
|
|
|
|
|
|
|
Logger.info("Loading Minion Bases.");
|
|
|
|
MinionType.InitializeMinions();
|
|
|
|
|
|
|
|
Logger.info("Loading Pirate Names.");
|
|
|
|
NPCManager.loadAllPirateNames();
|
|
|
|
|
|
|
|
Logger.info("Loading Support Types");
|
|
|
|
SupportMsgType.InitializeSupportMsgType();
|
|
|
|
|
|
|
|
//Load Buildings, Mobs and NPCs for server
|
|
|
|
|
|
|
|
getWorldBuildingsMobsNPCs();
|
|
|
|
|
|
|
|
// Configure realms for serialization
|
|
|
|
// Doing this after the world is loaded
|
|
|
|
|
|
|
|
Logger.info("Configuring realm serialization data");
|
|
|
|
Realm.configureAllRealms();
|
|
|
|
|
|
|
|
Logger.info("Loading Mine data.");
|
|
|
|
Mine.loadAllMines();
|
|
|
|
|
|
|
|
Logger.info("Loading Shrine data.");
|
|
|
|
DbManager.ShrineQueries.LOAD_ALL_SHRINES();
|
|
|
|
|
|
|
|
Logger.info("Initialize Resource type lookup");
|
|
|
|
Enum.ResourceType.InitializeResourceTypes();
|
|
|
|
|
|
|
|
Logger.info("Loading Warehouse data.");
|
|
|
|
DbManager.WarehouseQueries.LOAD_ALL_WAREHOUSES();
|
|
|
|
|
|
|
|
Logger.info("Loading Runegate data.");
|
|
|
|
Runegate.loadAllRunegates();
|
|
|
|
|
|
|
|
Logger.info("Loading Max Skills for Trainers");
|
|
|
|
DbManager.SkillsBaseQueries.LOAD_ALL_MAX_SKILLS_FOR_CONTRACT();
|
|
|
|
|
|
|
|
//pick a startup Hotzone
|
|
|
|
ZoneManager.generateAndSetRandomHotzone();
|
|
|
|
|
|
|
|
Logger.info("Loading All Players from database to Server Cache");
|
|
|
|
long start = System.currentTimeMillis();
|
|
|
|
|
|
|
|
try {
|
|
|
|
DbManager.PlayerCharacterQueries.GET_ALL_CHARACTERS();
|
|
|
|
} catch (Exception e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
|
|
|
|
long end = System.currentTimeMillis();
|
|
|
|
|
|
|
|
Logger.info("Loading All Players took " + (end - start) + " ms.");
|
|
|
|
|
|
|
|
ItemProductionManager.ITEMPRODUCTIONMANAGER.initialize();
|
|
|
|
|
|
|
|
Logger.info("Loading Player Heraldries");
|
|
|
|
DbManager.PlayerCharacterQueries.LOAD_HERALDY();
|
|
|
|
|
|
|
|
Logger.info("Running Heraldry Audit for Deleted Players");
|
|
|
|
Heraldry.AuditHeraldry();
|
|
|
|
|
|
|
|
//intiate mob ai thread
|
|
|
|
Logger.info("Starting Mob AI Thread");
|
|
|
|
MobAIThread.startAIThread();
|
|
|
|
|
|
|
|
for (Zone zone : ZoneManager.getAllZones()) {
|
|
|
|
if (zone.getHeightMap() != null) {
|
|
|
|
if (zone.getHeightMap().getBucketWidthX() == 0) {
|
|
|
|
System.out.println("Zone load num: " + zone.getLoadNum() + " has no bucket width");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Logger.info("World data loaded.");
|
|
|
|
|
|
|
|
//set default accesslevel for server *** Refactor who two separate variables?
|
|
|
|
MBServerStatics.accessLevel = worldAccessLevel;
|
|
|
|
Logger.info("Default access level set to " + MBServerStatics.accessLevel);
|
|
|
|
|
|
|
|
Logger.info("Initializing Network");
|
|
|
|
Network.init();
|
|
|
|
|
|
|
|
Logger.info("Initializing Client Connection Manager");
|
|
|
|
initClientConnectionManager();
|
|
|
|
|
|
|
|
//intiate mob respawn thread
|
|
|
|
Logger.info("Starting Mob Respawn Thread");
|
|
|
|
MobRespawnThread.startRespawnThread();
|
|
|
|
|
|
|
|
// Run maintenance
|
|
|
|
|
|
|
|
MaintenanceManager.dailyMaintenance();
|
|
|
|
|
|
|
|
Logger.info("Starting Orphan Item Purge");
|
|
|
|
PurgeOprhans.startPurgeThread();
|
|
|
|
|
|
|
|
// Open/Close mines for the current window
|
|
|
|
|
|
|
|
Logger.info("Processing mine window.");
|
|
|
|
HourlyJobThread.processMineWindow();
|
|
|
|
|
|
|
|
// Calculate bootstrap time and rest boot time to current time.
|
|
|
|
|
|
|
|
Duration bootDuration = Duration.between(LocalDateTime.now(), bootTime);
|
|
|
|
long bootSeconds = Math.abs(bootDuration.getSeconds());
|
|
|
|
String boottime = String.format("%d hours %02d minutes %02d seconds", bootSeconds / 3600, (bootSeconds % 3600) / 60, (bootSeconds % 60));
|
|
|
|
Logger.info("Bootstrap time was " + boottime);
|
|
|
|
|
|
|
|
bootTime = LocalDateTime.now();
|
|
|
|
|
|
|
|
Logger.info("Running garbage collection...");
|
|
|
|
System.gc();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected boolean initDatabaselayer() {
|
|
|
|
|
|
|
|
// Try starting a GOM <-> DB connection.
|
|
|
|
try {
|
|
|
|
|
|
|
|
Logger.info("Configuring GameObjectManager to use Database: '"
|
|
|
|
+ ConfigManager.MB_DATABASE_NAME.getValue() + "' on "
|
|
|
|
+ ConfigManager.MB_DATABASE_ADDRESS.getValue() + ':'
|
|
|
|
+ ConfigManager.MB_DATABASE_PORT.getValue());
|
|
|
|
|
|
|
|
DbManager.configureConnectionPool();
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
Logger.error(e.getMessage());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
PreparedStatementShared.submitPreparedStatementsCleaningJob();
|
|
|
|
|
|
|
|
if (MBServerStatics.DB_DEBUGGING_ON_BY_DEFAULT) {
|
|
|
|
PreparedStatementShared.enableDebugging();
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void getWorldBuildingsMobsNPCs() {
|
|
|
|
|
|
|
|
ArrayList<Zone> rootParent;
|
|
|
|
|
|
|
|
rootParent = DbManager.ZoneQueries.GET_MAP_NODES(worldUUID);
|
|
|
|
|
|
|
|
if (rootParent.isEmpty()) {
|
|
|
|
Logger.error("populateWorldBuildings: No entries found in worldMap for parent " + worldUUID);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Set sea floor object for server
|
|
|
|
|
|
|
|
Zone seaFloor = rootParent.get(0);
|
|
|
|
seaFloor.setParent(null);
|
|
|
|
ZoneManager.setSeaFloor(seaFloor);
|
|
|
|
|
|
|
|
rootParent.addAll(DbManager.ZoneQueries.GET_ALL_NODES(seaFloor));
|
|
|
|
|
|
|
|
long start = System.currentTimeMillis();
|
|
|
|
|
|
|
|
for (Zone zone : rootParent) {
|
|
|
|
|
|
|
|
ZoneManager.addZone(zone.getLoadNum(), zone);
|
|
|
|
zone.generateWorldAltitude();
|
|
|
|
|
|
|
|
|
|
|
|
//Handle Buildings
|
|
|
|
|
|
|
|
try {
|
|
|
|
ArrayList<Building> bList;
|
|
|
|
bList = DbManager.BuildingQueries.GET_ALL_BUILDINGS_FOR_ZONE(zone);
|
|
|
|
|
|
|
|
for (Building b : bList) {
|
|
|
|
|
|
|
|
b.setObjectTypeMask(MBServerStatics.MASK_BUILDING);
|
|
|
|
b.setLoc(b.getLoc());
|
|
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
|
|
Logger.error(e);
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
|
|
|
|
//Handle Mobs
|
|
|
|
|
|
|
|
try {
|
|
|
|
ArrayList<Mob> mobs;
|
|
|
|
mobs = DbManager.MobQueries.GET_ALL_MOBS_FOR_ZONE(zone);
|
|
|
|
|
|
|
|
for (Mob m : mobs) {
|
|
|
|
m.setObjectTypeMask(MBServerStatics.MASK_MOB | m.getTypeMasks());
|
|
|
|
m.setLoc(m.getLoc());
|
|
|
|
|
|
|
|
// Load Minions for Guard Captains here.
|
|
|
|
|
|
|
|
if (m.building != null && m.building.getBlueprint() != null && m.building.getBlueprint().getBuildingGroup() == Enum.BuildingGroup.BARRACK)
|
|
|
|
DbManager.MobQueries.LOAD_GUARD_MINIONS(m);
|
|
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
|
|
Logger.error(e);
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
|
|
|
|
//Handle NPCs
|
|
|
|
|
|
|
|
try {
|
|
|
|
ArrayList<NPC> npcs;
|
|
|
|
|
|
|
|
// Ignore NPCs on the seafloor (npc guild leaders, etc)
|
|
|
|
|
|
|
|
if (zone.equals(seaFloor))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
npcs = DbManager.NPCQueries.GET_ALL_NPCS_FOR_ZONE(zone);
|
|
|
|
|
|
|
|
for (NPC n : npcs) {
|
|
|
|
n.setObjectTypeMask(MBServerStatics.MASK_NPC);
|
|
|
|
n.setLoc(n.getLoc());
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
Logger.error(e);
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
//Handle cities
|
|
|
|
|
|
|
|
ZoneManager.loadCities(zone);
|
|
|
|
ZoneManager.populateWorldZones(zone);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
Logger.info("time to load World Objects: " + (System.currentTimeMillis() - start) + " ms");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Called to remove a client on "leave world", "quit game", killed client
|
|
|
|
* process, etc.
|
|
|
|
*/
|
|
|
|
|
|
|
|
public void removeClient(ClientConnection origin) {
|
|
|
|
|
|
|
|
if (origin == null) {
|
|
|
|
Logger.info(
|
|
|
|
"ClientConnection null in removeClient.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
PlayerCharacter playerCharacter = SessionManager.getPlayerCharacter(
|
|
|
|
origin);
|
|
|
|
|
|
|
|
if (playerCharacter == null)
|
|
|
|
// TODO log this
|
|
|
|
return;
|
|
|
|
|
|
|
|
//cancel any trade
|
|
|
|
if (playerCharacter.getCharItemManager() != null)
|
|
|
|
playerCharacter.getCharItemManager().endTrade(true);
|
|
|
|
|
|
|
|
// Release any mine claims
|
|
|
|
|
|
|
|
Mine.releaseMineClaims(playerCharacter);
|
|
|
|
|
|
|
|
// logout
|
|
|
|
long delta = MBServerStatics.LOGOUT_TIMER_MS;
|
|
|
|
|
|
|
|
if (System.currentTimeMillis() - playerCharacter.getTimeStamp("LastCombatPlayer") < 60000) {
|
|
|
|
delta = 60000;
|
|
|
|
|
|
|
|
}
|
|
|
|
playerCharacter.stopMovement(playerCharacter.getLoc());
|
|
|
|
UpdateStateMsg updateStateMsg = new UpdateStateMsg();
|
|
|
|
updateStateMsg.setPlayer(playerCharacter);
|
|
|
|
|
|
|
|
updateStateMsg.setActivity(5);
|
|
|
|
DispatchMessage.dispatchMsgToInterestArea(playerCharacter, updateStateMsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);
|
|
|
|
|
|
|
|
if (playerCharacter.region != null)
|
|
|
|
if (PlayerCharacter.CanBindToBuilding(playerCharacter, playerCharacter.region.parentBuildingID))
|
|
|
|
playerCharacter.bindBuilding = playerCharacter.region.parentBuildingID;
|
|
|
|
else
|
|
|
|
playerCharacter.bindBuilding = 0;
|
|
|
|
|
|
|
|
playerCharacter.getLoadedObjects().clear();
|
|
|
|
playerCharacter.getLoadedStaticObjects().clear();
|
|
|
|
|
|
|
|
LogoutCharacterJob logoutJob = new LogoutCharacterJob(playerCharacter, this);
|
|
|
|
JobContainer jc = JobScheduler.getInstance().scheduleJob(logoutJob,
|
|
|
|
System.currentTimeMillis() + delta);
|
|
|
|
playerCharacter.getTimers().put("Logout", jc);
|
|
|
|
playerCharacter.getTimestamps().put("logout", System.currentTimeMillis());
|
|
|
|
|
|
|
|
//send update to friends that you are logged off.
|
|
|
|
|
|
|
|
PlayerFriends.SendFriendsStatus(playerCharacter, false);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public void logoutCharacter(PlayerCharacter player) {
|
|
|
|
|
|
|
|
if (player == null) {
|
|
|
|
Logger.error("Unable to find PlayerCharacter to logout");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
//remove player from loaded mobs agro maps
|
|
|
|
for (AbstractWorldObject awo : WorldGrid.getObjectsInRangePartial(player.getLoc(), MBServerStatics.CHARACTER_LOAD_RANGE, MBServerStatics.MASK_MOB)) {
|
|
|
|
Mob loadedMob = (Mob) awo;
|
|
|
|
loadedMob.playerAgroMap.remove(player.getObjectUUID());
|
|
|
|
}
|
|
|
|
player.getTimestamps().put("logout", System.currentTimeMillis());
|
|
|
|
player.setEnteredWorld(false);
|
|
|
|
|
|
|
|
// remove from simulation and zero current loc
|
|
|
|
|
|
|
|
WorldGrid.RemoveWorldObject(player);
|
|
|
|
|
|
|
|
// clear Logout Timer
|
|
|
|
|
|
|
|
if (player.getTimers() != null)
|
|
|
|
player.getTimers().remove("Logout");
|
|
|
|
|
|
|
|
if (player.getPet() != null)
|
|
|
|
player.getPet().dismiss();
|
|
|
|
|
|
|
|
NPCManager.dismissNecroPets(player);
|
|
|
|
|
|
|
|
// Set player inactive so they quit loading for other players
|
|
|
|
|
|
|
|
player.setActive(false);
|
|
|
|
|
|
|
|
// Remove from group
|
|
|
|
|
|
|
|
Group group = GroupManager.getGroup(player);
|
|
|
|
|
|
|
|
try {
|
|
|
|
if (group != null)
|
|
|
|
GroupManager.LeaveGroup(player);
|
|
|
|
} catch (MsgSendException e) {
|
|
|
|
Logger.error(e.toString());
|
|
|
|
}
|
|
|
|
|
|
|
|
player.respawnLock.writeLock().lock();
|
|
|
|
try {
|
|
|
|
if (!player.isAlive())
|
|
|
|
player.respawn(false, false, true);
|
|
|
|
} catch (Exception e) {
|
|
|
|
Logger.error(e);
|
|
|
|
} finally {
|
|
|
|
player.respawnLock.writeLock().unlock();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void processTrashFile() {
|
|
|
|
|
|
|
|
ArrayList<String> machineList;
|
|
|
|
ArrayList<PlayerCharacter> trashList = new ArrayList<>();
|
|
|
|
ArrayList<Integer> accountList = new ArrayList<>();
|
|
|
|
|
|
|
|
File trashFile = new File("trash");
|
|
|
|
|
|
|
|
if (trashFile.exists() == false)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Build list of machineID's in the trash file
|
|
|
|
|
|
|
|
machineList = DbManager.AccountQueries.GET_TRASH_LIST();
|
|
|
|
|
|
|
|
// Build list of trash characters associated with that machineID
|
|
|
|
|
|
|
|
for (String machineID : machineList) {
|
|
|
|
trashList = DbManager.AccountQueries.GET_ALL_CHARS_FOR_MACHINE(machineID);
|
|
|
|
|
|
|
|
|
|
|
|
// Deactivate these players and add them to loginCache table
|
|
|
|
|
|
|
|
for (PlayerCharacter trashPlayer : trashList) {
|
|
|
|
|
|
|
|
if (trashPlayer == null)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Need to collate accounts.
|
|
|
|
|
|
|
|
if (!accountList.contains(trashPlayer.getAccount().getObjectUUID()))
|
|
|
|
accountList.add(trashPlayer.getAccount().getObjectUUID());
|
|
|
|
|
|
|
|
DbManager.PlayerCharacterQueries.SET_ACTIVE(trashPlayer, false);
|
|
|
|
DbManager.AccountQueries.INVALIDATE_LOGIN_CACHE(trashPlayer.getObjectUUID(), "character");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// delete vault of associated accounts and then invalidate them
|
|
|
|
// in the login cache.
|
|
|
|
|
|
|
|
for (Integer accountID : accountList) {
|
|
|
|
DbManager.AccountQueries.DELETE_VAULT_FOR_ACCOUNT(accountID);
|
|
|
|
DbManager.AccountQueries.INVALIDATE_LOGIN_CACHE(accountID, "account");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Trigger the Login Server to invalidate these accounts in the cache..
|
|
|
|
|
|
|
|
try {
|
|
|
|
Files.write(Paths.get("cacheInvalid"), "".getBytes());
|
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
|
|
|
|
// If any of these players are active disconnect them.
|
|
|
|
// The account and player should be removed from the login
|
|
|
|
// server cache file by now.
|
|
|
|
|
|
|
|
Timer timer = new Timer("Disconnect Trash");
|
|
|
|
timer.schedule(new DisconnectTrashTask(trashList), 3000L);
|
|
|
|
|
|
|
|
// Clean up after ourselves
|
|
|
|
|
|
|
|
try {
|
|
|
|
Files.deleteIfExists(Paths.get("trash"));
|
|
|
|
DbManager.AccountQueries.CLEAR_TRASH_TABLE();
|
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
private void processFlashFile() {
|
|
|
|
|
|
|
|
File flashFile = new File("flash");
|
|
|
|
String flashString;
|
|
|
|
List<String> fileContents;
|
|
|
|
|
|
|
|
if (flashFile.exists() == false)
|
|
|
|
return;
|
|
|
|
|
|
|
|
try {
|
|
|
|
fileContents = Files.readAllLines(Paths.get("flash"));
|
|
|
|
} catch (IOException e) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Flash file detected: read contents
|
|
|
|
// and send as a flash.
|
|
|
|
|
|
|
|
flashString = fileContents.toString();
|
|
|
|
|
|
|
|
if (flashString == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (flashString == "")
|
|
|
|
flashString = "Rebooting for to fix bug.";
|
|
|
|
|
|
|
|
Logger.info("Sending flash from external interface");
|
|
|
|
Logger.info("Msg: " + flashString);
|
|
|
|
|
|
|
|
ChatSystemMsg msg = new ChatSystemMsg(null, flashString);
|
|
|
|
msg.setChannel(engine.Enum.ChatChannelType.FLASH.getChannelID());
|
|
|
|
msg.setMessageType(Enum.ChatMessageType.INFO.ordinal());
|
|
|
|
DispatchMessage.dispatchMsgToAll(msg);
|
|
|
|
|
|
|
|
// Delete file
|
|
|
|
|
|
|
|
try {
|
|
|
|
Files.deleteIfExists(Paths.get("flash"));
|
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|