/* This file is part of the OdinMS Maple Story Server Copyright (C) 2008 Patrick Huy Matthias Butz Jan Christian Meyer This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation version 3 as published by the Free Software Foundation. You may not use, modify or distribute this program under any other version of the GNU Affero General Public License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ package net.server; import java.io.FileInputStream; import java.io.IOException; import java.net.InetSocketAddress; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import net.MapleServerHandler; import net.mina.MapleCodecFactory; import net.server.channel.Channel; import net.server.guild.MapleAlliance; import net.server.guild.MapleGuild; import net.server.guild.MapleGuildCharacter; import net.server.world.World; import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.buffer.SimpleBufferAllocator; import org.apache.mina.core.filterchain.IoFilter; import org.apache.mina.core.service.IoAcceptor; import org.apache.mina.core.session.IdleStatus; import org.apache.mina.filter.codec.ProtocolCodecFilter; import org.apache.mina.transport.socket.nio.NioSocketAcceptor; import server.CashShop.CashItemFactory; import server.MapleItemInformationProvider; import server.TimerManager; import tools.DatabaseConnection; import tools.FilePrinter; import tools.Pair; import client.MapleCharacter; import client.SkillFactory; import constants.ServerConstants; import server.quest.MapleQuest; public class Server implements Runnable { private IoAcceptor acceptor; private List> channels = new LinkedList<>(); private List worlds = new ArrayList<>(); private Properties subnetInfo = new Properties(); private static Server instance = null; private List> worldRecommendedList = new LinkedList<>(); private Map guilds = new LinkedHashMap<>(); private PlayerBuffStorage buffStorage = new PlayerBuffStorage(); private Map alliances = new LinkedHashMap<>(); private boolean online = false; public static long uptime = System.currentTimeMillis(); public static Server getInstance() { if (instance == null) { instance = new Server(); } return instance; } public boolean isOnline() { return online; } public List> worldRecommendedList() { return worldRecommendedList; } public void removeChannel(int worldid, int channel) { channels.remove(channel); World world = worlds.get(worldid); if (world != null) { world.removeChannel(channel); } } public Channel getChannel(int world, int channel) { return worlds.get(world).getChannel(channel); } public List getChannelsFromWorld(int world) { return worlds.get(world).getChannels(); } public List getAllChannels() { List channelz = new ArrayList<>(); for (World world : worlds) { for (Channel ch : world.getChannels()) { channelz.add(ch); } } return channelz; } public String getIP(int world, int channel) { return channels.get(world).get(channel); } @Override public void run() { Properties p = new Properties(); try { p.load(new FileInputStream("world.ini")); } catch (Exception e) { System.out.println("Please start create_server.bat"); System.exit(0); } System.out.println("MapleSolaxia v" + ServerConstants.VERSION + " starting up.\r\n"); if(ServerConstants.SHUTDOWNHOOK) Runtime.getRuntime().addShutdownHook(new Thread(shutdown(false))); //DatabaseConnection.getConnection(); Connection c = DatabaseConnection.getConnection(); try { PreparedStatement ps = c.prepareStatement("UPDATE accounts SET loggedin = 0"); ps.executeUpdate(); ps.close(); ps = c.prepareStatement("UPDATE characters SET HasMerchant = 0"); ps.executeUpdate(); ps.close(); } catch (SQLException sqle) { sqle.printStackTrace(); } IoBuffer.setUseDirectBuffer(false); IoBuffer.setAllocator(new SimpleBufferAllocator()); acceptor = new NioSocketAcceptor(); acceptor.getFilterChain().addLast("codec", (IoFilter) new ProtocolCodecFilter(new MapleCodecFactory())); TimerManager tMan = TimerManager.getInstance(); tMan.start(); tMan.register(tMan.purge(), 300000);//Purging ftw... tMan.register(new RankingWorker(), ServerConstants.RANKING_INTERVAL); long timeToTake = System.currentTimeMillis(); SkillFactory.loadAllSkills(); System.out.println("Skills loaded in " + ((System.currentTimeMillis() - timeToTake) / 1000.0) + " seconds"); timeToTake = System.currentTimeMillis(); MapleItemInformationProvider.getInstance().getAllItems(); CashItemFactory.getSpecialCashItems(); System.out.println("Items loaded in " + ((System.currentTimeMillis() - timeToTake) / 1000.0) + " seconds"); timeToTake = System.currentTimeMillis(); MapleQuest.loadAllQuest(); System.out.println("Quest loaded in " + ((System.currentTimeMillis() - timeToTake) / 1000.0) + " seconds\r\n"); try { for (int i = 0; i < Integer.parseInt(p.getProperty("worlds")); i++) { System.out.println("Starting world " + i); World world = new World(i, Integer.parseInt(p.getProperty("flag" + i)), p.getProperty("eventmessage" + i), ServerConstants.EXP_RATE, ServerConstants.DROP_RATE, ServerConstants.MESO_RATE, ServerConstants.BOSS_DROP_RATE); worldRecommendedList.add(new Pair<>(i, p.getProperty("whyamirecommended" + i))); worlds.add(world); channels.add(new LinkedHashMap()); for (int j = 0; j < Integer.parseInt(p.getProperty("channels" + i)); j++) { int channelid = j + 1; Channel channel = new Channel(i, channelid); world.addChannel(channel); channels.get(i).put(channelid, channel.getIP()); } world.setServerMessage(p.getProperty("servermessage" + i)); System.out.println("Finished loading world " + i + "\r\n"); } } catch (Exception e) { System.out.println("Error in moople.ini, start CreateINI.bat to re-make the file."); e.printStackTrace();//For those who get errors System.exit(0); } acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 30); acceptor.setHandler(new MapleServerHandler()); try { acceptor.bind(new InetSocketAddress(8484)); } catch (IOException ex) { ex.printStackTrace(); } System.out.println("Listening on port 8484\r\n\r\n"); System.out.println("Solaxia is now online."); online = true; } public void shutdown() { try { TimerManager.getInstance().stop(); acceptor.unbind(); } catch (NullPointerException e) { FilePrinter.printError(FilePrinter.EXCEPTION_CAUGHT, e); } System.out.println("Server offline."); System.exit(0);// BOEIEND :D } public static void main(String args[]) { Server.getInstance().run(); } public Properties getSubnetInfo() { return subnetInfo; } public MapleAlliance getAlliance(int id) { synchronized (alliances) { if (alliances.containsKey(id)) { return alliances.get(id); } return null; } } public void addAlliance(int id, MapleAlliance alliance) { synchronized (alliances) { if (!alliances.containsKey(id)) { alliances.put(id, alliance); } } } public void disbandAlliance(int id) { synchronized (alliances) { MapleAlliance alliance = alliances.get(id); if (alliance != null) { for (Integer gid : alliance.getGuilds()) { guilds.get(gid).setAllianceId(0); } alliances.remove(id); } } } public void allianceMessage(int id, final byte[] packet, int exception, int guildex) { MapleAlliance alliance = alliances.get(id); if (alliance != null) { for (Integer gid : alliance.getGuilds()) { if (guildex == gid) { continue; } MapleGuild guild = guilds.get(gid); if (guild != null) { guild.broadcast(packet, exception); } } } } public boolean addGuildtoAlliance(int aId, int guildId) { MapleAlliance alliance = alliances.get(aId); if (alliance != null) { alliance.addGuild(guildId); return true; } return false; } public boolean removeGuildFromAlliance(int aId, int guildId) { MapleAlliance alliance = alliances.get(aId); if (alliance != null) { alliance.removeGuild(guildId); return true; } return false; } public boolean setAllianceRanks(int aId, String[] ranks) { MapleAlliance alliance = alliances.get(aId); if (alliance != null) { alliance.setRankTitle(ranks); return true; } return false; } public boolean setAllianceNotice(int aId, String notice) { MapleAlliance alliance = alliances.get(aId); if (alliance != null) { alliance.setNotice(notice); return true; } return false; } public boolean increaseAllianceCapacity(int aId, int inc) { MapleAlliance alliance = alliances.get(aId); if (alliance != null) { alliance.increaseCapacity(inc); return true; } return false; } public Set getChannelServer(int world) { return new HashSet<>(channels.get(world).keySet()); } public byte getHighestChannelId() { byte highest = 0; for (Iterator it = channels.get(0).keySet().iterator(); it.hasNext();) { Integer channel = it.next(); if (channel != null && channel.intValue() > highest) { highest = channel.byteValue(); } } return highest; } public int createGuild(int leaderId, String name) { return MapleGuild.createGuild(leaderId, name); } public MapleGuild getGuild(int id, int world, MapleGuildCharacter mgc) { synchronized (guilds) { if (guilds.get(id) != null) { return guilds.get(id); } MapleGuild g = new MapleGuild(id, world); if (g.getId() == -1) { return null; } if (mgc != null) { g.setOnline(mgc.getId(), true, mgc.getChannel()); } guilds.put(id, g); return g; } } public void clearGuilds() {//remake synchronized (guilds) { guilds.clear(); } //for (List world : worlds.values()) { //reloadGuildCharacters(); } public void setGuildMemberOnline(MapleGuildCharacter mgc, boolean bOnline, int channel) { MapleGuild g = getGuild(mgc.getGuildId(), mgc.getWorld(), mgc); g.setOnline(mgc.getId(), bOnline, channel); } public int addGuildMember(MapleGuildCharacter mgc) { MapleGuild g = guilds.get(mgc.getGuildId()); if (g != null) { return g.addGuildMember(mgc); } return 0; } public boolean setGuildAllianceId(int gId, int aId) { MapleGuild guild = guilds.get(gId); if (guild != null) { guild.setAllianceId(aId); return true; } return false; } public void leaveGuild(MapleGuildCharacter mgc) { MapleGuild g = guilds.get(mgc.getGuildId()); if (g != null) { g.leaveGuild(mgc); } } public void guildChat(int gid, String name, int cid, String msg) { MapleGuild g = guilds.get(gid); if (g != null) { g.guildChat(name, cid, msg); } } public void changeRank(int gid, int cid, int newRank) { MapleGuild g = guilds.get(gid); if (g != null) { g.changeRank(cid, newRank); } } public void expelMember(MapleGuildCharacter initiator, String name, int cid) { MapleGuild g = guilds.get(initiator.getGuildId()); if (g != null) { g.expelMember(initiator, name, cid); } } public void setGuildNotice(int gid, String notice) { MapleGuild g = guilds.get(gid); if (g != null) { g.setGuildNotice(notice); } } public void memberLevelJobUpdate(MapleGuildCharacter mgc) { MapleGuild g = guilds.get(mgc.getGuildId()); if (g != null) { g.memberLevelJobUpdate(mgc); } } public void changeRankTitle(int gid, String[] ranks) { MapleGuild g = guilds.get(gid); if (g != null) { g.changeRankTitle(ranks); } } public void setGuildEmblem(int gid, short bg, byte bgcolor, short logo, byte logocolor) { MapleGuild g = guilds.get(gid); if (g != null) { g.setGuildEmblem(bg, bgcolor, logo, logocolor); } } public void disbandGuild(int gid) { synchronized (guilds) { MapleGuild g = guilds.get(gid); g.disbandGuild(); guilds.remove(gid); } } public boolean increaseGuildCapacity(int gid) { MapleGuild g = guilds.get(gid); if (g != null) { return g.increaseCapacity(); } return false; } public void gainGP(int gid, int amount) { MapleGuild g = guilds.get(gid); if (g != null) { g.gainGP(amount); } } public void guildMessage(int gid, byte[] packet) { guildMessage(gid, packet, -1); } public void guildMessage(int gid, byte[] packet, int exception) { MapleGuild g = guilds.get(gid); if(g != null) { g.broadcast(packet, exception); } } public PlayerBuffStorage getPlayerBuffStorage() { return buffStorage; } public void deleteGuildCharacter(MapleGuildCharacter mgc) { setGuildMemberOnline(mgc, false, (byte) -1); if (mgc.getGuildRank() > 1) { leaveGuild(mgc); } else { disbandGuild(mgc.getGuildId()); } } public void reloadGuildCharacters(int world) { World worlda = getWorld(world); for (MapleCharacter mc : worlda.getPlayerStorage().getAllCharacters()) { if (mc.getGuildId() > 0) { setGuildMemberOnline(mc.getMGC(), true, worlda.getId()); memberLevelJobUpdate(mc.getMGC()); } } worlda.reloadGuildSummary(); } public void broadcastMessage(final byte[] packet) { for (Channel ch : getChannelsFromWorld(0)) { ch.broadcastPacket(packet); } } public void broadcastGMMessage(final byte[] packet) { for (Channel ch : getChannelsFromWorld(0)) { ch.broadcastGMPacket(packet); } } public boolean isGmOnline() { for (Channel ch : getChannelsFromWorld(0)) { for (MapleCharacter player : ch.getPlayerStorage().getAllCharacters()) { if (player.isGM()){ return true; } } } return false; } public World getWorld(int id) { return worlds.get(id); } public List getWorlds() { return worlds; } public final Runnable shutdown(final boolean restart) {//only once :D return new Runnable() { @Override public void run() { System.out.println((restart ? "Restarting" : "Shutting down") + " the server!\r\n"); if (getWorlds() == null) return;//already shutdown for (World w : getWorlds()) { w.shutdown(); } /*for (World w : getWorlds()) { while (w.getPlayerStorage().getAllCharacters().size() > 0) { try { Thread.sleep(1000); } catch (InterruptedException ie) { System.err.println("FUCK MY LIFE"); } } } for (Channel ch : getAllChannels()) { while (ch.getConnectedClients() > 0) { try { Thread.sleep(1000); } catch (InterruptedException ie) { System.err.println("FUCK MY LIFE"); } } }*/ TimerManager.getInstance().purge(); TimerManager.getInstance().stop(); for (Channel ch : getAllChannels()) { while (!ch.finishedShutdown()) { try { Thread.sleep(1000); } catch (InterruptedException ie) { System.err.println("FUCK MY LIFE"); } } } worlds.clear(); worlds = null; channels.clear(); channels = null; worldRecommendedList.clear(); worldRecommendedList = null; System.out.println("Worlds + Channels are offline."); acceptor.unbind(); acceptor = null; if (!restart) { System.exit(0); } else { System.out.println("\r\nRestarting the server....\r\n"); try { instance.finalize();//FUU I CAN AND IT'S FREE } catch (Throwable ex) { ex.printStackTrace(); } instance = null; System.gc(); getInstance().run();//DID I DO EVERYTHING?! D: } } }; } }