diff --git a/README.md b/README.md index 51ae40c1fb..04cc30f580 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,8 @@ Hamachi is optional, though. You don't have to install Hamachi if you want to ma Set the "MapleSolaxia" folder on a place of your preference. It is recommended for use "C:\Nexon\MapleSolaxia". -Setting up the SQL: open MySQL Query Browser, and define these parameters at startup and click OK: +Setting up the SQL: open MySQL Query Browser, define these parameters at startup and click OK. + Server Host: localhost Port: 3306 Username: root Now it must be done CAREFULLY: @@ -80,7 +81,7 @@ Now it is OPTIONAL, you don't need to run it if you don't want, as it will simpl 3. File -> Open Script... -> Browse for "C:\Nexon\MapleSolaxia\sql" -> db_shopupdate.sql, and execute it. -At the end of the execution of these SQLs, you should have installed a database schema named "maplesolaxia". REGISTER YOUR FIRST ACCOUNT to be used in-game by creating manually a entry on the table "accounts" at that database with a login and a password. +At the end of the execution of these SQLs, you should have installed a database schema named "maplesolaxia". REGISTER YOUR FIRST ACCOUNT to be used in-game by creating manually an entry on the table "accounts" at that database with a login and a password. Configure the IP you want to use for your MapleStory server in "configuration.ini" file, or set it as "localhost" if you want to run it only on your machine. Alternatively, you can use the IP given by Hamachi to use on a Hamachi network, or you can use a non-Hamachi method of port-forwarding. Neither will be approached here. diff --git a/dist/MapleSolaxia.jar b/dist/MapleSolaxia.jar index ab8790bc3b..e23f7eec1d 100644 Binary files a/dist/MapleSolaxia.jar and b/dist/MapleSolaxia.jar differ diff --git a/docs/feature_list.txt b/docs/feature_list.txt index 33c3247ff6..1a28ca525e 100644 --- a/docs/feature_list.txt +++ b/docs/feature_list.txt @@ -1,6 +1,6 @@ ========== MapleSolaxiaV2 ========== Credits: -Ronan - Freelance Developer +Ronan - Developer Vcoc - Freelance Developer --------------------------- @@ -81,17 +81,27 @@ Server potentials: * Custom jail system (needs provided custom wz). * Delete Character 100% (requires ENABLE_PIC activated). * Autosaver (periodically saves on DB current state of every player in-game). -* Both fixed and randomized versions of HP/MP growth rate abailable, regarding player job (enable one at ServerConstants). Placeholder for HP/MP washing feature. +* Both fixed and randomized versions of HP/MP growth rate available, regarding player job (enable one at ServerConstants). Placeholder for HP/MP washing feature. Admin/GM commands: * Server commands layered by GM levels. * Spawn Zakum/Horntail/Pinkbean 100%. * New commands. +External tools: +* MapleArrowFetcher - Updates min/max quantity dropped on all arrows drop data, calculations based on mob level and whether it's a boss or not. +* MapleCouponInstaller - Retrieves coupon info from the WZ and makes a SQL table with it. The server will use that table to gather info regarding rates and intervals. +* MapleIdRetriever - Two behaviors: generates a SQL table with relation (id, name) of the handbook given as input. Given a file with names, outputs a file with ids. +* MapleMesoFetcher - Creates meso drop data for mobs with more than 4 items (thus overworld mobs), calculations based on mob level and whether it's a boss or not. +* MapleQuestItemFetcher - Searches the SQL tables and project files and reports in all relevant data regarding missing/erroneous quest items. +* MobBookIndexer - Generates a SQL table with all relations of cardid and mobid present in the mob book. +* MobBookUpdate - Generates a wz.xml that is a copy of the original MonsterBook.wz.xml, except it updates the drop data info in the book with those currently on DB. + Project: * Organized project code. * Highly configurable server (see all server flags at ServerConstants). * Fixed/added some missing packets for MoveEnvironment, summons and others. * Reviewed many Java object aspects that needed concurrency protection. +* Heavily reviewed future task management inside the project. Way less trivial schedules are spawned now, relieving task overload on the TimerManager. --------------------------- \ No newline at end of file diff --git a/docs/mychanges_ptbr.txt b/docs/mychanges_ptbr.txt index 1ffcddf0c2..5d165adbbe 100644 --- a/docs/mychanges_ptbr.txt +++ b/docs/mychanges_ptbr.txt @@ -650,4 +650,13 @@ Implementado nova ferramenta: MapleQuestItemFetcher. Ela vasculha a DB e os XMLs 05 Novembro 2017, Adicionado sistema de level cap para jobs correntes. -Revisto acesso concorrente sobre o componente EM da classe EIM. \ No newline at end of file +Revisto acesso concorrente sobre o componente EM da classe EIM. + +07 Novembro 2017, +Refatorado esquema de schedules por toda a source, diminuindo drasticamente as chamadas ao TimerManager. +Refatorado algumas chamadas à DB, busca somente aquilo que é necessário. + +08 - 09 Novembro 2017, +Adicionado packet para extra slot pendant. +Corrigido possível bug em MapleMapFactory. +Modificado todas as DB tables agora utilizando InnoDB (ganhos do MyISAM em contraste se tornou ínfimo, para casos críticos). \ No newline at end of file diff --git a/sql/db_database.sql b/sql/db_database.sql index dac79e818d..6df9a9d3f5 100644 --- a/sql/db_database.sql +++ b/sql/db_database.sql @@ -206,7 +206,7 @@ CREATE TABLE IF NOT EXISTS `temp_data` ( `chance` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`dropperid`, `itemid`), KEY `mobid` (`dropperid`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=0 ; +) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=0 ; INSERT IGNORE INTO `temp_data` (`id`, `dropperid`, `itemid`, `minimum_quantity`, `maximum_quantity`, `questid`, `chance`) VALUES (1, 9400121, 4000138, 1, 1, 0, 600000), @@ -15929,7 +15929,7 @@ CREATE TABLE IF NOT EXISTS `monsterbook` ( `charid` int(11) unsigned NOT NULL, `cardid` int(11) NOT NULL, `level` int(1) DEFAULT '1' -) ENGINE=MyISAM DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=latin1; CREATE TABLE IF NOT EXISTS `monstercarddata` ( `id` int(11) NOT NULL AUTO_INCREMENT, @@ -15937,7 +15937,7 @@ CREATE TABLE IF NOT EXISTS `monstercarddata` ( `mobid` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) USING BTREE, UNIQUE KEY `id` (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=309 ; +) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=309 ; INSERT INTO `monstercarddata` (`id`, `cardid`, `mobid`) VALUES (1, 2380000, 100100), @@ -16313,7 +16313,7 @@ CREATE TABLE IF NOT EXISTS `nxcode` ( `type` int(11) NOT NULL DEFAULT '0', `item` int(11) NOT NULL DEFAULT '10000', PRIMARY KEY (`code`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1; +) ENGINE=InnoDB DEFAULT CHARSET=latin1; CREATE TABLE IF NOT EXISTS `nxcoupons` ( `id` int(11) NOT NULL AUTO_INCREMENT, @@ -16418,7 +16418,7 @@ CREATE TABLE IF NOT EXISTS `questactions` ( `status` int(11) NOT NULL DEFAULT '0', `data` blob NOT NULL, PRIMARY KEY (`questactionid`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; +) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; CREATE TABLE IF NOT EXISTS `questprogress` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, @@ -16434,7 +16434,7 @@ CREATE TABLE IF NOT EXISTS `questrequirements` ( `status` int(11) NOT NULL DEFAULT '0', `data` blob NOT NULL, PRIMARY KEY (`questrequirementid`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; +) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; CREATE TABLE IF NOT EXISTS `queststatus` ( `queststatusid` int(10) unsigned NOT NULL AUTO_INCREMENT, @@ -16456,7 +16456,7 @@ CREATE TABLE IF NOT EXISTS `reactordrops` ( `questid` int(5) NOT NULL DEFAULT '-1', PRIMARY KEY (`reactordropid`), KEY `reactorid` (`reactorid`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1 PACK_KEYS=1 AUTO_INCREMENT=841 ; +) ENGINE=InnoDB DEFAULT CHARSET=latin1 PACK_KEYS=1 AUTO_INCREMENT=841 ; INSERT INTO `reactordrops` (`reactordropid`, `reactorid`, `itemid`, `chance`, `questid`) VALUES (1, 2001, 4031161, 1, 1008), @@ -17339,7 +17339,7 @@ CREATE TABLE IF NOT EXISTS `shopitems` ( `pitch` int(11) NOT NULL DEFAULT '0', `position` int(11) NOT NULL COMMENT 'sort is an arbitrary field designed to give leeway when modifying shops. The lowest number is 104 and it increments by 4 for each item to allow decent space for swapping/inserting/removing items.', PRIMARY KEY (`shopitemid`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=20047 ; +) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=20047 ; INSERT INTO `shopitems` (`shopitemid`, `shopid`, `itemid`, `price`, `pitch`, `position`) VALUES (1, 11000, 1332005, 500, 0, 104), @@ -20967,7 +20967,7 @@ CREATE TABLE IF NOT EXISTS `shops` ( `shopid` int(10) unsigned NOT NULL AUTO_INCREMENT, `npcid` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`shopid`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=10000000 ; +) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=10000000 ; INSERT INTO `shops` (`shopid`, `npcid`) VALUES (11000, 11000), @@ -21256,7 +21256,7 @@ CREATE TABLE IF NOT EXISTS `skillmacros` ( `name` varchar(13) DEFAULT NULL, `shout` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; +) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; CREATE TABLE IF NOT EXISTS `skills` ( `id` int(11) NOT NULL AUTO_INCREMENT, diff --git a/sql/db_drops.sql b/sql/db_drops.sql index 47759a60f3..d309ad2ff6 100644 --- a/sql/db_drops.sql +++ b/sql/db_drops.sql @@ -19885,7 +19885,7 @@ USE `maplesolaxia`; PRIMARY KEY (`id`), UNIQUE KEY (`dropperid`, `itemid`), KEY `mobid` (`dropperid`) - ) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; + ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; #pass (sorted) data from one table to another INSERT INTO drop_data (dropperid, itemid, minimum_quantity, maximum_quantity, questid, chance) @@ -21223,7 +21223,7 @@ USE `maplesolaxia`; `mobid` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) USING BTREE, UNIQUE KEY `id` (`id`) - ) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; + ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; INSERT INTO `monstercarddata` (`cardid`, `mobid`) (SELECT itemid, min(dropperid) FROM drop_data where itemid>=2380000 and itemid<2390000 group by itemid); diff --git a/src/client/MapleCharacter.java b/src/client/MapleCharacter.java index 886cce135c..7dae8d9148 100644 --- a/src/client/MapleCharacter.java +++ b/src/client/MapleCharacter.java @@ -257,6 +257,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { private Map coolDowns = new LinkedHashMap<>(); private EnumMap diseases = new EnumMap<>(MapleDisease.class); private Map doors = new LinkedHashMap<>(); + private Map questExpirations = new LinkedHashMap<>(); private ScheduledFuture dragonBloodSchedule; private ScheduledFuture hpDecreaseTask; private ScheduledFuture beholderHealingSchedule, beholderBuffSchedule, berserkSchedule; @@ -264,14 +265,14 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { private ScheduledFuture buffExpireTask = null; private ScheduledFuture itemExpireTask = null; private ScheduledFuture diseaseExpireTask = null; + private ScheduledFuture questExpireTask = null; private ScheduledFuture recoveryTask = null; private ScheduledFuture extraRecoveryTask = null; private ScheduledFuture chairRecoveryTask = null; private ScheduledFuture pendantOfSpirit = null; //1122017 - private List> timers = new ArrayList<>(); private Lock chrLock = new ReentrantLock(true); private Lock effLock = new ReentrantLock(true); - private Lock petLock = new ReentrantLock(true); + private Lock petLock = new ReentrantLock(true); // for quest tasks as well private Lock prtLock = new ReentrantLock(); private Map> excluded = new LinkedHashMap<>(); private Set excludedItems = new LinkedHashSet<>(); @@ -2088,13 +2089,15 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { addHP(-getMap().getHPDec()); lastHpDec = System.currentTimeMillis(); } - - hpDecreaseTask = TimerManager.getInstance().schedule(new Runnable() { + } + + private void startHpDecreaseTask(long lastHpTask) { + hpDecreaseTask = TimerManager.getInstance().register(new Runnable() { @Override public void run() { doHurtHp(); } - }, 10000); + }, 10000, 10000 - lastHpTask); } public void resetHpDecreaseTask() { @@ -2103,16 +2106,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { } long lastHpTask = System.currentTimeMillis() - lastHpDec; - if(lastHpTask >= 10000) { - doHurtHp(); - } else { - hpDecreaseTask = TimerManager.getInstance().schedule(new Runnable() { - @Override - public void run() { - doHurtHp(); - } - }, 10000 - lastHpTask); - } + startHpDecreaseTask((lastHpTask > 10000) ? 10000 : lastHpTask); } public void dropMessage(String message) { @@ -2127,10 +2121,6 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { return nf.format(MapleGuild.CHANGE_EMBLEM_COST); } - public List> getTimers() { - return timers; - } - private void enforceMaxHpMp() { List> stats = new ArrayList<>(2); if (getMp() > getCurrentMaxMp()) { @@ -7221,7 +7211,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { public void showNote() { try { Connection con = DatabaseConnection.getConnection(); - try (PreparedStatement ps = con.prepareStatement("SELECT * FROM notes WHERE `to`=? AND `deleted` = 0", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE)) { + try (PreparedStatement ps = con.prepareStatement("SELECT * FROM notes WHERE `to` = ? AND `deleted` = 0", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE)) { ps.setString(1, this.getName()); try (ResultSet rs = ps.executeQuery()) { rs.last(); @@ -7466,15 +7456,83 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { updateQuest(newStatus); } - public void questTimeLimit(final MapleQuest quest, int seconds) { - ScheduledFuture sf = TimerManager.getInstance().schedule(new Runnable() { - @Override - public void run() { - expireQuest(quest); + public void cancelQuestExpirationTask() { + petLock.lock(); + try { + if (questExpireTask != null) { + questExpireTask.cancel(false); + questExpireTask = null; } - }, seconds * 1000); + } finally { + petLock.unlock(); + } + } + + public void questExpirationTask() { + petLock.lock(); + try { + if(!questExpirations.isEmpty()) { + if(questExpireTask == null) { + questExpireTask = TimerManager.getInstance().register(new Runnable() { + @Override + public void run() { + runQuestExpireTask(); + } + }, 10 * 1000); + } + } + } finally { + petLock.unlock(); + } + } + + private void runQuestExpireTask() { + petLock.lock(); + try { + long timeNow = System.currentTimeMillis(); + List expireList = new LinkedList<>(); + + for(Entry qe : questExpirations.entrySet()) { + if(qe.getValue() <= timeNow) expireList.add(qe.getKey()); + } + + if(!expireList.isEmpty()) { + for(MapleQuest quest : expireList) { + expireQuest(quest); + questExpirations.remove(quest); + } + + if(questExpirations.isEmpty()) { + questExpireTask.cancel(false); + questExpireTask = null; + } + } + } finally { + petLock.unlock(); + } + } + + private void registerQuestExpire(MapleQuest quest, long time) { + petLock.lock(); + try { + if(questExpireTask == null) { + questExpireTask = TimerManager.getInstance().register(new Runnable() { + @Override + public void run() { + runQuestExpireTask(); + } + }, 10 * 1000); + } + + questExpirations.put(quest, System.currentTimeMillis() + time); + } finally { + petLock.unlock(); + } + } + + public void questTimeLimit(final MapleQuest quest, int seconds) { + registerQuestExpire(quest, seconds * 1000); announce(MaplePacketCreator.addQuestTimeLimit(quest.getId(), seconds * 1000)); - timers.add(sf); } public void questTimeLimit2(final MapleQuest quest, long expires) { @@ -7483,14 +7541,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { if(timeLeft <= 0) { expireQuest(quest); } else { - ScheduledFuture sf = TimerManager.getInstance().schedule(new Runnable() { - @Override - public void run() { - expireQuest(quest); - } - }, timeLeft); - - timers.add(sf); + registerQuestExpire(quest, timeLeft); } } @@ -7903,10 +7954,19 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { cancelSkillCooldownTask(); cancelExpirationTask(); - for (ScheduledFuture sf : timers) { - sf.cancel(false); + petLock.lock(); + try { + if (questExpireTask != null) { + questExpireTask.cancel(false); + questExpireTask = null; + + questExpirations.clear(); + questExpirations = null; + } + } finally { + petLock.unlock(); } - timers.clear(); + if (maplemount != null) { maplemount.empty(); maplemount = null; @@ -7921,7 +7981,6 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { family = null; client = null; map = null; - timers = null; } } diff --git a/src/client/MapleClient.java b/src/client/MapleClient.java index d7d1c4863e..6b3068888c 100644 --- a/src/client/MapleClient.java +++ b/src/client/MapleClient.java @@ -41,7 +41,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.ScheduledFuture; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -70,7 +69,6 @@ import scripting.quest.QuestActionManager; import scripting.quest.QuestScriptManager; import server.life.MapleMonster; import server.MapleTrade; -import server.TimerManager; import server.maps.*; import server.quest.MapleQuest; import tools.LogHelper; @@ -101,7 +99,6 @@ public class MapleClient { private int gmlevel; private Set macs = new HashSet<>(); private Map engines = new HashMap<>(); - private ScheduledFuture idleTask = null; private byte characterSlots = 3; private byte loginattempt = 0; private String pin = null; @@ -941,15 +938,13 @@ public class MapleClient { } private void clear() { //usable when defining client = null shortly after + Server.getInstance().unregisterLoginState(this); + this.accountName = null; this.macs = null; this.hwid = null; this.birthday = null; //this.engines = null; - if (this.idleTask != null) { - this.idleTask.cancel(true); - this.idleTask = null; - } this.player = null; this.receive = null; this.send = null; @@ -1005,25 +1000,17 @@ public class MapleClient { lastPong = System.currentTimeMillis(); } - public void sendPing() { - final long then = System.currentTimeMillis(); - announce(MaplePacketCreator.getPing()); - TimerManager.getInstance().schedule(new Runnable() { - - @Override - public void run() { - try { - if (lastPong < then) { - if (session != null && session.isConnected()) { - session.close(false); - } - } - } catch (NullPointerException e) { - e.printStackTrace(); - } - } - }, 15000); - } + public void testPing(long timeThen) { + try { + if (lastPong < timeThen) { + if (session != null && session.isConnected()) { + session.close(false); + } + } + } catch (NullPointerException e) { + e.printStackTrace(); + } + } public String getHWID() { return hwid; @@ -1053,14 +1040,6 @@ public class MapleClient { engines.remove(name); } - public ScheduledFuture getIdleTask() { - return idleTask; - } - - public void setIdleTask(ScheduledFuture idleTask) { - this.idleTask = idleTask; - } - public NPCConversationManager getCM() { return NPCScriptManager.getInstance().getCM(this); } @@ -1321,6 +1300,7 @@ public class MapleClient { player.cancelBuffExpireTask(); player.cancelDiseaseExpireTask(); player.cancelSkillCooldownTask(); + player.cancelQuestExpirationTask(); //Cancelling magicdoor? Nope //Cancelling mounts? Noty if (player.getBuffedValue(MapleBuffStat.PUPPET) != null) { diff --git a/src/client/command/Commands.java b/src/client/command/Commands.java index 55ba527837..fc5be29d05 100644 --- a/src/client/command/Commands.java +++ b/src/client/command/Commands.java @@ -349,6 +349,10 @@ public class Commands { break; case "staff": + player.yellowMessage("MapleSolaxiaV2 Staff"); + player.yellowMessage("Ronan - Developer"); + player.yellowMessage("Vcoc - Freelance Developer"); + player.yellowMessage(""); player.yellowMessage("MapleSolaxia Staff"); player.yellowMessage("Aria - Administrator"); player.yellowMessage("Twdtwd - Administrator"); @@ -358,9 +362,6 @@ public class Commands { player.yellowMessage("SourMjolk - Game Master"); player.yellowMessage("Kanade - Game Master"); player.yellowMessage("Kitsune - Game Master"); - player.yellowMessage("MapleSolaxiaV2 Staff"); - player.yellowMessage("Ronan - Freelance Developer"); - player.yellowMessage("Vcoc - Freelance Developer"); break; case "lastrestart": @@ -451,7 +452,7 @@ public class Commands { output += "#b" + data.getRight() + "#k is dropped by:\r\n"; try { Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("SELECT * FROM drop_data WHERE itemid = ? LIMIT 50"); + PreparedStatement ps = con.prepareStatement("SELECT dropperid FROM drop_data WHERE itemid = ? LIMIT 50"); ps.setInt(1, data.getLeft()); ResultSet rs = ps.executeQuery(); while(rs.next()) { @@ -1513,11 +1514,13 @@ public class Commands { case "reloadmap": MapleMap oldMap = c.getPlayer().getMap(); - MapleMap newMap = c.getChannelServer().getMapFactory().getMap(player.getMapId()); + MapleMap newMap = c.getChannelServer().getMapFactory().resetMap(player.getMapId()); + int callerid = c.getPlayer().getId(); + for (MapleCharacter ch : oldMap.getCharacters()) { ch.changeMap(newMap); + if(ch.getId() != callerid) ch.dropMessage("You have been relocated due to map reloading. Sorry for the inconvenience."); } - oldMap = null; newMap.respawn(); break; diff --git a/src/constants/ServerConstants.java b/src/constants/ServerConstants.java index 1542fbad63..1ff8120359 100644 --- a/src/constants/ServerConstants.java +++ b/src/constants/ServerConstants.java @@ -85,6 +85,7 @@ public class ServerConstants { //Dangling Items Configuration public static final int ITEM_EXPIRE_TIME = 3 * 60 * 1000; //Time before items start disappearing. Recommended to be set up to 3 minutes. public static final int ITEM_MONITOR_TIME = 5 * 60 * 1000; //Interval between item monitoring tasks on maps, which checks for dangling (null) item objects on the map item history. + public static final int ITEM_EXPIRE_CHECK = 10 * 1000; //Interval between item expiring tasks on maps, which checks and makes disappear expired items. public static final int ITEM_LIMIT_ON_MAP = 200; //Max number of items allowed on a map. //Some Gameplay Enhancing Configurations diff --git a/src/net/MapleServerHandler.java b/src/net/MapleServerHandler.java index 6abe58b09b..7107ade5a1 100644 --- a/src/net/MapleServerHandler.java +++ b/src/net/MapleServerHandler.java @@ -42,6 +42,15 @@ import tools.data.input.SeekableLittleEndianAccessor; import client.MapleClient; import constants.ServerConstants; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.ScheduledFuture; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import server.TimerManager; + public class MapleServerHandler extends IoHandlerAdapter { private PacketProcessor processor; @@ -49,16 +58,26 @@ public class MapleServerHandler extends IoHandlerAdapter { private static final SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy HH:mm"); private static AtomicLong sessionId = new AtomicLong(7777); + private Lock idleLock = new ReentrantLock(true); + private Lock tempLock = new ReentrantLock(true); + private Map idleSessions = new HashMap<>(100); + private Map tempIdleSessions = new HashMap<>(); + private ScheduledFuture idleManager = null; + public MapleServerHandler() { this.processor = PacketProcessor.getProcessor(-1, -1); + + idleManagerTask(); } public MapleServerHandler(int world, int channel) { this.processor = PacketProcessor.getProcessor(world, channel); this.world = world; this.channel = channel; + + idleManagerTask(); } - + @Override public void exceptionCaught(IoSession session, Throwable cause) throws Exception { System.out.println("disconnect by exception"); @@ -155,8 +174,70 @@ public class MapleServerHandler extends IoHandlerAdapter { public void sessionIdle(final IoSession session, final IdleStatus status) throws Exception { MapleClient client = (MapleClient) session.getAttribute(MapleClient.CLIENT_KEY); if (client != null) { - client.sendPing(); + registerIdleSession(client); } super.sessionIdle(session, status); } + + private void registerIdleSession(MapleClient c) { + if(idleLock.tryLock()) { + idleSessions.put(c, System.currentTimeMillis()); + c.announce(MaplePacketCreator.getPing()); + + idleLock.unlock(); + } else { + tempLock.lock(); + try { + tempIdleSessions.put(c, System.currentTimeMillis()); + c.announce(MaplePacketCreator.getPing()); + } finally { + tempLock.unlock(); + } + } + } + + private void manageIdleSessions() { + long timeNow = System.currentTimeMillis(); + long timeThen = timeNow - 15000; + + idleLock.lock(); + try { + for(Entry mc : idleSessions.entrySet()) { + if(timeNow - mc.getValue() >= 15000) { + mc.getKey().testPing(timeThen); + } + } + + idleSessions.clear(); + + if(!tempIdleSessions.isEmpty()) { + tempLock.lock(); + try { + for(Entry mc : tempIdleSessions.entrySet()) { + idleSessions.put(mc.getKey(), mc.getValue()); + } + + tempIdleSessions.clear(); + } finally { + tempLock.unlock(); + } + } + } finally { + idleLock.unlock(); + } + } + + private void idleManagerTask() { + this.idleManager = TimerManager.getInstance().register(new Runnable() { + @Override + public void run() { + manageIdleSessions(); + } + }, 10000); + } + + private void cancelIdleManagerTask() { + this.idleManager.cancel(false); + this.idleManager = null; + } } diff --git a/src/net/SendOpcode.java b/src/net/SendOpcode.java index 3ef6bbc6ff..ca6190921e 100644 --- a/src/net/SendOpcode.java +++ b/src/net/SendOpcode.java @@ -132,7 +132,7 @@ public enum SendOpcode { NOTIFY_LEVELUP(0x69), NOTIFY_MARRIAGE(0x6A), NOTIFY_JOB_CHANGE(0x6B), - //SET_BUY_EQUIP_EXT(0x6C),//lol? + //SET_BUY_EQUIP_EXT(0x6C), //probably extra pendant slot for other versions? MAPLE_TV_USE_RES(0x6D), //It's not blank, It's a popup nibs AVATAR_MEGAPHONE_RESULT(0x6E),//bot useless.. SET_AVATAR_MEGAPHONE(0x6F), @@ -145,7 +145,7 @@ public enum SendOpcode { NEW_YEAR_CARD_RES(0x76), RANDOM_MORPH_RES(0x77), CANCEL_NAME_CHANGE_BY_OTHER(0x78), - SET_BUY_EQUIP_EXT(0x79), + SET_EXTRA_PENDANT_SLOT(0x79), SCRIPT_PROGRESS_MESSAGE(0x7A), DATA_CRC_CHECK_FAILED(0x7B), MACRO_SYS_DATA_INIT(0x7C), diff --git a/src/net/server/Server.java b/src/net/server/Server.java index 4dedeb53d0..45d71ac0da 100644 --- a/src/net/server/Server.java +++ b/src/net/server/Server.java @@ -37,6 +37,7 @@ import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.concurrent.locks.ReentrantLock; @@ -63,6 +64,7 @@ import server.TimerManager; import tools.DatabaseConnection; import tools.FilePrinter; import tools.Pair; +import client.MapleClient; import client.MapleCharacter; import client.SkillFactory; import constants.ItemConstants; @@ -82,7 +84,8 @@ public class Server implements Runnable { private static Server instance = null; private List> worldRecommendedList = new LinkedList<>(); private final Map guilds = new LinkedHashMap<>(); - private final Lock shutdownLock = new ReentrantLock(); + private final Map inLoginState = new LinkedHashMap<>(); + private final Lock srvLock = new ReentrantLock(); private final PlayerBuffStorage buffStorage = new PlayerBuffStorage(); private final Map alliances = new LinkedHashMap<>(); private boolean online = false; @@ -279,6 +282,7 @@ public class Server implements Runnable { TimerManager tMan = TimerManager.getInstance(); tMan.start(); tMan.register(tMan.purge(), ServerConstants.PURGING_INTERVAL);//Purging ftw... + disconnectIdlesOnLoginTask(); long timeLeft = getTimeLeftForNextHour(); tMan.register(new CouponWorker(), ServerConstants.COUPON_INTERVAL, timeLeft); @@ -720,11 +724,64 @@ public class Server implements Runnable { return worlds; } + public void registerLoginState(MapleClient c) { + srvLock.lock(); + try { + inLoginState.put(c, System.currentTimeMillis() + 600000); + } finally { + srvLock.unlock(); + } + } + + public void unregisterLoginState(MapleClient c) { + srvLock.lock(); + try { + inLoginState.remove(c); + } finally { + srvLock.unlock(); + } + } + + private void disconnectIdlesOnLoginState() { + srvLock.lock(); + try { + List toDisconnect = new LinkedList<>(); + long timeNow = System.currentTimeMillis(); + + for(Entry mc : inLoginState.entrySet()) { + if(timeNow > mc.getValue()) { + toDisconnect.add(mc.getKey()); + } + } + + for(MapleClient c : toDisconnect) { + if(c.isLoggedIn()) { + c.disconnect(false, false); + } else { + c.getSession().close(true); + } + + inLoginState.remove(c); + } + } finally { + srvLock.unlock(); + } + } + + private void disconnectIdlesOnLoginTask() { + TimerManager.getInstance().register(new Runnable() { + @Override + public void run() { + disconnectIdlesOnLoginState(); + } + }, 300000); + } + public final Runnable shutdown(final boolean restart) {//no player should be online when trying to shutdown! return new Runnable() { @Override public void run() { - shutdownLock.lock(); + srvLock.lock(); try { System.out.println((restart ? "Restarting" : "Shutting down") + " the server!\r\n"); @@ -788,7 +845,7 @@ public class Server implements Runnable { getInstance().run();//DID I DO EVERYTHING?! D: } } finally { - shutdownLock.unlock(); + srvLock.unlock(); } } }; diff --git a/src/net/server/channel/handlers/BBSOperationHandler.java b/src/net/server/channel/handlers/BBSOperationHandler.java index 9e11ac86f4..f6ec2614c5 100644 --- a/src/net/server/channel/handlers/BBSOperationHandler.java +++ b/src/net/server/channel/handlers/BBSOperationHandler.java @@ -297,7 +297,7 @@ public final class BBSOperationHandler extends AbstractMaplePacketHandler { if (mc.getGuildId() <= 0) { return; } - Connection con = null; + Connection con; try { con = DatabaseConnection.getConnection(); PreparedStatement ps2; diff --git a/src/net/server/channel/handlers/EnterCashShopHandler.java b/src/net/server/channel/handlers/EnterCashShopHandler.java index b9a9817706..986cd30001 100644 --- a/src/net/server/channel/handlers/EnterCashShopHandler.java +++ b/src/net/server/channel/handlers/EnterCashShopHandler.java @@ -65,6 +65,7 @@ public class EnterCashShopHandler extends AbstractMaplePacketHandler { mc.cancelDiseaseExpireTask(); mc.cancelSkillCooldownTask(); mc.cancelExpirationTask(); + mc.cancelQuestExpirationTask(); c.announce(MaplePacketCreator.openCashShop(c, false)); c.announce(MaplePacketCreator.showCashInventory(c)); diff --git a/src/net/server/channel/handlers/EnterMTSHandler.java b/src/net/server/channel/handlers/EnterMTSHandler.java index e5fbad205b..f4072f1da7 100644 --- a/src/net/server/channel/handlers/EnterMTSHandler.java +++ b/src/net/server/channel/handlers/EnterMTSHandler.java @@ -74,6 +74,7 @@ public final class EnterMTSHandler extends AbstractMaplePacketHandler { chr.cancelDiseaseExpireTask(); chr.cancelSkillCooldownTask(); chr.cancelExpirationTask(); + chr.cancelQuestExpirationTask(); chr.saveToDB(); chr.getMap().removePlayer(c.getPlayer()); diff --git a/src/net/server/channel/handlers/MTSHandler.java b/src/net/server/channel/handlers/MTSHandler.java index d55fff8405..5e5b19b270 100644 --- a/src/net/server/channel/handlers/MTSHandler.java +++ b/src/net/server/channel/handlers/MTSHandler.java @@ -343,15 +343,15 @@ public final class MTSHandler extends AbstractMaplePacketHandler { } } else if (op == 9) { //add to cart int id = slea.readInt(); //id of the item - Connection con = null; + Connection con; try { con = DatabaseConnection.getConnection(); - try (PreparedStatement ps1 = con.prepareStatement("SELECT * FROM mts_items WHERE id = ? AND seller <> ?")) { - ps1.setInt(1, id);//Previene que agregues al cart tus propios items + try (PreparedStatement ps1 = con.prepareStatement("SELECT id FROM mts_items WHERE id = ? AND seller <> ?")) { + ps1.setInt(1, id); //Dummy query, prevents adding to cart self owned items ps1.setInt(2, c.getPlayer().getId()); try (ResultSet rs1 = ps1.executeQuery()) { if (rs1.next()) { - PreparedStatement ps = con.prepareStatement("SELECT * FROM mts_cart WHERE cid = ? AND itemid = ?"); + PreparedStatement ps = con.prepareStatement("SELECT cid FROM mts_cart WHERE cid = ? AND itemid = ?"); ps.setInt(1, c.getPlayer().getId()); ps.setInt(2, id); try (ResultSet rs = ps.executeQuery()) { diff --git a/src/net/server/channel/handlers/PlayerLoggedinHandler.java b/src/net/server/channel/handlers/PlayerLoggedinHandler.java index cfc9e1860c..79a4ad6f93 100644 --- a/src/net/server/channel/handlers/PlayerLoggedinHandler.java +++ b/src/net/server/channel/handlers/PlayerLoggedinHandler.java @@ -267,6 +267,7 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler { player.diseaseExpireTask(); player.skillCooldownTask(); player.expirationTask(); + player.questExpirationTask(); if (GameConstants.hasSPTable(player.getJob()) && player.getJob().getId() != 2001) { player.createDragon(); } diff --git a/src/net/server/guild/MapleAlliance.java b/src/net/server/guild/MapleAlliance.java index abbb6ed386..29814cddc8 100644 --- a/src/net/server/guild/MapleAlliance.java +++ b/src/net/server/guild/MapleAlliance.java @@ -208,7 +208,7 @@ public class MapleAlliance { ps.close(); rs.close(); - ps = con.prepareStatement("SELECT * FROM allianceguilds WHERE allianceid = ?"); + ps = con.prepareStatement("SELECT guildid FROM allianceguilds WHERE allianceid = ?"); ps.setInt(1, id); rs = ps.executeQuery(); diff --git a/src/net/server/handlers/KeepAliveHandler.java b/src/net/server/handlers/KeepAliveHandler.java index 7286c5e979..93f51774a2 100644 --- a/src/net/server/handlers/KeepAliveHandler.java +++ b/src/net/server/handlers/KeepAliveHandler.java @@ -26,10 +26,12 @@ import net.MaplePacketHandler; import tools.data.input.SeekableLittleEndianAccessor; public class KeepAliveHandler implements MaplePacketHandler { + @Override public void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { c.pongReceived(); } + @Override public boolean validateState(MapleClient c) { return true; } diff --git a/src/net/server/handlers/login/CharSelectedHandler.java b/src/net/server/handlers/login/CharSelectedHandler.java index dd60f262d1..1ed5cfaa23 100644 --- a/src/net/server/handlers/login/CharSelectedHandler.java +++ b/src/net/server/handlers/login/CharSelectedHandler.java @@ -41,9 +41,7 @@ public final class CharSelectedHandler extends AbstractMaplePacketHandler { return; } - if (c.getIdleTask() != null) { - c.getIdleTask().cancel(true); - } + Server.getInstance().unregisterLoginState(c); c.updateLoginState(MapleClient.LOGIN_SERVER_TRANSITION); String[] socket = Server.getInstance().getIP(c.getWorld(), c.getChannel()).split(":"); try { diff --git a/src/net/server/handlers/login/CharSelectedWithPicHandler.java b/src/net/server/handlers/login/CharSelectedWithPicHandler.java index 5420b1fd6f..1863f60498 100644 --- a/src/net/server/handlers/login/CharSelectedWithPicHandler.java +++ b/src/net/server/handlers/login/CharSelectedWithPicHandler.java @@ -26,9 +26,7 @@ public class CharSelectedWithPicHandler extends AbstractMaplePacketHandler { return; } if (c.checkPic(pic)) { - if (c.getIdleTask() != null) { - c.getIdleTask().cancel(true); - } + Server.getInstance().unregisterLoginState(c); c.updateLoginState(MapleClient.LOGIN_SERVER_TRANSITION); String[] socket = Server.getInstance().getIP(c.getWorld(), c.getChannel()).split(":"); diff --git a/src/net/server/handlers/login/LoginPasswordHandler.java b/src/net/server/handlers/login/LoginPasswordHandler.java index 0c3011820a..cb60064f7c 100644 --- a/src/net/server/handlers/login/LoginPasswordHandler.java +++ b/src/net/server/handlers/login/LoginPasswordHandler.java @@ -24,6 +24,7 @@ package net.server.handlers.login; import java.util.Calendar; import net.MaplePacketHandler; +import net.server.Server; import server.TimerManager; import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; @@ -73,12 +74,7 @@ public final class LoginPasswordHandler implements MaplePacketHandler { private static void login(MapleClient c){ c.announce(MaplePacketCreator.getAuthSuccess(c));//why the fk did I do c.getAccountName()? - final MapleClient client = c; - c.setIdleTask(TimerManager.getInstance().schedule(new Runnable() { - @Override - public void run() { - client.disconnect(false, false); - } - }, 600000)); + + Server.getInstance().registerLoginState(c); } } diff --git a/src/net/server/handlers/login/PickCharHandler.java b/src/net/server/handlers/login/PickCharHandler.java index de1ba7a4e2..3221bc0af1 100644 --- a/src/net/server/handlers/login/PickCharHandler.java +++ b/src/net/server/handlers/login/PickCharHandler.java @@ -49,9 +49,7 @@ public final class PickCharHandler extends AbstractMaplePacketHandler { e.printStackTrace(); c.setChannel(1); } - if (c.getIdleTask() != null) { - c.getIdleTask().cancel(true); - } + Server.getInstance().unregisterLoginState(c); c.updateLoginState(MapleClient.LOGIN_SERVER_TRANSITION); String[] socket = Server.getInstance().getIP(c.getWorld(), c.getChannel()).split(":"); try { diff --git a/src/net/server/handlers/login/RegisterPicHandler.java b/src/net/server/handlers/login/RegisterPicHandler.java index 7510658f48..75e16ac211 100644 --- a/src/net/server/handlers/login/RegisterPicHandler.java +++ b/src/net/server/handlers/login/RegisterPicHandler.java @@ -29,9 +29,7 @@ public final class RegisterPicHandler extends AbstractMaplePacketHandler { String pic = slea.readMapleAsciiString(); if (c.getPic() == null || c.getPic().equals("")) { c.setPic(pic); - if (c.getIdleTask() != null) { - c.getIdleTask().cancel(true); - } + Server.getInstance().unregisterLoginState(c); c.updateLoginState(MapleClient.LOGIN_SERVER_TRANSITION); String[] socket = Server.getInstance().getIP(c.getWorld(), c.getChannel()).split(":"); try { diff --git a/src/net/server/handlers/login/SetGenderHandler.java b/src/net/server/handlers/login/SetGenderHandler.java index 2273fd5d3f..8fdb27897e 100644 --- a/src/net/server/handlers/login/SetGenderHandler.java +++ b/src/net/server/handlers/login/SetGenderHandler.java @@ -24,7 +24,7 @@ package net.server.handlers.login; import client.MapleClient; import net.AbstractMaplePacketHandler; -import server.TimerManager; +import net.server.Server; import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; @@ -40,12 +40,8 @@ public class SetGenderHandler extends AbstractMaplePacketHandler { c.setGender(slea.readByte()); c.announce(MaplePacketCreator.getAuthSuccess(c)); final MapleClient client = c; - c.setIdleTask(TimerManager.getInstance().schedule(new Runnable() { - @Override - public void run() { - client.getSession().close(true); - } - }, 600000)); + + Server.getInstance().registerLoginState(c); } } diff --git a/src/net/server/handlers/login/ViewAllCharSelectedWithPicHandler.java b/src/net/server/handlers/login/ViewAllCharSelectedWithPicHandler.java index e35da2a132..e73b49b589 100644 --- a/src/net/server/handlers/login/ViewAllCharSelectedWithPicHandler.java +++ b/src/net/server/handlers/login/ViewAllCharSelectedWithPicHandler.java @@ -29,9 +29,7 @@ public class ViewAllCharSelectedWithPicHandler extends AbstractMaplePacketHandle return; } if (c.checkPic(pic)) { - if (c.getIdleTask() != null) { - c.getIdleTask().cancel(true); - } + Server.getInstance().unregisterLoginState(c); c.updateLoginState(MapleClient.LOGIN_SERVER_TRANSITION); String[] socket = Server.getInstance().getIP(c.getWorld(), c.getChannel()).split(":"); diff --git a/src/net/server/handlers/login/ViewAllPicRegisterHandler.java b/src/net/server/handlers/login/ViewAllPicRegisterHandler.java index 63557c0ad8..b8706b890d 100644 --- a/src/net/server/handlers/login/ViewAllPicRegisterHandler.java +++ b/src/net/server/handlers/login/ViewAllPicRegisterHandler.java @@ -28,9 +28,7 @@ public final class ViewAllPicRegisterHandler extends AbstractMaplePacketHandler slea.readMapleAsciiString(); String pic = slea.readMapleAsciiString(); c.setPic(pic); - if (c.getIdleTask() != null) { - c.getIdleTask().cancel(true); - } + Server.getInstance().unregisterLoginState(c); c.updateLoginState(MapleClient.LOGIN_SERVER_TRANSITION); String[] socket = Server.getInstance().getIP(c.getWorld(), channel).split(":"); try { diff --git a/src/server/MapleItemInformationProvider.java b/src/server/MapleItemInformationProvider.java index f8e6659dfc..c7e78eecd4 100644 --- a/src/server/MapleItemInformationProvider.java +++ b/src/server/MapleItemInformationProvider.java @@ -1508,7 +1508,7 @@ public class MapleItemInformationProvider { Connection con = null; try { con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("SELECT * FROM drop_data WHERE itemid = ? LIMIT 50"); + PreparedStatement ps = con.prepareStatement("SELECT dropperid FROM drop_data WHERE itemid = ? LIMIT 50"); ps.setInt(1, itemId); ResultSet rs = ps.executeQuery(); while(rs.next()) { diff --git a/src/server/MapleShop.java b/src/server/MapleShop.java index df111ce591..7975da9591 100644 --- a/src/server/MapleShop.java +++ b/src/server/MapleShop.java @@ -236,7 +236,7 @@ public class MapleShop { con.close(); return null; } - ps = con.prepareStatement("SELECT * FROM shopitems WHERE shopid = ? ORDER BY position DESC"); + ps = con.prepareStatement("SELECT itemid, price, pitch FROM shopitems WHERE shopid = ? ORDER BY position DESC"); ps.setInt(1, shopId); rs = ps.executeQuery(); List recharges = new ArrayList<>(rechargeableItems); diff --git a/src/server/life/MapleMonsterInformationProvider.java b/src/server/life/MapleMonsterInformationProvider.java index 3e4e3c8d06..5f9a8876f4 100644 --- a/src/server/life/MapleMonsterInformationProvider.java +++ b/src/server/life/MapleMonsterInformationProvider.java @@ -111,7 +111,7 @@ public class MapleMonsterInformationProvider { Connection con = null; try { con = DatabaseConnection.getConnection(); - ps = con.prepareStatement("SELECT * FROM drop_data WHERE dropperid = ?"); + ps = con.prepareStatement("SELECT itemid, chance, minimum_quantity, maximum_quantity, questid FROM drop_data WHERE dropperid = ?"); ps.setInt(1, monsterId); rs = ps.executeQuery(); diff --git a/src/server/maps/MapleMap.java b/src/server/maps/MapleMap.java index 000871ccd1..15d6b947b1 100644 --- a/src/server/maps/MapleMap.java +++ b/src/server/maps/MapleMap.java @@ -98,6 +98,7 @@ public class MapleMap { private Map portals = new HashMap<>(); private Map backgroundTypes = new HashMap<>(); private Map environment = new LinkedHashMap<>(); + private Map droppedItems = new LinkedHashMap<>(); private LinkedList> registeredDrops = new LinkedList<>(); private List areas = new ArrayList<>(); private MapleFootholdTree footholds = null; @@ -130,6 +131,7 @@ public class MapleMap { private int mobCapacity = -1; private ScheduledFuture mapMonitor = null; private ScheduledFuture itemMonitor = null; + private ScheduledFuture expireItemsTask = null; private short itemMonitorTimeout; private Pair timeMob = null; private short mobInterval = 5000; @@ -368,7 +370,6 @@ public class MapleMap { private void spawnRangedMapObject(MapleMapObject mapobject, DelayedPacketCreation packetbakery, SpawnCondition condition) { chrRLock.lock(); - try { int curOID = getUsableOID(); mapobject.setObjectId(curOID); @@ -585,6 +586,9 @@ public class MapleMap { try { itemMonitor.cancel(false); itemMonitor = null; + + expireItemsTask.cancel(false); + expireItemsTask = null; } finally { chrWLock.unlock(); } @@ -621,6 +625,13 @@ public class MapleMap { if(!registeredDrops.isEmpty()) cleanItemMonitor(); } }, ServerConstants.ITEM_MONITOR_TIME, ServerConstants.ITEM_MONITOR_TIME); + + expireItemsTask = TimerManager.getInstance().register(new Runnable() { + @Override + public void run() { + makeDisappearExpiredItemDrops(); + } + }, ServerConstants.ITEM_EXPIRE_CHECK, ServerConstants.ITEM_EXPIRE_CHECK); itemMonitorTimeout = 1; } finally { @@ -637,7 +648,7 @@ public class MapleMap { } } - private void registerItemDrop(MapleMapItem mdrop) { + private void instantiateItemDrop(MapleMapItem mdrop) { if(droppedItemCount.get() >= ServerConstants.ITEM_LIMIT_ON_MAP) { MapleMapObject mapobj; @@ -654,10 +665,9 @@ public class MapleMap { makeDisappearItemFromMap(mapobj); } - if(!everlast) TimerManager.getInstance().schedule(new ExpireMapItemJob(mdrop), ServerConstants.ITEM_EXPIRE_TIME); - objectWLock.lock(); try { + if(!everlast) registerItemDrop(mdrop); registeredDrops.add(new WeakReference<>((MapleMapObject) mdrop)); } finally { objectWLock.unlock(); @@ -666,6 +676,31 @@ public class MapleMap { droppedItemCount.incrementAndGet(); } + private void registerItemDrop(MapleMapItem mdrop) { + droppedItems.put(mdrop, System.currentTimeMillis() + ServerConstants.ITEM_EXPIRE_TIME); + } + + private void makeDisappearExpiredItemDrops() { + objectWLock.lock(); + try { + List toDisappear = new LinkedList<>(); + long timeNow = System.currentTimeMillis(); + + for(Entry it : droppedItems.entrySet()) { + if(it.getValue() < timeNow) { + toDisappear.add(it.getKey()); + } + } + + for(MapleMapItem mmi : toDisappear) { + makeDisappearItemFromMap(mmi); + droppedItems.remove(mmi); + } + } finally { + objectWLock.unlock(); + } + } + public void pickItemDrop(byte[] pickupPacket, MapleMapItem mdrop) { broadcastMessage(pickupPacket, mdrop.getPosition()); @@ -686,7 +721,7 @@ public class MapleMap { } }, null); - registerItemDrop(mdrop); + instantiateItemDrop(mdrop); activateItemReactors(mdrop, chr.getClient()); } @@ -702,7 +737,7 @@ public class MapleMap { } }, null); - registerItemDrop(mdrop); + instantiateItemDrop(mdrop); } public final void disappearingItemDrop(final MapleMapObject dropper, final MapleCharacter owner, final Item item, final Point pos) { @@ -1719,7 +1754,7 @@ public class MapleMap { }, null); broadcastMessage(MaplePacketCreator.dropItemFromMapObject(mdrop, dropper.getPosition(), droppos, (byte) 0)); - registerItemDrop(mdrop); + instantiateItemDrop(mdrop); activateItemReactors(mdrop, owner.getClient()); } @@ -2694,20 +2729,6 @@ public class MapleMap { } } - private class ExpireMapItemJob implements Runnable { - - private MapleMapItem mapitem; - - public ExpireMapItemJob(MapleMapItem mapitem) { - this.mapitem = mapitem; - } - - @Override - public void run() { - makeDisappearItemFromMap(mapitem); - } - } - private class ActivateItemReactor implements Runnable { private MapleMapItem mapitem; diff --git a/src/server/maps/MapleMapFactory.java b/src/server/maps/MapleMapFactory.java index ce700d55ae..556794dca9 100644 --- a/src/server/maps/MapleMapFactory.java +++ b/src/server/maps/MapleMapFactory.java @@ -69,6 +69,222 @@ public class MapleMapFactory { this.mapsWLock = rrwl.writeLock(); } + public MapleMap resetMap(int mapid) { + mapsWLock.lock(); + try { + maps.remove(Integer.valueOf(mapid)); + } finally { + mapsWLock.unlock(); + } + + return getMap(mapid); + } + + private synchronized MapleMap loadMapFromWz(int mapid, Integer omapid) { + MapleMap map; + + mapsRLock.lock(); + try { + map = maps.get(omapid); + } finally { + mapsRLock.unlock(); + } + + if (map != null) { + return map; + } + + String mapName = getMapName(mapid); + MapleData mapData = source.getData(mapName); + MapleData infoData = mapData.getChildByPath("info"); + + String link = MapleDataTool.getString(infoData.getChildByPath("link"), ""); + if (!link.equals("")) { //nexon made hundreds of dojo maps so to reduce the size they added links. + mapName = getMapName(Integer.parseInt(link)); + mapData = source.getData(mapName); + } + float monsterRate = 0; + MapleData mobRate = infoData.getChildByPath("mobRate"); + if (mobRate != null) { + monsterRate = ((Float) mobRate.getData()).floatValue(); + } + map = new MapleMap(mapid, world, channel, MapleDataTool.getInt("returnMap", infoData), monsterRate); + map.setEventInstance(event); + + String onFirstEnter = MapleDataTool.getString(infoData.getChildByPath("onFirstUserEnter"), String.valueOf(mapid)); + map.setOnFirstUserEnter(onFirstEnter.equals("") ? String.valueOf(mapid) : onFirstEnter); + + String onEnter = MapleDataTool.getString(infoData.getChildByPath("onUserEnter"), String.valueOf(mapid)); + map.setOnUserEnter(onEnter.equals("") ? String.valueOf(mapid) : onEnter); + + map.setFieldLimit(MapleDataTool.getInt(infoData.getChildByPath("fieldLimit"), 0)); + map.setMobInterval((short) MapleDataTool.getInt(infoData.getChildByPath("createMobInterval"), 5000)); + PortalFactory portalFactory = new PortalFactory(); + for (MapleData portal : mapData.getChildByPath("portal")) { + map.addPortal(portalFactory.makePortal(MapleDataTool.getInt(portal.getChildByPath("pt")), portal)); + } + MapleData timeMob = infoData.getChildByPath("timeMob"); + if (timeMob != null) { + map.timeMob(MapleDataTool.getInt(timeMob.getChildByPath("id")), + MapleDataTool.getString(timeMob.getChildByPath("message"))); + } + + int bounds[] = new int[4]; + bounds[0] = MapleDataTool.getInt(infoData.getChildByPath("VRTop")); + bounds[1] = MapleDataTool.getInt(infoData.getChildByPath("VRBottom")); + + if(bounds[0] == bounds[1]) { // old-style baked map + MapleData minimapData = mapData.getChildByPath("miniMap"); + if(minimapData != null) { + bounds[0] = MapleDataTool.getInt(minimapData.getChildByPath("centerX")) * -1; + bounds[1] = MapleDataTool.getInt(minimapData.getChildByPath("centerY")) * -1; + bounds[2] = MapleDataTool.getInt(minimapData.getChildByPath("height")); + bounds[3] = MapleDataTool.getInt(minimapData.getChildByPath("width")); + + map.setMapPointBoundings(bounds[0], bounds[1], bounds[2], bounds[3]); + } + } else { + bounds[2] = MapleDataTool.getInt(infoData.getChildByPath("VRLeft")); + bounds[3] = MapleDataTool.getInt(infoData.getChildByPath("VRRight")); + + map.setMapLineBoundings(bounds[0], bounds[1], bounds[2], bounds[3]); + } + + List allFootholds = new LinkedList<>(); + Point lBound = new Point(); + Point uBound = new Point(); + for (MapleData footRoot : mapData.getChildByPath("foothold")) { + for (MapleData footCat : footRoot) { + for (MapleData footHold : footCat) { + int x1 = MapleDataTool.getInt(footHold.getChildByPath("x1")); + int y1 = MapleDataTool.getInt(footHold.getChildByPath("y1")); + int x2 = MapleDataTool.getInt(footHold.getChildByPath("x2")); + int y2 = MapleDataTool.getInt(footHold.getChildByPath("y2")); + MapleFoothold fh = new MapleFoothold(new Point(x1, y1), new Point(x2, y2), Integer.parseInt(footHold.getName())); + fh.setPrev(MapleDataTool.getInt(footHold.getChildByPath("prev"))); + fh.setNext(MapleDataTool.getInt(footHold.getChildByPath("next"))); + if (fh.getX1() < lBound.x) { + lBound.x = fh.getX1(); + } + if (fh.getX2() > uBound.x) { + uBound.x = fh.getX2(); + } + if (fh.getY1() < lBound.y) { + lBound.y = fh.getY1(); + } + if (fh.getY2() > uBound.y) { + uBound.y = fh.getY2(); + } + allFootholds.add(fh); + } + } + } + MapleFootholdTree fTree = new MapleFootholdTree(lBound, uBound); + for (MapleFoothold fh : allFootholds) { + fTree.insert(fh); + } + map.setFootholds(fTree); + if (mapData.getChildByPath("area") != null) { + for (MapleData area : mapData.getChildByPath("area")) { + int x1 = MapleDataTool.getInt(area.getChildByPath("x1")); + int y1 = MapleDataTool.getInt(area.getChildByPath("y1")); + int x2 = MapleDataTool.getInt(area.getChildByPath("x2")); + int y2 = MapleDataTool.getInt(area.getChildByPath("y2")); + map.addMapleArea(new Rectangle(x1, y1, (x2 - x1), (y2 - y1))); + } + } + try { + Connection con = DatabaseConnection.getConnection(); + try (PreparedStatement ps = con.prepareStatement("SELECT * FROM playernpcs WHERE map = ?")) { + ps.setInt(1, omapid); + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + map.addMapObject(new PlayerNPCs(rs)); + } + } + } + + con.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + + for (MapleData life : mapData.getChildByPath("life")) { + String id = MapleDataTool.getString(life.getChildByPath("id")); + String type = MapleDataTool.getString(life.getChildByPath("type")); + AbstractLoadedMapleLife myLife = loadLife(life, id, type); + if (myLife instanceof MapleMonster) { + MapleMonster monster = (MapleMonster) myLife; + int mobTime = MapleDataTool.getInt("mobTime", life, 0); + int team = MapleDataTool.getInt("team", life, -1); + if (mobTime == -1) { //does not respawn, force spawn once + map.spawnMonster(monster); + } else { + map.addMonsterSpawn(monster, mobTime, team); + } + + //should the map be reseted, use allMonsterSpawn list of monsters to spawn them again + map.addAllMonsterSpawn(monster, mobTime, team); + } else { + map.addMapObject(myLife); + } + } + + if (mapData.getChildByPath("reactor") != null) { + for (MapleData reactor : mapData.getChildByPath("reactor")) { + String id = MapleDataTool.getString(reactor.getChildByPath("id")); + if (id != null) { + MapleReactor newReactor = loadReactor(reactor, id); + map.spawnReactor(newReactor); + } + } + } + try { + map.setMapName(MapleDataTool.getString("mapName", nameData.getChildByPath(getMapStringName(omapid)), "")); + map.setStreetName(MapleDataTool.getString("streetName", nameData.getChildByPath(getMapStringName(omapid)), "")); + } catch (Exception e) { + e.printStackTrace(); + System.err.println("Not found mapid " + omapid); + + map.setMapName(""); + map.setStreetName(""); + } + + map.setClock(mapData.getChildByPath("clock") != null); + map.setEverlast(infoData.getChildByPath("everlast") != null); + map.setTown(infoData.getChildByPath("town") != null); + map.setHPDec(MapleDataTool.getIntConvert("decHP", infoData, 0)); + map.setHPDecProtect(MapleDataTool.getIntConvert("protectItem", infoData, 0)); + map.setForcedReturnMap(MapleDataTool.getInt(infoData.getChildByPath("forcedReturn"), 999999999)); + map.setBoat(mapData.getChildByPath("shipObj") != null); + map.setTimeLimit(MapleDataTool.getIntConvert("timeLimit", infoData, -1)); + map.setFieldType(MapleDataTool.getIntConvert("fieldType", infoData, 0)); + map.setMobCapacity(MapleDataTool.getIntConvert("fixedMobCapacity", infoData, 500));//Is there a map that contains more than 500 mobs? + + HashMap backTypes = new HashMap<>(); + try { + for (MapleData layer : mapData.getChildByPath("back")) { // yolo + int layerNum = Integer.parseInt(layer.getName()); + int type = MapleDataTool.getInt(layer.getChildByPath("type"), 0); + + backTypes.put(layerNum, type); + } + } catch (Exception e) { + e.printStackTrace(); + // swallow cause I'm cool + } + map.setBackgroundTypes(backTypes); + + mapsWLock.lock(); + try { + maps.put(omapid, map); + } finally { + mapsWLock.unlock(); + } + + return map; + } + public MapleMap getMap(int mapid) { Integer omapid = Integer.valueOf(mapid); MapleMap map; @@ -80,211 +296,7 @@ public class MapleMapFactory { mapsRLock.unlock(); } - if (map == null) { - synchronized (this) { - mapsRLock.lock(); - try { - map = maps.get(omapid); - } finally { - mapsRLock.unlock(); - } - - if (map != null) { - return map; - } - String mapName = getMapName(mapid); - MapleData mapData = source.getData(mapName); - MapleData infoData = mapData.getChildByPath("info"); - - String link = MapleDataTool.getString(infoData.getChildByPath("link"), ""); - if (!link.equals("")) { //nexon made hundreds of dojo maps so to reduce the size they added links. - mapName = getMapName(Integer.parseInt(link)); - mapData = source.getData(mapName); - } - float monsterRate = 0; - MapleData mobRate = infoData.getChildByPath("mobRate"); - if (mobRate != null) { - monsterRate = ((Float) mobRate.getData()).floatValue(); - } - map = new MapleMap(mapid, world, channel, MapleDataTool.getInt("returnMap", infoData), monsterRate); - map.setEventInstance(event); - - String onFirstEnter = MapleDataTool.getString(infoData.getChildByPath("onFirstUserEnter"), String.valueOf(mapid)); - map.setOnFirstUserEnter(onFirstEnter.equals("") ? String.valueOf(mapid) : onFirstEnter); - - String onEnter = MapleDataTool.getString(infoData.getChildByPath("onUserEnter"), String.valueOf(mapid)); - map.setOnUserEnter(onEnter.equals("") ? String.valueOf(mapid) : onEnter); - - map.setFieldLimit(MapleDataTool.getInt(infoData.getChildByPath("fieldLimit"), 0)); - map.setMobInterval((short) MapleDataTool.getInt(infoData.getChildByPath("createMobInterval"), 5000)); - PortalFactory portalFactory = new PortalFactory(); - for (MapleData portal : mapData.getChildByPath("portal")) { - map.addPortal(portalFactory.makePortal(MapleDataTool.getInt(portal.getChildByPath("pt")), portal)); - } - MapleData timeMob = infoData.getChildByPath("timeMob"); - if (timeMob != null) { - map.timeMob(MapleDataTool.getInt(timeMob.getChildByPath("id")), - MapleDataTool.getString(timeMob.getChildByPath("message"))); - } - - int bounds[] = new int[4]; - bounds[0] = MapleDataTool.getInt(infoData.getChildByPath("VRTop")); - bounds[1] = MapleDataTool.getInt(infoData.getChildByPath("VRBottom")); - - if(bounds[0] == bounds[1]) { // old-style baked map - MapleData minimapData = mapData.getChildByPath("miniMap"); - if(minimapData != null) { - bounds[0] = MapleDataTool.getInt(minimapData.getChildByPath("centerX")) * -1; - bounds[1] = MapleDataTool.getInt(minimapData.getChildByPath("centerY")) * -1; - bounds[2] = MapleDataTool.getInt(minimapData.getChildByPath("height")); - bounds[3] = MapleDataTool.getInt(minimapData.getChildByPath("width")); - - map.setMapPointBoundings(bounds[0], bounds[1], bounds[2], bounds[3]); - } - } else { - bounds[2] = MapleDataTool.getInt(infoData.getChildByPath("VRLeft")); - bounds[3] = MapleDataTool.getInt(infoData.getChildByPath("VRRight")); - - map.setMapLineBoundings(bounds[0], bounds[1], bounds[2], bounds[3]); - } - - List allFootholds = new LinkedList<>(); - Point lBound = new Point(); - Point uBound = new Point(); - for (MapleData footRoot : mapData.getChildByPath("foothold")) { - for (MapleData footCat : footRoot) { - for (MapleData footHold : footCat) { - int x1 = MapleDataTool.getInt(footHold.getChildByPath("x1")); - int y1 = MapleDataTool.getInt(footHold.getChildByPath("y1")); - int x2 = MapleDataTool.getInt(footHold.getChildByPath("x2")); - int y2 = MapleDataTool.getInt(footHold.getChildByPath("y2")); - MapleFoothold fh = new MapleFoothold(new Point(x1, y1), new Point(x2, y2), Integer.parseInt(footHold.getName())); - fh.setPrev(MapleDataTool.getInt(footHold.getChildByPath("prev"))); - fh.setNext(MapleDataTool.getInt(footHold.getChildByPath("next"))); - if (fh.getX1() < lBound.x) { - lBound.x = fh.getX1(); - } - if (fh.getX2() > uBound.x) { - uBound.x = fh.getX2(); - } - if (fh.getY1() < lBound.y) { - lBound.y = fh.getY1(); - } - if (fh.getY2() > uBound.y) { - uBound.y = fh.getY2(); - } - allFootholds.add(fh); - } - } - } - MapleFootholdTree fTree = new MapleFootholdTree(lBound, uBound); - for (MapleFoothold fh : allFootholds) { - fTree.insert(fh); - } - map.setFootholds(fTree); - if (mapData.getChildByPath("area") != null) { - for (MapleData area : mapData.getChildByPath("area")) { - int x1 = MapleDataTool.getInt(area.getChildByPath("x1")); - int y1 = MapleDataTool.getInt(area.getChildByPath("y1")); - int x2 = MapleDataTool.getInt(area.getChildByPath("x2")); - int y2 = MapleDataTool.getInt(area.getChildByPath("y2")); - map.addMapleArea(new Rectangle(x1, y1, (x2 - x1), (y2 - y1))); - } - } - try { - Connection con = DatabaseConnection.getConnection(); - try (PreparedStatement ps = con.prepareStatement("SELECT * FROM playernpcs WHERE map = ?")) { - ps.setInt(1, omapid); - try (ResultSet rs = ps.executeQuery()) { - while (rs.next()) { - map.addMapObject(new PlayerNPCs(rs)); - } - } - } - - con.close(); - } catch (SQLException e) { - e.printStackTrace(); - } - - for (MapleData life : mapData.getChildByPath("life")) { - String id = MapleDataTool.getString(life.getChildByPath("id")); - String type = MapleDataTool.getString(life.getChildByPath("type")); - if (id.equals("9001105")) { - id = "9001108";//soz - } - AbstractLoadedMapleLife myLife = loadLife(life, id, type); - if (myLife instanceof MapleMonster) { - MapleMonster monster = (MapleMonster) myLife; - int mobTime = MapleDataTool.getInt("mobTime", life, 0); - int team = MapleDataTool.getInt("team", life, -1); - if (mobTime == -1) { //does not respawn, force spawn once - map.spawnMonster(monster); - } else { - map.addMonsterSpawn(monster, mobTime, team); - } - - //should the map be reseted, use allMonsterSpawn list of monsters to spawn them again - map.addAllMonsterSpawn(monster, mobTime, team); - } else { - map.addMapObject(myLife); - } - } - - if (mapData.getChildByPath("reactor") != null) { - for (MapleData reactor : mapData.getChildByPath("reactor")) { - String id = MapleDataTool.getString(reactor.getChildByPath("id")); - if (id != null) { - MapleReactor newReactor = loadReactor(reactor, id); - map.spawnReactor(newReactor); - } - } - } - try { - map.setMapName(MapleDataTool.getString("mapName", nameData.getChildByPath(getMapStringName(omapid)), "")); - map.setStreetName(MapleDataTool.getString("streetName", nameData.getChildByPath(getMapStringName(omapid)), "")); - } catch (Exception e) { - e.printStackTrace(); - System.err.println("Not found mapid " + omapid); - - map.setMapName(""); - map.setStreetName(""); - } - - map.setClock(mapData.getChildByPath("clock") != null); - map.setEverlast(infoData.getChildByPath("everlast") != null); - map.setTown(infoData.getChildByPath("town") != null); - map.setHPDec(MapleDataTool.getIntConvert("decHP", infoData, 0)); - map.setHPDecProtect(MapleDataTool.getIntConvert("protectItem", infoData, 0)); - map.setForcedReturnMap(MapleDataTool.getInt(infoData.getChildByPath("forcedReturn"), 999999999)); - map.setBoat(mapData.getChildByPath("shipObj") != null); - map.setTimeLimit(MapleDataTool.getIntConvert("timeLimit", infoData, -1)); - map.setFieldType(MapleDataTool.getIntConvert("fieldType", infoData, 0)); - map.setMobCapacity(MapleDataTool.getIntConvert("fixedMobCapacity", infoData, 500));//Is there a map that contains more than 500 mobs? - - HashMap backTypes = new HashMap<>(); - try { - for (MapleData layer : mapData.getChildByPath("back")) { // yolo - int layerNum = Integer.parseInt(layer.getName()); - int type = MapleDataTool.getInt(layer.getChildByPath("type"), 0); - - backTypes.put(layerNum, type); - } - } catch (Exception e) { - e.printStackTrace(); - // swallow cause I'm cool - } - map.setBackgroundTypes(backTypes); - - mapsWLock.lock(); - try { - maps.put(omapid, map); - } finally { - mapsWLock.unlock(); - } - } - } - return map; + return (map != null) ? map : loadMapFromWz(mapid, omapid); } public boolean isMapLoaded(int mapId) { diff --git a/src/tools/DatabaseConnection.java b/src/tools/DatabaseConnection.java index e4c640c174..a9403b1380 100644 --- a/src/tools/DatabaseConnection.java +++ b/src/tools/DatabaseConnection.java @@ -26,16 +26,16 @@ public class DatabaseConnection { } int denies = 0; - while(true) { // There is no way it can pass with a null out of here + while(true) { // There is no way it can pass with a null out of here? try { return DriverManager.getConnection(ServerConstants.DB_URL, ServerConstants.DB_USER, ServerConstants.DB_PASS); } catch (SQLException sqle) { denies++; if(denies == 3) { - // Give up, return null :3 - FilePrinter.printError(FilePrinter.SQL_EXCEPTION, "SQL Driver refused to give a connection after " + denies + " tries."); - return null; + // Give up, throw exception. Nothing good will come from this. + FilePrinter.printError(FilePrinter.SQL_EXCEPTION, "SQL Driver refused to give a connection after " + denies + " tries. Problem: " + sqle.getMessage()); + throw sqle; } } } diff --git a/src/tools/MaplePacketCreator.java b/src/tools/MaplePacketCreator.java index d7b01e6d5a..4d1ba1d288 100644 --- a/src/tools/MaplePacketCreator.java +++ b/src/tools/MaplePacketCreator.java @@ -313,6 +313,13 @@ public class MaplePacketCreator { } } } + + public static byte[] setExtraPendantSlot(boolean toggleExtraSlot) { + final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); + mplew.writeShort(SendOpcode.SET_EXTRA_PENDANT_SLOT.getValue()); + mplew.writeBool(toggleExtraSlot); + return mplew.getPacket(); + } private static void addCharEntry(final MaplePacketLittleEndianWriter mplew, MapleCharacter chr, boolean viewall) { addCharStats(mplew, chr);