forked from MagicBane/Server
Initial Repository Push
This commit is contained in:
@@ -0,0 +1,830 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2022
|
||||
// www.magicbane.com
|
||||
|
||||
|
||||
package engine.server;
|
||||
|
||||
import engine.Enum;
|
||||
import engine.gameManager.ConfigManager;
|
||||
import engine.math.Vector3fImmutable;
|
||||
|
||||
public class MBServerStatics {
|
||||
|
||||
public static final int revisionNumber = 1;
|
||||
|
||||
public static String getEmulatorVersion() {
|
||||
return Integer.toString(revisionNumber);
|
||||
}
|
||||
|
||||
public static final String CMDLINE_ARGS_EXE_NAME_DELIMITER = "-name";
|
||||
public static final String CMDLINE_ARGS_CONFIG_FILE_PATH_DELIMITER = "-config";
|
||||
public static final String CMDLINE_ARGS_CALLER_DELIMITER = "-caller";
|
||||
public static final String CMDLINE_ARGS_REASON_DELIMITER = "-reason";
|
||||
public static final String EXISTING_CONNECTION_CLOSED = "An existing connection was forcibly closed by the remote host";
|
||||
public static final String RESET_BY_PEER = "Connection reset by peer";
|
||||
/*
|
||||
* ####Debugging Flags####
|
||||
*/
|
||||
public static final boolean POWERS_DEBUG = false;
|
||||
public static final boolean MOVEMENT_SYNC_DEBUG = false;
|
||||
public static final boolean BONUS_TRAINS_ENABLED = false;
|
||||
public static final boolean REGENS_DEBUG = false;
|
||||
public static final boolean SHOW_SAFE_MODE_CHANGE = false;
|
||||
public static final boolean COMBAT_TARGET_HITBOX_DEBUG = false; // output
|
||||
// hit box
|
||||
// calcs
|
||||
public static final boolean PRINT_INCOMING_OPCODES = false; // print
|
||||
// incoming
|
||||
// opcodes to
|
||||
// console
|
||||
|
||||
public static final int BANK_GOLD_LIMIT = 25000000;
|
||||
public static final int PLAYER_GOLD_LIMIT = 10000000;
|
||||
public static final int BUILDING_GOLD_LIMIT = 15000000;
|
||||
|
||||
public static final String VENDOR_FULL = "This vendor has no more gold to give.";
|
||||
public static final boolean HEIGHTMAP_DEBUG = false;
|
||||
public static final boolean FAST_LOAD = false; // skip loading mobs,
|
||||
// buildings, npcs
|
||||
public static final boolean FAST_LOAD_INIT = false; // skip loading mobs,
|
||||
// buildings, npcs
|
||||
/*
|
||||
* Login cache flags
|
||||
*/
|
||||
public static final boolean SKIP_CACHE_LOGIN = false; // skip caching // login server
|
||||
public static final boolean SKIP_CACHE_LOGIN_PLAYER = false; // skip caching // on login
|
||||
public static final boolean SKIP_CACHE_LOGIN_ITEM = false; // skip caching
|
||||
|
||||
/*
|
||||
* Logger
|
||||
*/
|
||||
public static final int bannerWidth = 80;
|
||||
public static final int typeWidth = 10;
|
||||
public static final int originWidth = 25;
|
||||
public static final int logWidth = 80;
|
||||
|
||||
/*
|
||||
* ConfigSystem related
|
||||
*/
|
||||
public static final String DEFAULT_CONFIG_DIR = "mb.conf/";
|
||||
public static final String DEFAULT_DATA_DIR = "mb.data/";
|
||||
/*
|
||||
* ChatManager related
|
||||
*/
|
||||
public static final int SHOUT_PERCEPTION_RADIUS_MOD = 2;
|
||||
/*
|
||||
* DevCmd related
|
||||
*/
|
||||
public static final String DEV_CMD_PREFIX = "./";
|
||||
|
||||
/*
|
||||
* JobManager related
|
||||
*/
|
||||
|
||||
// The number of elements in INITIAL_WORKERS defines the initial number of
|
||||
// job pools
|
||||
public static final int[] INITIAL_JOBPOOL_WORKERS = { 4, 2, 1 };
|
||||
public static final int DEFAULT_JOBPOOL_WORKERS = 1;
|
||||
public static final int DEFAULT_LOGIN_JOBPOOL_WORKERS = 5;
|
||||
|
||||
public static final int JOBWORKER_IDLE_TIMEOUT_MS = 750;
|
||||
public static final int JOBMANAGER_INTERNAL_MONITORING_INTERVAL_MS = 1000;
|
||||
public static final int JOB_STALL_THRESHOLD_MS = 120 * 1000;
|
||||
public static final int MAX_JOB_HISTORY_OBJECTS = 1000; // max number of
|
||||
// historic jobs to
|
||||
// store on queue
|
||||
// after execution
|
||||
public static final int JOBSTATISTICS_WAKE_INTERVAL_MS = 500; // wake up and
|
||||
// gather
|
||||
// job stats
|
||||
// every X
|
||||
// ms,
|
||||
// decrease
|
||||
// this is
|
||||
// we blow
|
||||
// the job
|
||||
// history
|
||||
// queue
|
||||
|
||||
public static final int SCHEDULER_INITIAL_CAPACITY = 1000;
|
||||
public static final int SCHEDULER_EXECUTION_TIME_COMPENSATION = 16;
|
||||
|
||||
/*
|
||||
* Concurrent Hash Map - Defaults
|
||||
*/
|
||||
|
||||
public static final int CHM_INIT_CAP = 10;
|
||||
public static final float CHM_LOAD = 0.75f;
|
||||
public static final int CHM_THREAD_HIGH = 4;
|
||||
public static final int CHM_THREAD_MED = 2;
|
||||
public static final int CHM_THREAD_LOW = 1;
|
||||
|
||||
/*
|
||||
* LoginServer related
|
||||
*/
|
||||
|
||||
public static final String PCMajorVer = "1.2.25.5";
|
||||
public static final String PCMinorVer = "5.25.5";
|
||||
|
||||
public static final String MACMajorVer = "1.2.24.3";
|
||||
public static final String MACMinorVer = "5.24.3";
|
||||
|
||||
/*
|
||||
* LoginErrorMsg related
|
||||
*/
|
||||
public static final int LOGINERROR_INVALID_USERNAME_PASSWORD = 1;
|
||||
public static final int LOGINERROR_ACCOUNT_SUSPENDED = 2;
|
||||
|
||||
/*
|
||||
* Message is Version:
|
||||
*/
|
||||
public static final int LOGINERROR_INCORRECT_CLIENT_VERSION = 3;
|
||||
public static final int LOGINERROR_NOT_ALLOWED_TO_LOGIN_YET = 4;
|
||||
|
||||
/*
|
||||
* Message is 'Error ='
|
||||
*/
|
||||
public static final int LOGINERROR_LOGINSERVER_IS_UNAVAILABLE = 5;
|
||||
public static final int LOGINERROR_INVALID_ADMIN_USERNAME_PASSWORD = 6;
|
||||
public static final int LOGINERROR_NO_MORE_PLAYTIME_ON_ACCOUNT = 7;
|
||||
public static final int LOGINERROR_ACCOUNT_DOESNT_HAVE_SUBSCRIPTION = 8;
|
||||
public static final int LOGINERROR_ACCOUNT_INSECURE_CHANGE_PASSWORD = 9;
|
||||
public static final int LOGINERROR_TOO_MANY_LOGIN_TRIES = 10;
|
||||
|
||||
/*
|
||||
* Message is 'Error ='
|
||||
*/
|
||||
public static final int LOGINERROR_NOMOREPLAYTIME = 7;
|
||||
public static final int LOGINERROR_INACTIVE = 8;
|
||||
public static final int LOGINERROR_UNABLE_TO_LOGIN = 11;
|
||||
public static final int LOGINERROR_LOGINSERVER_BUSY = 12;
|
||||
public static final int LOGINERROR_BLANK = 13;
|
||||
|
||||
/*
|
||||
* >13 = 'blank' 12 = 'Login Server is currently busy, please try again in a
|
||||
* few minutes.' 11 = 'Unable to login. Please try again. If this problem
|
||||
* persists, contact customer support (error = )' 10 = 'You have made too
|
||||
* many unsuccessful login attempts, you must wait 15 minutes.' 9 = 'Your
|
||||
* Account is insecure, you must change your password before logging in
|
||||
* again.' 8 = 'This Account does not have an active Shadowbane
|
||||
* Subscription' 7 = 'No More PlayTime on this account 6 = 'Invalid
|
||||
* Administrator Username/Password' 5 = 'Login Server Is Unavailable (Error
|
||||
* = 0)' 4 = 'YouAreNotAllowedToLoginYet' 3 = 'Incorrect ClientVersion,
|
||||
* latest is' 2 = 'This Account Has Been Suspended' 1 = 'Invalid
|
||||
* Username/Password'
|
||||
*/
|
||||
|
||||
/*
|
||||
* Name Validation Related
|
||||
*/
|
||||
public static final int INVALIDNAME_FIRSTNAME_MUST_BE_LONGER = 1;
|
||||
public static final int INVALIDNAME_FIRSTANDLAST_MUST_BE_SHORTER = 2;
|
||||
public static final int INVALIDNAME_FIRSTNAME_MUST_NOT_HAVE_SPACES = 3;
|
||||
public static final int INVALIDNAME_FIRSTNAME_INVALID_CHARACTERS = 4;
|
||||
public static final int INVALIDNAME_PLEASE_CHOOSE_ANOTHER_FIRSTNAME = 5;
|
||||
public static final int INVALIDNAME_PLEASE_CHOOSE_ANOTHER_LASTNAME = 7;
|
||||
public static final int INVALIDNAME_LASTNAME_UNAVAILABLE = 8;
|
||||
public static final int INVALIDNAME_FIRSTNAME_UNAVAILABLE = 9;
|
||||
public static final int INVALIDNAME_WRONG_WORLD_ID = 10;
|
||||
public static final int INVALIDNAME_GENERIC = 11;
|
||||
|
||||
/*
|
||||
* 1: A first name of at least 3 character(s) must be entered 2: Your first
|
||||
* and last name cannot be more than 15 characters each. 3: Your first name
|
||||
* may not contain spaces 4: There are invalid characters in the first name.
|
||||
* 5: Please choose another first name 7: Please choose another last name 8:
|
||||
* That last name is unavailable 9: That first name is unavailable 10: Your
|
||||
* client sent an invalid world id 11: Invalid name. Choose another
|
||||
*/
|
||||
public static final int MIN_NAME_LENGTH = 3;
|
||||
public static final int MAX_NAME_LENGTH = 15;
|
||||
|
||||
/*
|
||||
* ClientConnection related
|
||||
*/
|
||||
public static final boolean TCP_NO_DELAY_DEFAULT = true;
|
||||
public static final byte MAX_CRYPTO_INIT_TRIES = 10;
|
||||
|
||||
|
||||
/*
|
||||
* EmuConnectionManager related
|
||||
*/
|
||||
public static final long delayBetweenConnectionChecks = 5000L; // in ms
|
||||
public static final long delayBetweenReconnectAttempts = 2000L; // in ms
|
||||
public static final int maxReconnectAttempts = 20;
|
||||
public static final long reconnectTimeout = 15000L;
|
||||
public static boolean DEBUG_PROTOCOL = false;
|
||||
/*
|
||||
* Account Related
|
||||
*/
|
||||
|
||||
public static final byte MAX_LOGIN_ATTEMPTS = 5;
|
||||
public static final int RESET_LOGIN_ATTEMPTS_AFTER = (15 * 60 * 1000); // in
|
||||
// ms
|
||||
public static final int MAX_ACTIVE_GAME_ACCOUNTS_PER_DISCORD_ACCOUNT = 4; // 0
|
||||
// to
|
||||
// disable
|
||||
/*
|
||||
* Character related
|
||||
*/
|
||||
|
||||
public static final byte MAX_NUM_OF_CHARACTERS = 7;
|
||||
|
||||
public static final int STAT_STR_ID = 0x8AC3C0E6;
|
||||
public static final int STAT_SPI_ID = 0xACB82E33;
|
||||
public static final int STAT_CON_ID = 0xB15DC77E;
|
||||
public static final int STAT_DEX_ID = 0xE07B3336;
|
||||
public static final int STAT_INT_ID = 0xFF665EC3;
|
||||
|
||||
/*
|
||||
* Skill attributeIDs
|
||||
*/
|
||||
|
||||
public static final int SKILL_RUNNING = 5;
|
||||
|
||||
/*
|
||||
* EquipSlot
|
||||
*/
|
||||
|
||||
public static final int SLOT_UNEQUIPPED = 0;
|
||||
public static final int SLOT_MAINHAND = 1;
|
||||
public static final int SLOT_OFFHAND = 2;
|
||||
public static final int SLOT_HELMET = 3;
|
||||
public static final int SLOT_CHEST = 4;
|
||||
public static final int SLOT_ARMS = 5;
|
||||
public static final int SLOT_GLOVES = 6;
|
||||
public static final int SLOT_RING1 = 7;
|
||||
public static final int SLOT_RING2 = 8;
|
||||
public static final int SLOT_NECKLACE = 9;
|
||||
public static final int SLOT_LEGGINGS = 10;
|
||||
public static final int SLOT_FEET = 11;
|
||||
public static final int SLOT_HAIRSTYLE = 18; // 17 & 18? Weird.
|
||||
public static final int SLOT_BEARDSTYLE = 17; // 17 & 18? Weird.
|
||||
|
||||
// Equip[0] = Slot1 = Weapon MainHand
|
||||
// Equip[1] = Slot2 = OffHand
|
||||
// Equip[2] = Slot3 = Helmet
|
||||
// Equip[3] = Slot4 = Chest
|
||||
// Equip[4] = Slot5 = Arms
|
||||
// Equip[5] = Slot6 = Gloves
|
||||
// Equip[6] = Slot7 = Ring1
|
||||
// Equip[7] = Slot8 = Ring2
|
||||
// Equip[8] = Slot9 = Necklace
|
||||
// Equip[9] = Slot10 = Leggings
|
||||
// Equip[10] = Slot11 = Feet
|
||||
// Equip[11] = Slot17 = HairStyle
|
||||
// Equip[12] = Slot18 = BeardStyle
|
||||
|
||||
/*
|
||||
* Group Formation Names
|
||||
*/
|
||||
public static final String[] FORMATION_NAMES = { "Column", "Line", "Box",
|
||||
"Triangle", "Circle", "Ranks", "Wedge", "Inverse Wedge", "T" };
|
||||
|
||||
/*
|
||||
* Runes
|
||||
*/
|
||||
|
||||
public static final int RUNETYPE_TRAIT = 1;
|
||||
|
||||
public static final int RUNE_COST_ATTRIBUTE_ID = 0;
|
||||
|
||||
public static final int RUNE_STR_ATTRIBUTE_ID = 1;
|
||||
public static final int RUNE_DEX_ATTRIBUTE_ID = 2;
|
||||
public static final int RUNE_CON_ATTRIBUTE_ID = 3;
|
||||
public static final int RUNE_INT_ATTRIBUTE_ID = 4;
|
||||
public static final int RUNE_SPI_ATTRIBUTE_ID = 5;
|
||||
|
||||
public static final int RUNE_STR_MAX_ATTRIBUTE_ID = 6;
|
||||
public static final int RUNE_DEX_MAX_ATTRIBUTE_ID = 7;
|
||||
public static final int RUNE_CON_MAX_ATTRIBUTE_ID = 8;
|
||||
public static final int RUNE_INT_MAX_ATTRIBUTE_ID = 9;
|
||||
public static final int RUNE_SPI_MAX_ATTRIBUTE_ID = 10;
|
||||
|
||||
public static final int RUNE_STR_MIN_NEEDED_ATTRIBUTE_ID = 11;
|
||||
public static final int RUNE_DEX_MIN_NEEDED_ATTRIBUTE_ID = 12;
|
||||
public static final int RUNE_CON_MIN_NEEDED_ATTRIBUTE_ID = 13;
|
||||
public static final int RUNE_INT_MIN_NEEDED_ATTRIBUTE_ID = 14;
|
||||
public static final int RUNE_SPI_MIN_NEEDED_ATTRIBUTE_ID = 15;
|
||||
|
||||
/*
|
||||
* DBMan
|
||||
*/
|
||||
public static final int NO_DB_ROW_ASSIGNED_YET = Integer.MAX_VALUE;
|
||||
|
||||
/*
|
||||
* PreparedStatement query debugging
|
||||
*/
|
||||
public static final boolean DB_DEBUGGING_ON_BY_DEFAULT = false; // warning:
|
||||
// not
|
||||
// recommended
|
||||
// for a
|
||||
// live
|
||||
// production
|
||||
// server
|
||||
public static final boolean ENABLE_QUERY_TIME_WARNING = true;
|
||||
public static final boolean ENABLE_UPDATE_TIME_WARNING = true;
|
||||
public static final boolean ENABLE_EXECUTION_TIME_WARNING = true;
|
||||
|
||||
/*
|
||||
* ClientEncryption
|
||||
*/
|
||||
public static final int AUTHENTICATION_WAIT_TIMEOUT = 1000 * 2; // seconds
|
||||
public static final int MaxGetKeyFromClientTries = 4;
|
||||
public static final int MaxProtocolMessagesPerSecond = 20; // 60 per second
|
||||
|
||||
/*
|
||||
* Guild Colors
|
||||
*/
|
||||
|
||||
// public static final int GUILD_COLOR_LIGHTGREEN = 0;
|
||||
// public static final int GUILD_COLOR_GREEN = 1;
|
||||
// public static final int GUILD_COLOR_DARKGREEN = 2;
|
||||
// public static final int GUILD_COLOR_LIGHTBLUE = 3;
|
||||
// public static final int GUILD_COLOR_BLUE = 4;
|
||||
// public static final int GUILD_COLOR_DARKBLUE = 5;
|
||||
// public static final int GUILD_COLOR_PURPLE = 6;
|
||||
// public static final int GUILD_COLOR_DARKRED = 7;
|
||||
// public static final int GUILD_COLOR_LIGHTRED = 8;
|
||||
// public static final int GUILD_COLOR_ORANGE = 9;
|
||||
// public static final int GUILD_COLOR_BROWNORANGE = 10;
|
||||
// public static final int GUILD_COLOR_BROWN = 11;
|
||||
// public static final int GUILD_COLOR_BROWNYELLOW = 12;
|
||||
// public static final int GUILD_COLOR_YELLOW = 13;
|
||||
// public static final int GUILD_COLOR_LIGHTGREY = 14;
|
||||
// public static final int GUILD_COLOR_GREY = 15;
|
||||
// public static final int GUILD_COLOR_DARKGREY = 16;
|
||||
// public static final int GUILD_COLOR_BLACK = 17;
|
||||
// public static final int GUILD_COLOR_BLUEGREEN = 18;
|
||||
// public static final int GUILD_COLOR_WHITE = 19;
|
||||
|
||||
/*
|
||||
* Timeout Related
|
||||
*/
|
||||
public static final int AFK_TIMEOUT_MS = (30 * 60 * 1000) * 100; // Added
|
||||
// *100
|
||||
// to
|
||||
// discount
|
||||
// it as
|
||||
// a
|
||||
// "random DC reason"
|
||||
public static final int KEEPALIVE_TIMEOUT_MS = (2 * 60 * 1000)
|
||||
+ (15 * 1000);
|
||||
public static final int TIMEOUT_CHECKS_TIMER_MS = (60 * 1000);
|
||||
|
||||
/*
|
||||
* Masks for Quad Tree. Masks should be multiple of 2.
|
||||
*/
|
||||
|
||||
public static final int MASK_PLAYER = 1;
|
||||
public static final int MASK_MOB = 2;
|
||||
public static final int MASK_PET = 4;
|
||||
public static final int MASK_CORPSE = 8;
|
||||
public static final int MASK_BUILDING = 16;
|
||||
public static final int MASK_UNDEAD = 64;
|
||||
public static final int MASK_BEAST = 128;
|
||||
public static final int MASK_HUMANOID = 256;
|
||||
public static final int MASK_NPC = 512;
|
||||
public static final int MASK_IAGENT = 2048;
|
||||
|
||||
public static final int MASK_DRAGON = 4096;
|
||||
public static final int MASK_RAT = 8192;
|
||||
public static final int MASK_SIEGE = 16384;
|
||||
public static final int MASK_CITY = 32768;
|
||||
public static final int MASK_ZONE = 65536;
|
||||
|
||||
/*
|
||||
* Combined QT Masks. For convenience
|
||||
*/
|
||||
|
||||
public static final int MASK_AGGRO = 5; // Player, Pet
|
||||
public static final int MASK_MOBILE = 7; // Player, Mob, Pet
|
||||
public static final int MASK_STATIC = 568; // Corpse, Building, Trigger, NPC
|
||||
|
||||
/*
|
||||
* World Coordinate Data
|
||||
*/
|
||||
public static final double MAX_WORLD_HEIGHT = -98304.0;
|
||||
public static final double MAX_WORLD_WIDTH = 131072.0;
|
||||
public static final float SEA_FLOOR_ALTITUDE = -1000f;
|
||||
public static int SPATIAL_HASH_BUCKETSX = 16384;
|
||||
public static int SPATIAL_HASH_BUCKETSY = 12288;
|
||||
public static float MAX_PLAYER_X_LOC = 129999;
|
||||
public static float MAX_PLAYER_Y_LOC = -97000;
|
||||
public static String NO_DELETE_COMBAT = "Can't delete items when in Combat with another player.";
|
||||
|
||||
/*
|
||||
* Rates
|
||||
*/
|
||||
|
||||
public static float EXP_RATE_MOD = 2f; // Probably don't want to declare
|
||||
// as final.
|
||||
public static float GOLD_RATE_MOD = 1.0f; // Probably don't want to declare
|
||||
// as final.
|
||||
public static float DROP_RATE_MOD = 1.0f; // Probably don't want to declare
|
||||
// as final.
|
||||
|
||||
// Hotzones
|
||||
public static float HOT_EXP_RATE_MOD = 2.0f; // Probably don't want to
|
||||
// declare as final.
|
||||
public static float HOT_GOLD_RATE_MOD = 1.5f; // Probably don't want to
|
||||
// declare as final.
|
||||
public static float HOT_DROP_RATE_MOD = 1.8f; // Probably don't want to
|
||||
// declare as final.
|
||||
|
||||
/*
|
||||
* Ranges
|
||||
*/
|
||||
public static final int CHARACTER_LOAD_RANGE = 400; // load range of mobile objects
|
||||
// (default: 300)
|
||||
public static final int STRUCTURE_LOAD_RANGE = 700; // load range of
|
||||
// (default: 600)
|
||||
|
||||
public static float LOOT_RANGE = 100;
|
||||
public static final int EXP_RANGE = 400;
|
||||
public static final int GOLD_SPLIT_RANGE = 600;
|
||||
// non-moving objects
|
||||
public static final int SAY_RANGE = 200;
|
||||
public static final int SHOUT_RANGE = 300;
|
||||
public static final int STATIC_THRESHOLD = 75; // Range must travel before
|
||||
// reloading statics
|
||||
public static final int FORMATION_RANGE = 75; // Max Distance a player can
|
||||
// be from group lead on
|
||||
// formation move
|
||||
public static final int OPENCLOSEDOORDISTANCE = 128; // Max distance a
|
||||
public static final int DOOR_CLOSE_TIMER = 30000; // 30 seconds
|
||||
// player can be from a door in order to toggle its state
|
||||
public static final int TRADE_RANGE = 10; // Max distance a player can be
|
||||
// from another player to trade
|
||||
public static final int NPC_TALK_RANGE = 20; // Range player can be to talk
|
||||
// to npc
|
||||
public static final int MAX_TELEPORT_RANGE = 1020; // Max range teleports
|
||||
// will work at
|
||||
public static final int RANGED_WEAPON_RANGE = 35; // any weapon attack
|
||||
// range beyond this
|
||||
// is ranged.
|
||||
public static final int CALL_FOR_HELP_RADIUS = 100; // Range mobs will
|
||||
// respond to calls
|
||||
// for help
|
||||
|
||||
public static final int TREE_TELEPORT_RADIUS = 30;
|
||||
|
||||
public static float MOB_SPEED_WALK = 6.5f;
|
||||
|
||||
public static float MOB_SPEED_WALKCOMBAT = 4.4f;
|
||||
|
||||
public static float MOB_SPEED_RUN = 14.67f;
|
||||
|
||||
public static float MOB_SPEED_RUNCOMBAT = 14.67f;
|
||||
|
||||
|
||||
/*
|
||||
* Noob Island Start Location for new players
|
||||
*/
|
||||
|
||||
public static final int[] DEFAULTGRID = {-1,1};
|
||||
public static final float startX = 19128;// 70149f; //19318.0f;
|
||||
public static final float startY = 94f; // 94f;
|
||||
public static final float startZ = -73553; // -73661.0f;
|
||||
public static final Vector3fImmutable DEFAULT_START = new Vector3fImmutable(
|
||||
MBServerStatics.startX, MBServerStatics.startY,
|
||||
MBServerStatics.startZ);
|
||||
|
||||
/*
|
||||
* Base movement speeds. Do NOT modify these. They must match the client
|
||||
*/
|
||||
public static final float FLYWALKSPEED = 6.33f;
|
||||
public static final float FLYRUNSPEED = 18.38f;
|
||||
public static final float SWIMSPEED = 6.5f;
|
||||
public static final float WALKSPEED = 6.5f;
|
||||
public static final float RUNSPEED = 14.67f;
|
||||
public static final float COMBATWALKSPEED = 4.44f;
|
||||
public static final float COMBATRUNSPEED = 14.67f;
|
||||
public static final float RUNSPEED_MOB = 15.4f;
|
||||
|
||||
public static final float MOVEMENT_DESYNC_TOLERANCE = 2f; // Distance out of
|
||||
public static String ITEMNOTINVENTORY = "Item must be in your inventory.";
|
||||
public static String ZEROITEM = "This item has zero quantity.";
|
||||
// sync with
|
||||
// client can be
|
||||
// before
|
||||
// generating
|
||||
// debug
|
||||
// messages
|
||||
// max units a player can desync before the server stops forcing
|
||||
// client->server sync
|
||||
public static final float MOVEMENT_MAX_DESYNC = 1000;
|
||||
|
||||
public static final int IGNORE_LIST_MAX = 60;
|
||||
|
||||
public static final float NO_WEAPON_RANGE = 8f; // Range for attack with no
|
||||
// weapon
|
||||
|
||||
|
||||
public static final float REGEN_IDLE = .06f;
|
||||
/*
|
||||
* Base regen rates. Do NOT modify these. They must match the client %per
|
||||
* second for health/mana. x per second for stamina.
|
||||
*/
|
||||
public static final float HEALTH_REGEN_SIT = 0.0033333f; // 100% in 3
|
||||
// minutes
|
||||
public static final float HEALTH_REGEN_IDLE = 0.000666667f; // 100% in 25
|
||||
// minutes
|
||||
public static final float HEALTH_REGEN_WALK = 0.0005f; // 100% in 33.33
|
||||
// minutes
|
||||
public static final float HEALTH_REGEN_RUN = 0f;
|
||||
public static final float HEALTH_REGEN_SWIM_NOSTAMINA = -.03f; // 100% in
|
||||
// 33.33
|
||||
// seconds.
|
||||
// Needs
|
||||
// verified
|
||||
|
||||
public static final float HEALTH_REGEN_SIT_STATIC = 0.33333f; // 100% in 3
|
||||
// minutes
|
||||
public static final float HEALTH_REGEN_IDLE_STATIC = 0.0666667f; // 100% in
|
||||
// 25
|
||||
// minutes
|
||||
public static final float HEALTH_REGEN_WALK_STATIC = 0.05f; // 100% in 33.33
|
||||
// minutes
|
||||
public static final float HEALTH_REGEN_RUN_STATIC = 0f;
|
||||
public static final float HEALTH_REGEN_SWIM_NOSTAMINA_STATIC = 0f; // 100%
|
||||
|
||||
public static final float MANA_REGEN_STATIC = 0.16666666666666666666666666666667f;
|
||||
// in 30
|
||||
// seconds.
|
||||
// Needs
|
||||
// verified
|
||||
|
||||
public static final float MANA_REGEN_SIT = 0.008333333f; // 100% in 2
|
||||
// minutes <=
|
||||
// needs
|
||||
// verified
|
||||
public static final float MANA_REGEN_IDLE = 0.00166667f; // 100% in 10
|
||||
// minutes <=
|
||||
// needs
|
||||
// verified
|
||||
public static final float MANA_REGEN_WALK = 0.00125f; // 100% in 13.333
|
||||
// minutes <= needs
|
||||
// verified
|
||||
public static final float MANA_REGEN_RUN = 0f;
|
||||
|
||||
public static final float STAMINA_REGEN_SIT = 2f; // 2 per second
|
||||
public static final float STAMINA_REGEN_IDLE = 0.2f; // 1 per 5 seconds
|
||||
public static final float STAMINA_REGEN_WALK = 0f;
|
||||
public static final float STAMINA_REGEN_RUN_COMBAT = -0.6499999762f;
|
||||
public static final float STAMINA_REGEN_RUN_NONCOMBAT = -0.400000006f;
|
||||
public static final float STAMINA_REGEN_SWIM = -1f; // -1 per second
|
||||
public static float STAMINA_REGEN_FLY_IDLE = -2f; // needs verifying
|
||||
public static float STAMINA_REGEN_FLY_WALK = -1f; // needs verifying
|
||||
public static float STAMINA_REGEN_FLY_RUN = -1.400000006f; // needs verifying
|
||||
public static float STAMINA_REGEN_FLY_RUN_COMBAT = -1.6499999762f; // needs verifying
|
||||
|
||||
public static final int REGEN_SENSITIVITY_PLAYER = 250; // calc regen ever X
|
||||
// ms
|
||||
public static final int REGEN_SENSITIVITY_MOB = 1000; // calc regen ever X
|
||||
// ms
|
||||
/*
|
||||
* Tombstone type to show Tombstone (2022); Tombstone, Grave (2023);
|
||||
* Tombstone, Skull (2024);
|
||||
*/
|
||||
public static final int TOMBSTONE = 2024;
|
||||
public static final int DEATH_SHROUD_DURATION = 1; // 3 minute death shroud
|
||||
public static final int SAFE_MODE_DURATION = 1; // 3 minute safe mode
|
||||
|
||||
/*
|
||||
* Timers
|
||||
*/
|
||||
public static final int LOGOUT_TIMER_MS = 1000; // logout delay applied
|
||||
// after the last
|
||||
// aggressive action
|
||||
public static final int CLEANUP_TIMER_MS = 15 * 60 * 1000; // Remove player
|
||||
// from cache
|
||||
// after 15
|
||||
// minutes
|
||||
public static final int CORPSE_CLEANUP_TIMER_MS = 15 * 60 * 1000; // Cleanup
|
||||
// corpse
|
||||
// in
|
||||
// world
|
||||
// after
|
||||
// 15
|
||||
// minutes
|
||||
public static final int DEFAULT_SPAWN_TIME_MS = 3 * 60 * 1000; // 3 minute
|
||||
// respawn
|
||||
// on mobs
|
||||
// default
|
||||
public static final int SESSION_CLEANUP_TIMER_MS = 30 * 1000; // cleanup
|
||||
// sessions
|
||||
// for login
|
||||
// 30
|
||||
// seconds
|
||||
// after
|
||||
// logout
|
||||
public static final int MOVEMENT_FREQUENCY_MS = 1000; // Update movement
|
||||
// once every X ms
|
||||
public static final int FLY_FREQUENCY_MS = 1000; // Update flight once every
|
||||
|
||||
public static final float FLY_RATE = .0078f;
|
||||
// x ms
|
||||
public static final int HEIGHT_CHANGE_TIMER_MS = 125; // Time in ms to fly
|
||||
// up or down 1 unit
|
||||
public static final long OPCODE_HANDLE_TIME_WARNING_MS = 250L;
|
||||
public static final long DB_QUERY_WARNING_TIME_MS = 250L;
|
||||
public static final long DB_UPDATE_WARNING_TIME_MS = 250L;
|
||||
public static final long DB_EXECUTION_WARNING_TIME_MS = 250L;
|
||||
public static boolean DB_ENABLE_QUERY_OUTPUT = false;
|
||||
public static final int SUMMON_MAX_WAIT = 18000; // 18 seconds to accept
|
||||
// summons
|
||||
public static final int THIRTY_SECONDS = 30000;
|
||||
public static final int FOURTYFIVE_SECONDS = 45000;
|
||||
public static final int ONE_MINUTE = 60000;
|
||||
public static final int FIVE_MINUTES = 300000;
|
||||
public static final int FIFTEEN_MINUTES = 900000;
|
||||
public static final int THIRTY_MINUTES = 1800000;
|
||||
public static final long TWENTY_FOUR_HOURS = 86400000;
|
||||
public static final int LOAD_OBJECT_DELAY = 500; // long to wait to update
|
||||
public static int IPLimit = 5000;
|
||||
// group list after
|
||||
// LoadChar
|
||||
public static final int UPDATE_LINK_WORLD = 2500;
|
||||
public static final int UPDATE_LINK_LOGIN = 2500;
|
||||
public static final int TELEPORT_TIME_IN_SECONDS = 10;
|
||||
public static final int REPLEDGE_TIME_IN_SECONDS = 0;
|
||||
public static final int CHECK_DATABASE_UPDATES = 10000; // update database
|
||||
// changes every 10
|
||||
// seconds.
|
||||
public static final int RUNEGATE_CLOSE_TIME = 30000; // runegate close timer
|
||||
public static final long PLAYER_KILL_XP_TIMER = 60 * 60 * 1000; // 60
|
||||
// minutes
|
||||
// between
|
||||
// grant xp
|
||||
// on same
|
||||
// target
|
||||
public static final int UPDATE_GROUP_RATE = 10000; // Update group info
|
||||
// every 10 seconds
|
||||
public static float PLAYER_HATE_DELIMITER = 50; // reduces 50 hate a second
|
||||
// while player idling.
|
||||
public static float PLAYER_COMBAT_HATE_MODIFIER = 2;
|
||||
|
||||
/*
|
||||
* AI
|
||||
*/
|
||||
|
||||
// The min distance from players at which the AI Manager feels safe to turn
|
||||
// off a mob.
|
||||
public static int AI_BASE_AGGRO_RANGE = 60;
|
||||
public static int AI_DROP_AGGRO_RANGE = 60;
|
||||
public static int AI_RECALL_RANGE = 400;
|
||||
public static int AI_PULSE_MOB_THRESHOLD = 200;
|
||||
public static int AI_THREAD_SLEEP = 1000;
|
||||
public static int AI_PATROL_DIVISOR = 10;
|
||||
public static int AI_POWER_DIVISOR = 20;
|
||||
public static int AI_PET_HEEL_DISTANCE = 10;
|
||||
public static int AI_PATROL_RADIUS = 60;
|
||||
|
||||
public static float AI_MAX_ANGLE = 10f;
|
||||
|
||||
public static final int AI_PET_TIME_BETWEEN_JOB_TICKS_MS = 250;
|
||||
|
||||
// Pet Settings
|
||||
public static final float PET_TELEPORT_DISTANCE = 600; // distance a pet
|
||||
// teleports to
|
||||
// player
|
||||
public static final float PET_FOLLOW_DISTANCE = 10; // distance a pet starts
|
||||
// moving towards owner
|
||||
public static final float PET_REST_DISTANCE = 4; // distance a pet stops
|
||||
// moving towards owner
|
||||
|
||||
/*
|
||||
* Combat
|
||||
*/
|
||||
public static final int COMBAT_SEND_DODGE = 20;
|
||||
public static final int COMBAT_SEND_BLOCK = 21;
|
||||
public static final int COMBAT_SEND_PARRY = 22;
|
||||
public static final short LEVELCAP = 75;
|
||||
public static final int LEVEL_CON_WHITE = 7;
|
||||
public static final int RESPAWN_TIMER = 90 * 1000;
|
||||
public static final int DESPAWN_TIMER = 12 * 1000;
|
||||
public static final int DESPAWN_TIMER_WITH_LOOT = 90 * 1000;
|
||||
public static final int DESPAWN_TIMER_ONCE_LOOTED = 5 * 1000;
|
||||
public static final int MAX_COMBAT_HITBOX_RADIUS = 80;
|
||||
public static final int PROC_CHANCE = 5; // %chance to proc
|
||||
public static float PRODUCTION_TIME_MULTIPLIER = .5f;
|
||||
|
||||
/*
|
||||
* Mob loot -- gold calculations
|
||||
*/
|
||||
public static final String STRONGBOX_DELAY_STRING = "StrongboxSpam";
|
||||
public static final String STRONGBOX_DELAY_OUTPUT = "You must wait 1 minute to do this again.";
|
||||
public static final int BIG_SPAM_DELAY = 10000;
|
||||
public static String BIG_SPAM_DELAY_STRING = "BIGSPAM";
|
||||
|
||||
public static final double GOLD_DROP_PERCENTAGE_CHANCE = 61d;
|
||||
public static final double GOLD_DROP_MULTIPLIER_GLOBAL = 1.0d; // tweak all
|
||||
// rates at
|
||||
// once
|
||||
public static final double GOLD_DROP_MULTIPLIER_HOTZONE = 2.0d;
|
||||
public static final double GOLD_DROP_MULTIPLIER_MAELSTROM = 1.1d;
|
||||
public static final double GOLD_DROP_MULTIPLIER_OBLIVION = 1.1d;
|
||||
public static final double[] GOLD_DROP_MINIMUM_PER_MOB_LEVEL = { 450, 450,
|
||||
450, 450, 450, // 0 - 4
|
||||
450, 450, 450, 450, 450, // 5 - 9
|
||||
450, 1000, 1000, 1000, 1000, // 10 - 14
|
||||
1000, 1000, 1000, 1000, 1000, // 15 - 19
|
||||
1000, 1000, 1000, 1000, 1000, // 20 - 24
|
||||
2000, 2000, 2000, 2000, 2000, // 25 - 29
|
||||
2000, 2000, 2000, 2000, 2000, // 30 - 34
|
||||
2000, 2000, 2000, 2000, 2000, // 35 - 39
|
||||
4000, 4000, 4000, 4000, 4000, // 40 - 44
|
||||
4000, 4000, 4000, 4000, 4000, // 45 - 49
|
||||
5000 // 50+
|
||||
};
|
||||
public static final double[] GOLD_DROP_MAXIMUM_PER_MOB_LEVEL = { 1000,
|
||||
1000, 1000, 1000, 1000, // 0 - 4
|
||||
1000, 1000, 1000, 1000, 1000, // 5 - 9
|
||||
1000, 2500, 2500, 2500, 2500, // 10 - 14
|
||||
2500, 2500, 2500, 2500, 2500, // 15 - 19
|
||||
2500, 2500, 2500, 2500, 2500, // 20 - 24
|
||||
4000, 4000, 4000, 4000, 4000, // 25 - 29
|
||||
4000, 4000, 4000, 4000, 4000, // 30 - 34
|
||||
4000, 4000, 4000, 4000, 4000, // 35 - 39
|
||||
9000, 9000, 9000, 9000, 9000, // 40 - 44
|
||||
9000, 9000, 9000, 9000, 9000, // 45 - 49
|
||||
12000 // 50+
|
||||
};
|
||||
|
||||
// DO NOT FINAL THESE FIELD!
|
||||
public static Enum.AccountStatus accessLevel; // Min account level to login to server
|
||||
public static boolean blockLogin = false;
|
||||
public static boolean ENABLE_VAULT_FILL = false;
|
||||
public static boolean ENABLE_MOB_LOOT = true;
|
||||
public static boolean ENABLE_AUDIT_JOB_WORKERS = true;
|
||||
public static boolean ENABLE_COMBAT_TARGET_HITBOX = true;
|
||||
|
||||
/*
|
||||
* Track Sensitivity
|
||||
*/
|
||||
// Rate that track arrow refreshes. When inside TRACK_ARROW_FAST_RANGE, use
|
||||
// TRACK_ARROW_SENSITIVITY_FAST speed, otherwise use TRACK_ARROW_SENSITIVITY
|
||||
// speed.
|
||||
public static final float TRACK_ARROW_FAST_RANGE = 50f; // Range to go from
|
||||
// Fast arrow to
|
||||
// slow
|
||||
public static final int TRACK_ARROW_SENSITIVITY = 1000; // Refresh track
|
||||
// arrows every X ms
|
||||
public static final int TRACK_ARROW_SENSITIVITY_FAST = 250; // Refresh track
|
||||
// arrows every
|
||||
// X ms
|
||||
|
||||
/*
|
||||
* Population breakpoints
|
||||
*/
|
||||
public static final int LOW_POPULATION = 100;
|
||||
public static final int NORMAL_POPULATION = 500;
|
||||
public static final int HIGH_POPULATION = 1000;
|
||||
public static final int VERY_OVERPOPULATED_POPULATION = 3000;
|
||||
public static final int FULL_POPULATION = 5000;
|
||||
|
||||
// Refresh sensetivities
|
||||
public static final int TRACK_WINDOW_THRESHOLD = 1000; // max refresh once
|
||||
// every 1 seconds.
|
||||
public static final int WHO_WINDOW_THRESHOLD = 3000; // max refresh once
|
||||
// every 3 seconds.
|
||||
public static final int VENDOR_WINDOW_THRESHOLD = 2000; // max refresh once
|
||||
// every 2 seconds.
|
||||
public static final int PURCHASE_THRESHOLD = 500; // max refresh once every
|
||||
// 0.5 seconds.
|
||||
public static final int SELL_THRESHOLD = 100; // max refresh once every 0.1
|
||||
// seconds.
|
||||
public static final int MAX_PLAYER_LOAD_SIZE = 1000;
|
||||
|
||||
// Mine related
|
||||
public static final int MINE_EARLY_WINDOW = 16; // 3pm
|
||||
public static final int MINE_LATE_WINDOW = 0; // Midnight
|
||||
|
||||
// Race
|
||||
public static final float RADIUS_ARACOIX = 0.68999999761581f;
|
||||
public static final float RADIUS_MINOTAUR = 0.69960004091263f;
|
||||
public static final float RADIUS_DWARF = 0;
|
||||
public static final float RADIUS_HUMAN = 0;
|
||||
public static final float RADIUS_NEPHILIM = 0;
|
||||
public static final float RADIUS_AELFBORN = 0;
|
||||
public static final float RADIUS_ELF = 0;
|
||||
public static final float RADIUS_VAMPIRE = 0;
|
||||
public static final float RADIUS_IREKEI = 0;
|
||||
public static final float RADIUS_HALF_GIANT = 0;
|
||||
public static final float RADIUS_SHADE = 0;
|
||||
public static final float RADIUS_CENTAUR = 0.68999999761581f;
|
||||
|
||||
public static String JUNIOR = "Junior";
|
||||
public static String VETERAN = "Veteran";
|
||||
public static String ELITE = "Elite";
|
||||
|
||||
public static int worldMapID = Integer.parseInt(ConfigManager.MB_WORLD_MAPID.getValue());
|
||||
public static int worldUUID = Integer.parseInt(ConfigManager.MB_WORLD_UUID.getValue());
|
||||
public static Enum.AccountStatus worldAccessLevel = Enum.AccountStatus.valueOf(ConfigManager.MB_WORLD_ACCESS_LVL.getValue());
|
||||
}
|
||||
@@ -0,0 +1,524 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2022
|
||||
// www.magicbane.com
|
||||
|
||||
|
||||
package engine.server.login;
|
||||
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import engine.Enum;
|
||||
import engine.gameManager.*;
|
||||
import engine.job.JobScheduler;
|
||||
import engine.jobs.CSessionCleanupJob;
|
||||
import engine.net.Network;
|
||||
import engine.net.client.ClientConnection;
|
||||
import engine.net.client.ClientConnectionManager;
|
||||
import engine.net.client.Protocol;
|
||||
import engine.net.client.msg.login.ServerStatusMsg;
|
||||
import engine.net.client.msg.login.VersionInfoMsg;
|
||||
import engine.objects.*;
|
||||
import engine.server.MBServerStatics;
|
||||
import engine.util.ByteUtils;
|
||||
import engine.util.ThreadUtils;
|
||||
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.InetAddress;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Iterator;
|
||||
|
||||
import static java.lang.System.exit;
|
||||
|
||||
public class LoginServer {
|
||||
|
||||
|
||||
// Instance variables
|
||||
|
||||
private VersionInfoMsg versionInfoMessage;
|
||||
public static HikariDataSource connectionPool = null;
|
||||
public static int population = 0;
|
||||
public static boolean worldServerRunning = false;
|
||||
public static boolean loginServerRunning = false;
|
||||
|
||||
public static ServerStatusMsg serverStatusMsg = new ServerStatusMsg(0, (byte) 1);
|
||||
|
||||
// This is the entrypoint for the MagicBane Login Server when
|
||||
// it is executed by the command line scripts. The fun begins here!
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
LoginServer loginServer;
|
||||
|
||||
// Initialize TinyLog logger with our own format
|
||||
|
||||
Configurator.defaultConfig()
|
||||
.addWriter(new RollingFileWriter("logs/login/login.txt", 30, new TimestampLabeler(), new StartupPolicy()))
|
||||
.level(Level.DEBUG)
|
||||
.formatPattern("{level} {date:yyyy-MM-dd HH:mm:ss.SSS} [{thread}] {class}.{method}({line}) : {message}")
|
||||
.activate();
|
||||
|
||||
try {
|
||||
|
||||
// Configure the the Login Server
|
||||
|
||||
loginServer = new LoginServer();
|
||||
ConfigManager.loginServer = loginServer;
|
||||
ConfigManager.handler = new LoginServerMsgHandler(loginServer);
|
||||
|
||||
ConfigManager.serverType = Enum.ServerType.LOGINSERVER;
|
||||
|
||||
if (ConfigManager.init() == false) {
|
||||
Logger.error("ABORT! Missing config entry!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Start the Login Server
|
||||
|
||||
loginServer.init();
|
||||
loginServer.exec();
|
||||
|
||||
exit(0);
|
||||
|
||||
} catch (Exception e) {
|
||||
Logger.error(e);
|
||||
e.printStackTrace();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Mainline execution loop for the login server.
|
||||
|
||||
private void exec() {
|
||||
|
||||
|
||||
LocalDateTime nextCacheTime = LocalDateTime.now();
|
||||
LocalDateTime nextServerTime = LocalDateTime.now();
|
||||
LocalDateTime nextDatabaseTime = LocalDateTime.now();
|
||||
|
||||
loginServerRunning = true;
|
||||
|
||||
while (true) {
|
||||
|
||||
// Invalidate cache for players driven by forum
|
||||
// and stored procedure forum_link_pass()
|
||||
|
||||
try {
|
||||
|
||||
// Run cache routine right away if requested.
|
||||
|
||||
File cacheFile = new File("cacheInvalid");
|
||||
|
||||
|
||||
if (cacheFile.exists() == true) {
|
||||
nextCacheTime = LocalDateTime.now();
|
||||
Files.deleteIfExists(Paths.get("cacheInvalid"));
|
||||
}
|
||||
|
||||
if (LocalDateTime.now().isAfter(nextCacheTime)) {
|
||||
invalidateCacheList();
|
||||
nextCacheTime = LocalDateTime.now().plusSeconds(30);
|
||||
}
|
||||
|
||||
if (LocalDateTime.now().isAfter(nextServerTime)) {
|
||||
checkServerHealth();
|
||||
nextServerTime = LocalDateTime.now().plusSeconds(1);
|
||||
}
|
||||
|
||||
if (LocalDateTime.now().isAfter(nextDatabaseTime)) {
|
||||
String pop = SimulationManager.getPopulationString();
|
||||
Logger.info("Keepalive: " + pop);
|
||||
nextDatabaseTime = LocalDateTime.now().plusMinutes(30);
|
||||
}
|
||||
|
||||
ThreadUtils.sleep(100);
|
||||
} catch (Exception e) {
|
||||
Logger.error(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Constructor
|
||||
|
||||
public LoginServer() {
|
||||
|
||||
}
|
||||
|
||||
private boolean init() {
|
||||
|
||||
// Initialize Application Protocol
|
||||
|
||||
Protocol.initProtocolLookup();
|
||||
|
||||
// Configure the VersionInfoMsgs:
|
||||
|
||||
this.versionInfoMessage = new VersionInfoMsg(MBServerStatics.PCMajorVer,
|
||||
MBServerStatics.PCMinorVer);
|
||||
|
||||
Logger.info("Initializing Database Pool");
|
||||
initDatabasePool();
|
||||
|
||||
Logger.info("Initializing Database layer");
|
||||
initDatabaseLayer();
|
||||
|
||||
Logger.info("Initializing Network");
|
||||
Network.init();
|
||||
|
||||
Logger.info("Initializing Client Connection Manager");
|
||||
initClientConnectionManager();
|
||||
|
||||
// instantiate AccountManager
|
||||
Logger.info("Initializing SessionManager.");
|
||||
|
||||
// Sets cross server behavior
|
||||
SessionManager.setCrossServerBehavior(0);
|
||||
|
||||
// activate powers manager
|
||||
Logger.info("Initializing PowersManager.");
|
||||
PowersManager.initPowersManager(false);
|
||||
|
||||
RuneBaseAttribute.LoadAllAttributes();
|
||||
RuneBase.LoadAllRuneBases();
|
||||
BaseClass.LoadAllBaseClasses();
|
||||
Race.loadAllRaces();
|
||||
RuneBaseEffect.LoadRuneBaseEffects();
|
||||
|
||||
Logger.info("Initializing Blueprint data.");
|
||||
Blueprint.loadAllBlueprints();
|
||||
|
||||
Logger.info("Loading Kits");
|
||||
DbManager.KitQueries.GET_ALL_KITS();
|
||||
|
||||
Logger.info("Initializing ItemBase data.");
|
||||
ItemBase.loadAllItemBases();
|
||||
|
||||
Logger.info("Initializing Race data");
|
||||
Enum.RaceType.initRaceTypeTables();
|
||||
Race.loadAllRaces();
|
||||
|
||||
Logger.info("Initializing Errant Guild");
|
||||
Guild.CreateErrantGuild();
|
||||
|
||||
Logger.info("Loading All Guilds");
|
||||
DbManager.GuildQueries.GET_ALL_GUILDS();
|
||||
|
||||
|
||||
Logger.info("***Boot Successful***");
|
||||
return true;
|
||||
}
|
||||
|
||||
private 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.configureDatabaseLayer();
|
||||
|
||||
} catch (Exception e) {
|
||||
Logger.error(e.getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
PreparedStatementShared.submitPreparedStatementsCleaningJob();
|
||||
|
||||
if (MBServerStatics.DB_DEBUGGING_ON_BY_DEFAULT) {
|
||||
PreparedStatementShared.enableDebugging();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void removeClient(ClientConnection conn) {
|
||||
if (conn == null) {
|
||||
Logger.info(
|
||||
"ClientConnection null in removeClient.");
|
||||
return;
|
||||
}
|
||||
String key = ByteUtils.byteArrayToSafeStringHex(conn
|
||||
.getSecretKeyBytes());
|
||||
|
||||
CSessionCleanupJob cscj = new CSessionCleanupJob(key);
|
||||
|
||||
JobScheduler.getInstance().scheduleJob(cscj,
|
||||
MBServerStatics.SESSION_CLEANUP_TIMER_MS);
|
||||
}
|
||||
|
||||
private void initClientConnectionManager() {
|
||||
|
||||
try {
|
||||
|
||||
String name = ConfigManager.MB_WORLD_NAME.getValue();
|
||||
|
||||
// Find publicIP address for use in worldserver response
|
||||
// message. Sending the client to an unroutable address
|
||||
// doesn't work so well.
|
||||
|
||||
URL whatismyip = new URL("http://checkip.amazonaws.com");
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(
|
||||
whatismyip.openStream()));
|
||||
ConfigManager.MB_PUBLIC_ADDR.setValue(in.readLine());
|
||||
Logger.info("Public address: " + ConfigManager.MB_PUBLIC_ADDR.getValue());
|
||||
|
||||
Logger.info("Magicbane network config: " + ConfigManager.MB_BIND_ADDR.getValue() + ":" + ConfigManager.MB_LOGIN_PORT.getValue());
|
||||
|
||||
InetAddress addy = InetAddress.getByName(ConfigManager.MB_BIND_ADDR.getValue());
|
||||
int port = Integer.parseInt(ConfigManager.MB_LOGIN_PORT.getValue());
|
||||
|
||||
ClientConnectionManager connectionManager = new ClientConnectionManager(name + ".ClientConnMan", addy,
|
||||
port);
|
||||
connectionManager.startup();
|
||||
|
||||
} catch (IOException e) {
|
||||
Logger.error(e.toString());
|
||||
}
|
||||
}
|
||||
/*
|
||||
* message handlers (relay)
|
||||
*/
|
||||
|
||||
// ==============================
|
||||
// Support Functions
|
||||
// ==============================
|
||||
|
||||
public VersionInfoMsg getDefaultVersionInfo() {
|
||||
return versionInfoMessage;
|
||||
}
|
||||
|
||||
//this updates a server being up or down without resending the entire char select screen.
|
||||
public void updateServersForAll(boolean isRunning) {
|
||||
|
||||
try {
|
||||
|
||||
Iterator<ClientConnection> i = SessionManager.getAllActiveClientConnections().iterator();
|
||||
|
||||
while (i.hasNext()) {
|
||||
|
||||
ClientConnection clientConnection = i.next();
|
||||
|
||||
if (clientConnection == null)
|
||||
continue;
|
||||
|
||||
Account ac = clientConnection.getAccount();
|
||||
|
||||
if (ac == null)
|
||||
continue;
|
||||
|
||||
boolean isUp = isRunning;
|
||||
|
||||
|
||||
if (MBServerStatics.worldAccessLevel.ordinal() > ac.status.ordinal())
|
||||
isUp = false;
|
||||
|
||||
LoginServer.serverStatusMsg.setServerID(MBServerStatics.worldMapID);
|
||||
LoginServer.serverStatusMsg.setIsUp(isUp ? (byte) 1 : (byte) 0);
|
||||
clientConnection.sendMsg(LoginServer.serverStatusMsg);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.error(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void checkServerHealth() {
|
||||
|
||||
// Check if worldserver is running
|
||||
|
||||
if (!isPortInUse(Integer.parseInt(ConfigManager.MB_WORLD_PORT.getValue()))) {
|
||||
worldServerRunning = false;
|
||||
population = 0;
|
||||
updateServersForAll(worldServerRunning);
|
||||
return;
|
||||
}
|
||||
|
||||
// Worldserver is running and writes a polling file.
|
||||
// Read the current population count from the server and
|
||||
// update player displays accordingly.
|
||||
|
||||
worldServerRunning = true;
|
||||
population = readPopulationFile();
|
||||
updateServersForAll(worldServerRunning);
|
||||
|
||||
}
|
||||
|
||||
private void initDatabasePool() {
|
||||
|
||||
HikariConfig config = new HikariConfig();
|
||||
|
||||
config.setMaximumPoolSize(33); // (16 cores 1 spindle)
|
||||
|
||||
config.setJdbcUrl("jdbc:mysql://" + ConfigManager.MB_DATABASE_ADDRESS.getValue() +
|
||||
":" + ConfigManager.MB_DATABASE_PORT.getValue() + "/" +
|
||||
ConfigManager.MB_DATABASE_NAME.getValue());
|
||||
config.setUsername(ConfigManager.MB_DATABASE_USER.getValue());
|
||||
config.setPassword(ConfigManager.MB_DATABASE_PASS.getValue());
|
||||
config.addDataSourceProperty("characterEncoding", "utf8");
|
||||
config.addDataSourceProperty("cachePrepStmts", "true");
|
||||
config.addDataSourceProperty("prepStmtCacheSize", "250");
|
||||
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
|
||||
|
||||
connectionPool = new HikariDataSource(config); // setup the connection pool
|
||||
|
||||
Logger.info("local database connection configured");
|
||||
}
|
||||
|
||||
public void invalidateCacheList() {
|
||||
|
||||
int objectUUID;
|
||||
String objectType;
|
||||
|
||||
try (Connection connection = connectionPool.getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("SELECT * FROM `login_cachelist`");
|
||||
ResultSet rs = statement.executeQuery()) {
|
||||
|
||||
while (rs.next()) {
|
||||
|
||||
objectUUID = rs.getInt("UID");
|
||||
objectType = rs.getString("type");
|
||||
|
||||
Logger.info("INVALIDATED : " + objectType + " UUID: " + objectUUID);
|
||||
|
||||
switch (objectType) {
|
||||
|
||||
case "account":
|
||||
DbManager.removeFromCache(Enum.GameObjectType.Account, objectUUID);
|
||||
break;
|
||||
case "character":
|
||||
DbManager.removeFromCache(Enum.GameObjectType.PlayerCharacter, objectUUID);
|
||||
PlayerCharacter player = (PlayerCharacter) DbManager.getObject(Enum.GameObjectType.PlayerCharacter, objectUUID);
|
||||
PlayerCharacter.initializePlayer(player);
|
||||
player.getAccount().characterMap.replace(player.getObjectUUID(), player);
|
||||
Logger.info("Player active state is : " + player.isActive());
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
Logger.info(e.toString());
|
||||
}
|
||||
|
||||
// clear the db table
|
||||
|
||||
try (Connection connection = connectionPool.getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("DELETE FROM `login_cachelist`")) {
|
||||
|
||||
statement.execute();
|
||||
|
||||
} catch (SQLException e) {
|
||||
Logger.info(e.toString());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static boolean getActiveBaneQuery(PlayerCharacter playerCharacter) {
|
||||
|
||||
boolean outStatus = false;
|
||||
|
||||
// char has never logged on so cannot have dropped a bane
|
||||
|
||||
if (playerCharacter.getHash() == null)
|
||||
return outStatus;
|
||||
|
||||
// query data warehouse for unresolved bane with this character
|
||||
|
||||
try (Connection connection = connectionPool.getConnection();
|
||||
PreparedStatement statement = buildQueryActiveBaneStatement(connection, playerCharacter);
|
||||
ResultSet rs = statement.executeQuery()) {
|
||||
|
||||
while (rs.next()) {
|
||||
|
||||
outStatus = true;
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
Logger.error(e.toString());
|
||||
}
|
||||
|
||||
return outStatus;
|
||||
}
|
||||
|
||||
private static PreparedStatement buildQueryActiveBaneStatement(Connection connection, PlayerCharacter playerCharacter) throws SQLException {
|
||||
PreparedStatement outStatement;
|
||||
String queryString = "SELECT `city_id` FROM `warehouse_banehistory` WHERE `char_id` = ? AND `RESOLUTION` = 'PENDING'";
|
||||
outStatement = connection.prepareStatement(queryString);
|
||||
outStatement.setString(1, playerCharacter.getHash());
|
||||
return outStatement;
|
||||
|
||||
}
|
||||
|
||||
public static boolean isPortInUse(int port) {
|
||||
|
||||
ProcessBuilder builder = new ProcessBuilder("/bin/bash", "-c", "lsof -i tcp:" + port + " | tail -n +2 | awk '{print $2}'");
|
||||
builder.redirectErrorStream(true);
|
||||
Process process = null;
|
||||
String line = null;
|
||||
boolean portInUse = false;
|
||||
|
||||
try {
|
||||
process = builder.start();
|
||||
|
||||
InputStream is = process.getInputStream();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
||||
|
||||
while ((line = reader.readLine()) != null) {
|
||||
portInUse = true;
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return portInUse;
|
||||
}
|
||||
|
||||
private int readPopulationFile() {
|
||||
|
||||
ProcessBuilder builder = new ProcessBuilder("/bin/bash", "-c", "cat " + MBServerStatics.DEFAULT_DATA_DIR + ConfigManager.MB_WORLD_NAME.getValue().replaceAll("'","") + ".pop");
|
||||
builder.redirectErrorStream(true);
|
||||
Process process = null;
|
||||
String line = null;
|
||||
int population = 0;
|
||||
|
||||
try {
|
||||
|
||||
process = builder.start();
|
||||
|
||||
InputStream is = process.getInputStream();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
||||
|
||||
while ((line = reader.readLine()) != null) {
|
||||
population = Integer.parseInt(line);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return 0;
|
||||
}
|
||||
|
||||
return population;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,444 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2022
|
||||
// www.magicbane.com
|
||||
|
||||
|
||||
package engine.server.login;
|
||||
|
||||
import engine.Enum;
|
||||
import engine.Enum.DispatchChannel;
|
||||
import engine.Enum.GameObjectType;
|
||||
import engine.gameManager.DbManager;
|
||||
import engine.gameManager.SessionManager;
|
||||
import engine.job.JobScheduler;
|
||||
import engine.jobs.DisconnectJob;
|
||||
import engine.net.Dispatch;
|
||||
import engine.net.DispatchMessage;
|
||||
import engine.net.NetMsgHandler;
|
||||
import engine.net.client.ClientConnection;
|
||||
import engine.net.client.Protocol;
|
||||
import engine.net.client.msg.ClientNetMsg;
|
||||
import engine.net.client.msg.ServerInfoMsg;
|
||||
import engine.net.client.msg.login.*;
|
||||
import engine.objects.Account;
|
||||
import engine.objects.GuildStatusController;
|
||||
import engine.objects.PlayerCharacter;
|
||||
import engine.server.MBServerStatics;
|
||||
import engine.session.CSSession;
|
||||
import engine.session.Session;
|
||||
import engine.util.ByteUtils;
|
||||
import engine.util.StringUtils;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
public class LoginServerMsgHandler implements NetMsgHandler {
|
||||
|
||||
private final LoginServer server;
|
||||
|
||||
LoginServerMsgHandler(LoginServer server) {
|
||||
super();
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
/*
|
||||
* =========================================================================
|
||||
* Client Messages
|
||||
* =========================================================================
|
||||
*/
|
||||
@Override
|
||||
public boolean handleClientMsg(ClientNetMsg clientNetMsg) {
|
||||
|
||||
if (clientNetMsg == null) {
|
||||
Logger.error("Recieved null msg. Returning.");
|
||||
return false;
|
||||
}
|
||||
|
||||
ClientConnection origin = (ClientConnection) clientNetMsg.getOrigin();
|
||||
Protocol protocolMsg = clientNetMsg.getProtocolMsg();
|
||||
|
||||
try {
|
||||
|
||||
switch (protocolMsg) {
|
||||
|
||||
case VERSIONINFO:
|
||||
this.VerifyCorrectClientVersion((VersionInfoMsg) clientNetMsg);
|
||||
break;
|
||||
|
||||
case LOGIN:
|
||||
if (LoginServer.loginServerRunning == true)
|
||||
this.Login((ClientLoginInfoMsg) clientNetMsg, origin);
|
||||
else
|
||||
this.KickToLogin(MBServerStatics.LOGINERROR_LOGINSERVER_BUSY, "", origin);
|
||||
break;
|
||||
|
||||
case KEEPALIVESERVERCLIENT:
|
||||
// echo the keep alive back
|
||||
origin.sendMsg(clientNetMsg);
|
||||
break;
|
||||
|
||||
case SELECTSERVER:
|
||||
this.SendServerInfo(origin);
|
||||
break;
|
||||
|
||||
case CREATECHAR:
|
||||
this.CommitNewCharacter((CommitNewCharacterMsg) clientNetMsg, origin);
|
||||
break;
|
||||
|
||||
case REMOVECHAR:
|
||||
this.DeleteCharacter((DeleteCharacterMsg) clientNetMsg, origin);
|
||||
break;
|
||||
|
||||
case SELECTCHAR:
|
||||
this.RequestGameServer((GameServerIPRequestMsg) clientNetMsg, origin);
|
||||
break;
|
||||
|
||||
case SETSELECTEDOBECT:
|
||||
// Why is this being sent to login server?
|
||||
break;
|
||||
|
||||
default:
|
||||
String ocHex = StringUtils.toHexString(protocolMsg.opcode);
|
||||
Logger.error("Cannot not handle Opcode: " + ocHex);
|
||||
return false;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Logger.error("protocolMsg:" + protocolMsg + e.toString());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void VerifyCorrectClientVersion(VersionInfoMsg vim) {
|
||||
ClientConnection cc;
|
||||
String cMajorVer;
|
||||
String cMinorVer;
|
||||
VersionInfoMsg outVim;
|
||||
|
||||
cc = (ClientConnection) vim.getOrigin();
|
||||
cMajorVer = vim.getMajorVersion();
|
||||
cMinorVer = vim.getMinorVersion();
|
||||
|
||||
if (!cMajorVer.equals(this.server.getDefaultVersionInfo().getMajorVersion())) {
|
||||
this.KickToLogin(MBServerStatics.LOGINERROR_INCORRECT_CLIENT_VERSION, "Major Version Failure: " + cMajorVer, cc);
|
||||
return;
|
||||
}
|
||||
|
||||
/* if (!cMinorVer.equals(this.server.getDefaultVersionInfo().getMinorVersion())) {
|
||||
this.KickToLogin(MBServerStatics.LOGINERROR_INCORRECT_CLIENT_VERSION, "Minor Version Failure: " + cMinorVer, cc);
|
||||
return;
|
||||
} */
|
||||
|
||||
if (cMinorVer == null) {
|
||||
this.KickToLogin(MBServerStatics.LOGINERROR_INCORRECT_CLIENT_VERSION, "Minor Version Failure: ", cc);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cMinorVer.length() < 8 || cMinorVer.length() > 16) {
|
||||
this.KickToLogin(MBServerStatics.LOGINERROR_INCORRECT_CLIENT_VERSION, "Minor Version Failure: ", cc);
|
||||
return;
|
||||
}
|
||||
|
||||
// set MachineID for this connection
|
||||
|
||||
cc.machineID = cMinorVer;
|
||||
|
||||
// send fake right back to the client
|
||||
outVim = new VersionInfoMsg(vim.getMajorVersion(), this.server.getDefaultVersionInfo().getMinorVersion() );
|
||||
cc.sendMsg(outVim);
|
||||
}
|
||||
|
||||
// our data access should be in a separate object
|
||||
private void Login(ClientLoginInfoMsg clientLoginInfoMessage, ClientConnection clientConnection) {
|
||||
|
||||
// Add zero length strings to eliminate the need for null checking.
|
||||
String uname = clientLoginInfoMessage.getUname();
|
||||
String pass = clientLoginInfoMessage.getPword();
|
||||
|
||||
// Check to see if there is actually any data in uname.pass
|
||||
if (uname.length() == 0) {
|
||||
this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "The username provided was zero length.", clientConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pass.length() == 0) {
|
||||
this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "The password provided was zero length.", clientConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
Account account;
|
||||
|
||||
account = DbManager.AccountQueries.GET_ACCOUNT(uname);
|
||||
|
||||
if (account == null) {
|
||||
|
||||
this.KickToLogin(MBServerStatics.LOGINERROR_INVALID_USERNAME_PASSWORD, "Could not find account (" + uname + ')', clientConnection);
|
||||
Logger.info("Could not find account (" + uname + ')');
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if (account.getLastLoginFailure() + MBServerStatics.RESET_LOGIN_ATTEMPTS_AFTER < System.currentTimeMillis())
|
||||
account.resetLoginAttempts();
|
||||
|
||||
// TODO: Log the login attempts IP, name, password and timestamp
|
||||
// Check number invalid login attempts. If 5 or greater, kick to login.
|
||||
if (account.getLoginAttempts() >= MBServerStatics.MAX_LOGIN_ATTEMPTS) {
|
||||
|
||||
this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Too many login in attempts for '" + uname + '\'', clientConnection);
|
||||
Logger.info("Too many login in attempts for '" + uname + '\'');
|
||||
return;
|
||||
}
|
||||
|
||||
if (account.lastPasswordCheck < System.currentTimeMillis()) {
|
||||
account.lastPasswordCheck = System.currentTimeMillis() + MBServerStatics.ONE_MINUTE;
|
||||
}
|
||||
|
||||
// Attempt to validate login
|
||||
try {
|
||||
if (!account.passIsValid(pass, clientConnection.getClientIpAddress(), clientConnection.machineID)) {
|
||||
|
||||
account.incrementLoginAttempts();
|
||||
this.KickToLogin(MBServerStatics.LOGINERROR_INVALID_USERNAME_PASSWORD, "", clientConnection);
|
||||
Logger.info("Incorrect password(" + uname + ')');
|
||||
return;
|
||||
}
|
||||
} catch (IllegalArgumentException e1) {
|
||||
this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "", clientConnection);
|
||||
Logger.info("Failed forum account validation(" + uname + ')');
|
||||
}
|
||||
|
||||
// Account deactivated
|
||||
|
||||
if (account.status.equals(Enum.AccountStatus.BANNED)) {
|
||||
this.KickToLogin(MBServerStatics.LOGINERROR_NO_MORE_PLAYTIME_ON_ACCOUNT, "", clientConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check to see if we have a Session mapped with this Account:
|
||||
Session session = SessionManager.getSession(account);
|
||||
|
||||
// If there is, then the account is in use and must be handled:
|
||||
// kick the 'other connection'
|
||||
if (session != null)
|
||||
this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Your account has been accessed from a different IP & Port.", session.getConn()); // Logout the character
|
||||
|
||||
|
||||
// TODO implement character logout
|
||||
// Get a new session
|
||||
session = SessionManager.getNewSession(account, clientConnection);
|
||||
|
||||
// Set Invalid Login Attempts to 0
|
||||
account.resetLoginAttempts();
|
||||
|
||||
// Send Login Response
|
||||
ClientLoginInfoMsg loginResponse = new ClientLoginInfoMsg(clientLoginInfoMessage);
|
||||
loginResponse.setUnknown06(8323072);
|
||||
loginResponse.setUnknown07(3276800);
|
||||
loginResponse.setUnknown08(196608);
|
||||
loginResponse.setUnknown09((short) 15);
|
||||
|
||||
clientConnection.sendMsg(loginResponse);
|
||||
|
||||
// send character select screen
|
||||
try {
|
||||
this.sendCharacterSelectScreen(session);
|
||||
} catch (Exception e) {
|
||||
Logger.error("Unable to Send Character Select Screen to client");
|
||||
this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Unable to send Character Select Screen to client.", clientConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
// Logging
|
||||
String addyPort = clientConnection.getRemoteAddressAndPortAsString();
|
||||
int id = account.getObjectUUID();
|
||||
|
||||
Logger.info(uname + '(' + id + ") has successfully logged in from " + addyPort);
|
||||
|
||||
}
|
||||
|
||||
private void KickToLogin(int errCode, String message, ClientConnection origin) {
|
||||
LoginErrorMsg msg = new LoginErrorMsg(errCode, message);
|
||||
|
||||
PlayerCharacter player = origin.getPlayerCharacter();
|
||||
|
||||
if (player == null) {
|
||||
origin.sendMsg(msg);
|
||||
} else {
|
||||
Dispatch dispatch = Dispatch.borrow(player, msg);
|
||||
DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
|
||||
}
|
||||
|
||||
|
||||
Logger.info("Kicking to Login. Message: '" + message + '\'');
|
||||
|
||||
DisconnectJob dj = new DisconnectJob(origin);
|
||||
JobScheduler.getInstance().scheduleJob(dj, 250);
|
||||
}
|
||||
|
||||
protected void sendCharacterSelectScreen(Session s) {
|
||||
sendCharacterSelectScreen(s, false);
|
||||
}
|
||||
|
||||
private void sendCharacterSelectScreen(Session s, boolean fromCommit) {
|
||||
|
||||
if (s.getAccount() != null) {
|
||||
CharSelectScreenMsg cssm = new CharSelectScreenMsg(s, fromCommit);
|
||||
s.getConn().sendMsg(cssm);
|
||||
} else {
|
||||
Logger.error("No Account Found: Unable to Send Character Select Screen");
|
||||
this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Unable to send Character Select Screen to client.", s.getConn());
|
||||
}
|
||||
}
|
||||
|
||||
private void SendServerInfo(ClientConnection conn) {
|
||||
ServerInfoMsg sim = new ServerInfoMsg();
|
||||
|
||||
if (!conn.sendMsg(sim)) {
|
||||
Logger.error("Failed to send message");
|
||||
|
||||
this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Unable to send ServerInfoMsg to client.", conn);
|
||||
}
|
||||
}
|
||||
|
||||
private void CommitNewCharacter(CommitNewCharacterMsg commitNewCharacterMessage, ClientConnection clientConnection) {
|
||||
|
||||
Session session = SessionManager.getSession(clientConnection);
|
||||
|
||||
if (session.getAccount() == null)
|
||||
return;
|
||||
|
||||
try {
|
||||
// Check to see if there is an available slot.
|
||||
if (session.getAccount().characterMap.size() >= MBServerStatics.MAX_NUM_OF_CHARACTERS) {
|
||||
this.sendCharacterSelectScreen(session);
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerCharacter pc = PlayerCharacter.generatePCFromCommitNewCharacterMsg(session.getAccount(), commitNewCharacterMessage, clientConnection);
|
||||
|
||||
if (pc == null) {
|
||||
Logger.info("Player returned null while creating character.");
|
||||
this.sendCharacterSelectScreen(session, true);
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerCharacter.initializePlayer(pc);
|
||||
session.getAccount().characterMap.putIfAbsent(pc.getObjectUUID(), pc);
|
||||
// Send back to Character Select Screen
|
||||
this.sendCharacterSelectScreen(session, true);
|
||||
|
||||
} catch (Exception e) {
|
||||
Logger.error(e);
|
||||
this.sendCharacterSelectScreen(session, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendInvalidNameMsg(String firstName, String lastName, int errorCode, ClientConnection clientConnection) {
|
||||
|
||||
InvalidNameMsg invalidNameMessage;
|
||||
|
||||
if (firstName.length() > 256 || lastName.length() > 256)
|
||||
invalidNameMessage = new InvalidNameMsg(firstName, lastName, errorCode);
|
||||
else
|
||||
invalidNameMessage = new InvalidNameMsg(firstName, lastName, errorCode);
|
||||
|
||||
clientConnection.sendMsg(invalidNameMessage);
|
||||
}
|
||||
|
||||
private void DeleteCharacter(DeleteCharacterMsg msg, ClientConnection origin) {
|
||||
|
||||
try {
|
||||
PlayerCharacter player;
|
||||
Session session;
|
||||
|
||||
session = SessionManager.getSession(origin);
|
||||
player = (PlayerCharacter) DbManager.getObject(GameObjectType.PlayerCharacter, msg.getCharacterUUID());
|
||||
|
||||
if (player == null) {
|
||||
Logger.error("Delete Error: PlayerID=" + msg.getCharacterUUID() + " not found.");
|
||||
this.sendCharacterSelectScreen(session);
|
||||
return;
|
||||
}
|
||||
|
||||
if (session.getAccount() == null) {
|
||||
Logger.error("Delete Error: Account not found.");
|
||||
this.sendCharacterSelectScreen(session);
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.getAccount() != origin.getAccount()) {
|
||||
Logger.error("Delete Error: Character " + player.getName() + " does not belong to account " + origin.getAccount().getUname());
|
||||
this.sendCharacterSelectScreen(session);
|
||||
return;
|
||||
}
|
||||
|
||||
//Can't delete as Guild Leader
|
||||
//TODO either find an error or just gdisband.
|
||||
|
||||
if (GuildStatusController.isGuildLeader(player.getGuildStatus())) {
|
||||
this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Cannot delete a guild leader.", origin);
|
||||
return;
|
||||
}
|
||||
|
||||
// check for active banes
|
||||
|
||||
if (LoginServer.getActiveBaneQuery(player)) {
|
||||
Logger.info("Character " + player.getName() + " has unresolved bane");
|
||||
this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Player has unresolved bane.", origin);
|
||||
return;
|
||||
}
|
||||
|
||||
player.getAccount().characterMap.remove(player.getObjectUUID());
|
||||
player.deactivateCharacter();
|
||||
|
||||
// TODO Delete Equipment
|
||||
// Resend Character Select Screen.
|
||||
this.sendCharacterSelectScreen(session);
|
||||
|
||||
} catch (Exception e) {
|
||||
Logger.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void RequestGameServer(GameServerIPRequestMsg gameServerIPRequestMessage, ClientConnection conn) {
|
||||
|
||||
Session session;
|
||||
PlayerCharacter player;
|
||||
|
||||
session = SessionManager.getSession(conn);
|
||||
player = (PlayerCharacter) DbManager.getObject(GameObjectType.PlayerCharacter, gameServerIPRequestMessage.getCharacterUUID());
|
||||
|
||||
if (player == null) {
|
||||
Logger.info("Unable to find character ID " + gameServerIPRequestMessage.getCharacterUUID());
|
||||
this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "PlayerCharacter lookup failed in .RequestGameServer().", conn);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!CSSession.updateCrossServerSession(ByteUtils.byteArrayToSafeStringHex(conn.getSecretKeyBytes()), gameServerIPRequestMessage.getCharacterUUID())) {
|
||||
Logger.info("Failed to update Cross server session, Kicking to Login for Character " + player.getObjectUUID());
|
||||
this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Failed to update Session Information", conn);
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.info("Failed to update Cross server session, Kicking to Login for Character " + player.getObjectUUID());
|
||||
Logger.error(e);
|
||||
}
|
||||
|
||||
// Set the last character used.
|
||||
Account account = session.getAccount();
|
||||
account.setLastCharIDUsed(gameServerIPRequestMessage.getCharacterUUID());
|
||||
|
||||
GameServerIPResponseMsg gsiprm = new GameServerIPResponseMsg();
|
||||
|
||||
if (!conn.sendMsg(gsiprm)) {
|
||||
Logger.error("Failed to send message");
|
||||
this.KickToLogin(MBServerStatics.LOGINERROR_UNABLE_TO_LOGIN, "Unable to send GameServerIPResponseMsg to client.", conn);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,861 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2022
|
||||
// www.magicbane.com
|
||||
|
||||
|
||||
|
||||
package engine.server.world;
|
||||
|
||||
import engine.Enum;
|
||||
import engine.Enum.BuildingGroup;
|
||||
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.ai.MobileFSMManager;
|
||||
import engine.db.archive.DataWarehouse;
|
||||
import engine.exception.MsgSendException;
|
||||
import engine.gameManager.*;
|
||||
import engine.job.JobContainer;
|
||||
import engine.job.JobScheduler;
|
||||
import engine.jobs.LogoutCharacterJob;
|
||||
import engine.jobs.MineActiveJob;
|
||||
import engine.loot.LootManager;
|
||||
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.InetAddress;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
|
||||
import static engine.gameManager.SimulationManager.SERVERHEARTBEAT;
|
||||
import static java.lang.System.exit;
|
||||
|
||||
public class WorldServer {
|
||||
|
||||
private static LocalDateTime bootTime = LocalDateTime.now();
|
||||
private static long lastHZChange = System.currentTimeMillis();
|
||||
public boolean isRunning = false;
|
||||
|
||||
// Member variable declaration
|
||||
|
||||
public static HashMap<Integer,HashMap<Integer,ArrayList<Integer>>> ZoneFidelityMobRunes = new HashMap<>();
|
||||
|
||||
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 long getLastHZChange() {
|
||||
return lastHZChange;
|
||||
}
|
||||
|
||||
public static void setLastHZChange(long lastChange) {
|
||||
lastHZChange = lastChange;
|
||||
}
|
||||
|
||||
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 void shutdown() {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
Logger.info("Magicbane network config: " + 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("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.CreateErrantGuild();
|
||||
|
||||
Logger.info("Initializing PowersManager.");
|
||||
// activate powers manager
|
||||
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 MeshBounds");
|
||||
MeshBounds.InitializeBuildingBounds();
|
||||
|
||||
// Load ItemBases
|
||||
Logger.info("Loading ItemBases");
|
||||
ItemBase.loadAllItemBases();
|
||||
|
||||
Logger.info("Loading PromotionClasses");
|
||||
DbManager.PromotionQueries.GET_ALL_PROMOTIONS();
|
||||
|
||||
Logger.info("Loading NPC and Mob Equipment Sets");
|
||||
EquipmentSetEntry.LoadAllEquipmentSets();
|
||||
|
||||
Logger.info("Loading Gold Loot for Mobbases");
|
||||
MobbaseGoldEntry.LoadMobbaseGold();
|
||||
|
||||
Logger.info("Loading fidelity mob runes.");
|
||||
DbManager.MobQueries.LOAD_RUNES_FOR_FIDELITY_MOBS();
|
||||
|
||||
//load lootTable
|
||||
Logger.info("Loading Loot Tables");
|
||||
LootTable.populateLootTables();
|
||||
|
||||
// Load new loot system
|
||||
Logger.info("Loading SuperLoot Tables");
|
||||
LootManager.loadLootData();
|
||||
RuneBaseAttribute.LoadAllAttributes();
|
||||
RuneBase.LoadAllRuneBases();
|
||||
BaseClass.LoadAllBaseClasses();
|
||||
Race.loadAllRaces();
|
||||
RuneBaseEffect.LoadRuneBaseEffects();
|
||||
|
||||
Logger.info("Loading MobBases.");
|
||||
DbManager.MobBaseQueries.GET_ALL_MOBBASES();
|
||||
|
||||
//load item enchantment values
|
||||
DbManager.LootQueries.LOAD_ENCHANT_VALUES();
|
||||
|
||||
//initialize realms
|
||||
Logger.info("Loading Realms");
|
||||
Realm.loadAllRealms();
|
||||
|
||||
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 Spaital Hash");
|
||||
RealmMap.loadRealmImageMap();
|
||||
|
||||
DbManager.MobBaseQueries.SET_AI_DEFAULTS();
|
||||
|
||||
Logger.info("Loading blueprint data.");
|
||||
StaticColliders.loadAllStaticColliders();
|
||||
BuildingRegions.loadAllStaticColliders();
|
||||
Blueprint.loadAllDoorNumbers();
|
||||
Blueprint.loadAllBlueprints();
|
||||
|
||||
Logger.info("Loading Special Loot For Mobs");
|
||||
DbManager.SpecialLootQueries.GenerateSpecialLoot();
|
||||
|
||||
Logger.info("Initializing Heightmap data");
|
||||
HeightMap.loadAlHeightMaps();
|
||||
|
||||
Logger.info("Loading Race data");
|
||||
Enum.RaceType.initRaceTypeTables();
|
||||
Race.loadAllRaces();
|
||||
|
||||
Logger.info("Loading building mountpoint data.");
|
||||
BuildingLocation.loadAllLocations();
|
||||
|
||||
// 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 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");
|
||||
|
||||
try{
|
||||
Realm.configureAllRealms();
|
||||
}catch(Exception e){
|
||||
Logger.error( e.getMessage());
|
||||
}
|
||||
|
||||
Logger.info("Loading Mine data.");
|
||||
//DbManager.MineQueries.syncMineWindowsWithToday();
|
||||
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 Pirate Names.");
|
||||
NPC.loadAllPirateNames();
|
||||
|
||||
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();
|
||||
|
||||
if (ZoneManager.getHotZone() != null)
|
||||
WorldServer.setLastHZChange(System.currentTimeMillis());
|
||||
|
||||
//Start Mines.
|
||||
|
||||
MineActiveJob maj = new MineActiveJob();
|
||||
maj.run();
|
||||
|
||||
Logger.info("Starting Mobile AI FSM");
|
||||
MobileFSMManager.getInstance();
|
||||
|
||||
|
||||
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 = MBServerStatics.worldAccessLevel;
|
||||
Logger.info("Default access level set to " + MBServerStatics.accessLevel);
|
||||
|
||||
Logger.info("Initializing Network");
|
||||
Network.init();
|
||||
|
||||
Logger.info("Initializing Client Connection Manager");
|
||||
initClientConnectionManager();
|
||||
|
||||
Logger.info("Starting message pumps");
|
||||
DispatchMessage.startMessagePump();
|
||||
|
||||
// Run maintenance
|
||||
MaintenanceManager.dailyMaintenance();
|
||||
|
||||
// Disabled but kept in case of emergency
|
||||
Logger.info("Starting Orphan Item Purge");
|
||||
PurgeOprhans.startPurgeThread();
|
||||
|
||||
// Calculate bootstrap time and rest boot time to current time.
|
||||
java.time.Duration bootDuration = java.time.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();
|
||||
LootTable.initialized = true;
|
||||
|
||||
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.configureDatabaseLayer();
|
||||
|
||||
} 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(MBServerStatics.worldUUID);
|
||||
|
||||
if (rootParent.isEmpty()) {
|
||||
Logger.error("populateWorldBuildings: No entries found in worldMap for parent " + MBServerStatics.worldUUID);
|
||||
return;
|
||||
}
|
||||
|
||||
//Set sea floor object for server
|
||||
Zone seaFloor = rootParent.get(0);
|
||||
seaFloor.setParent(null);
|
||||
ZoneManager.setSeaFloor(seaFloor);
|
||||
|
||||
// zoneManager.addZone(seaFloor.getLoadNum(), seaFloor); <- DIE IN A FUCKING CAR FIRE BONUS CODE LIKE THIS SUCKS FUCKING DICK
|
||||
|
||||
rootParent.addAll(DbManager.ZoneQueries.GET_ALL_NODES(seaFloor));
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
for (Zone zone : rootParent) {
|
||||
|
||||
try {
|
||||
ZoneManager.addZone(zone.getLoadNum(), zone);
|
||||
|
||||
try{
|
||||
zone.generateWorldAltitude();
|
||||
}catch(Exception e){
|
||||
Logger.error( e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
//Handle Buildings
|
||||
|
||||
ArrayList<Building> bList;
|
||||
bList = DbManager.BuildingQueries.GET_ALL_BUILDINGS_FOR_ZONE(zone);
|
||||
|
||||
for (Building b : bList) {
|
||||
|
||||
try {
|
||||
b.setObjectTypeMask(MBServerStatics.MASK_BUILDING);
|
||||
b.setLoc(b.getLoc());
|
||||
} catch (Exception e) {
|
||||
Logger.error( b.getObjectUUID() + " returned an Error Message :" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
//Handle Mobs
|
||||
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());
|
||||
m.setParentZone(zone);
|
||||
|
||||
//ADD GUARDS HERE.
|
||||
if (m.getBuilding() != null && m.getBuilding().getBlueprint() != null && m.getBuilding().getBlueprint().getBuildingGroup() == BuildingGroup.BARRACK)
|
||||
DbManager.MobQueries.LOAD_PATROL_POINTS(m);
|
||||
}
|
||||
|
||||
//Handle npc's
|
||||
ArrayList<NPC> npcs;
|
||||
|
||||
// Ignore npc's 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) {
|
||||
|
||||
try {
|
||||
n.setObjectTypeMask(MBServerStatics.MASK_NPC);
|
||||
n.setLoc(n.getLoc());
|
||||
n.setParentZone(zone);
|
||||
} catch (Exception e) {
|
||||
Logger.error( n.getObjectUUID() + " returned an Error Message :" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
//Handle cities
|
||||
|
||||
City.loadCities(zone);
|
||||
ZoneManager.populateWorldZones(zone);
|
||||
|
||||
} catch (Exception e) {
|
||||
Logger.info(e.getMessage() + zone.getName() + ' ' + zone.getObjectUUID());
|
||||
}
|
||||
}
|
||||
|
||||
Logger.info("time to load: " + (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 pc = SessionManager.getPlayerCharacter(
|
||||
origin);
|
||||
|
||||
if (pc == null)
|
||||
// TODO log this
|
||||
return;
|
||||
|
||||
//cancel any trade
|
||||
if (pc.getCharItemManager() != null)
|
||||
pc.getCharItemManager().endTrade(true);
|
||||
|
||||
// logout
|
||||
long delta = MBServerStatics.LOGOUT_TIMER_MS;
|
||||
|
||||
if (System.currentTimeMillis() - pc.getTimeStamp("LastCombatPlayer") < 60000) {
|
||||
delta = 60000;
|
||||
|
||||
}
|
||||
pc.stopMovement(pc.getLoc());
|
||||
UpdateStateMsg updateStateMsg = new UpdateStateMsg();
|
||||
updateStateMsg.setPlayer(pc);
|
||||
|
||||
updateStateMsg.setActivity(5);
|
||||
DispatchMessage.dispatchMsgToInterestArea(pc, updateStateMsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);
|
||||
|
||||
if (pc.getRegion() != null)
|
||||
if (PlayerCharacter.CanBindToBuilding(pc, pc.getRegion().parentBuildingID))
|
||||
pc.bindBuilding = pc.getRegion().parentBuildingID;
|
||||
else
|
||||
pc.bindBuilding = 0;
|
||||
|
||||
pc.getLoadedObjects().clear();
|
||||
pc.getLoadedStaticObjects().clear();
|
||||
|
||||
LogoutCharacterJob logoutJob = new LogoutCharacterJob(pc, this);
|
||||
JobContainer jc = JobScheduler.getInstance().scheduleJob(logoutJob,
|
||||
System.currentTimeMillis() + delta);
|
||||
pc.getTimers().put("Logout", jc);
|
||||
pc.getTimestamps().put("logout", System.currentTimeMillis());
|
||||
|
||||
//send update to friends that you are logged off.
|
||||
|
||||
PlayerFriends.SendFriendsStatus(pc,false);
|
||||
|
||||
}
|
||||
|
||||
public void logoutCharacter(PlayerCharacter player) {
|
||||
|
||||
if (player == null) {
|
||||
Logger.error("Unable to find PlayerCharacter to logout");
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
player.dismissNecroPets();
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void writePopulationFile() {
|
||||
|
||||
int population = SessionManager.getActivePlayerCharacterCount();
|
||||
try {
|
||||
|
||||
|
||||
File populationFile = new File(MBServerStatics.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 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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user