/* OrpheusMS: MapleStory Private Server based on OdinMS Copyright (C) 2012 Aaron Weiss <aaron@deviant-core.net> Patrick Huy <patrick.huy@frz.cc> Matthias Butz <matze@odinms.de> Jan Christian Meyer <vimes@odinms.de> 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, either version 3 of the License, or (at your option) any later version. 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 <http://www.gnu.org/licenses/>. */ package net.server; import java.io.File; import java.net.InetSocketAddress; import java.sql.Connection; import java.sql.PreparedStatement; import java.util.HashMap; import java.util.List; import java.util.Map; import client.MapleCharacter; import constants.ServerConstants; import java.util.ArrayList; import java.util.EnumMap; import java.util.Iterator; import java.util.Map.Entry; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; import tools.DatabaseConnection; import tools.Output; import net.MaplePacket; import net.MapleServerHandler; import net.PacketProcessor; import net.mina.MapleCodecFactory; import provider.MapleDataProviderFactory; import scripting.event.EventScriptManager; import server.TimerManager; import server.maps.MapleMapFactory; import tools.MaplePacketCreator; 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.SocketSessionConfig; import org.apache.mina.transport.socket.nio.NioSocketAcceptor; import server.events.gm.MapleEvent; import server.expeditions.MapleExpedition; import server.expeditions.MapleExpeditionType; import server.maps.HiredMerchant; import server.maps.MapleMap; public final class Channel { private int port = 7575; private PlayerStorage players = new PlayerStorage(); private byte world, channel; private IoAcceptor acceptor; private String ip, serverMessage; private MapleMapFactory mapFactory; private EventScriptManager eventSM; private Map<Integer, HiredMerchant> hiredMerchants = new HashMap<Integer, HiredMerchant>(); private ReentrantReadWriteLock merchant_lock = new ReentrantReadWriteLock(true); private EnumMap<MapleExpeditionType, MapleExpedition> expeditions = new EnumMap<MapleExpeditionType, MapleExpedition>(MapleExpeditionType.class); private MapleEvent event; private boolean finishedShutdown = false; public Channel(final byte world, final byte channel) { this.world = world; this.channel = channel; this.mapFactory = new MapleMapFactory(MapleDataProviderFactory.getDataProvider(new File(System.getProperty("wzpath") + "/Map.wz")), MapleDataProviderFactory.getDataProvider(new File(System.getProperty("wzpath") + "/String.wz")), world, channel); try { eventSM = new EventScriptManager(this, ServerConstants.EVENTS.split(" ")); Connection c = DatabaseConnection.getConnection(); 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(); port = 7575 + this.channel - 1; port += (world * 100); ip = ServerConstants.HOST + ":" + port; IoBuffer.setUseDirectBuffer(false); IoBuffer.setAllocator(new SimpleBufferAllocator()); acceptor = new NioSocketAcceptor(); TimerManager.getInstance().register(new respawnMaps(), 10000); acceptor.setHandler(new MapleServerHandler(PacketProcessor.getProcessor(), channel, world)); acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 30); acceptor.getFilterChain().addLast("codec", (IoFilter) new ProtocolCodecFilter(new MapleCodecFactory())); acceptor.bind(new InetSocketAddress(port)); ((SocketSessionConfig) acceptor.getSessionConfig()).setTcpNoDelay(true); eventSM.init(); Output.print("World " + world + ": Channel " + getId() + ": Listening on port " + port + "."); } catch (Exception e) { e.printStackTrace(); } } public final void shutdown() { try { Output.print("Server is now shutting down channel " + channel + " on world" + world + "."); closeAllMerchants(); players.disconnectAll(); acceptor.unbind(); finishedShutdown = true; Output.print("Channel " + channel + " on world" + world + " has been shut down."); } catch (Exception e) { Output.print("Channel " + channel + " on world" + world + " has failed to shut down.\r\n" + e); } } public void closeAllMerchants() { WriteLock wlock = merchant_lock.writeLock(); wlock.lock(); try { final Iterator<HiredMerchant> hmit = hiredMerchants.values().iterator(); while (hmit.hasNext()) { hmit.next().forceClose(); hmit.remove(); } } catch (Exception e) { } finally { wlock.unlock(); } } public MapleMapFactory getMapFactory() { return mapFactory; } public int getWorld() { return world; } public void addPlayer(MapleCharacter chr) { players.addPlayer(chr); chr.announce(MaplePacketCreator.serverMessage(serverMessage)); } public PlayerStorage getPlayerStorage() { return players; } public void removePlayer(MapleCharacter chr) { players.removePlayer(chr.getId()); } public int getConnectedClients() { return players.getAllCharacters().size(); } public void broadcastPacket(MaplePacket data) { for (MapleCharacter chr : players.getAllCharacters()) { chr.announce(data); } } public final byte getId() { return channel; } public String getIP() { return ip; } public MapleEvent getEvent() { return event; } public void setEvent(MapleEvent event) { this.event = event; } public EventScriptManager getEventSM() { return eventSM; } public void broadcastGMPacket(MaplePacket data) { for (MapleCharacter chr : players.getAllCharacters()) { if (chr.isGM()) { chr.announce(data); } } } public void broadcastGMPacket(MaplePacket data, String exclude) { for (MapleCharacter chr : players.getAllCharacters()) { if (chr.isGM() && !chr.getName().equals(exclude)) { chr.announce(data); } } } public void yellowWorldMessage(String msg) { for (MapleCharacter mc : getPlayerStorage().getAllCharacters()) { mc.announce(MaplePacketCreator.sendYellowTip(msg)); } } public void worldMessage(String msg) { for (MapleCharacter mc : getPlayerStorage().getAllCharacters()) { mc.dropMessage(msg); } } public List<MapleCharacter> getPartyMembers(MapleParty party) { List<MapleCharacter> partym = new ArrayList<MapleCharacter>(8); for (MaplePartyCharacter partychar : party.getMembers()) { if (partychar.getChannel() == getId()) { MapleCharacter chr = getPlayerStorage().getCharacterByName(partychar.getName()); if (chr != null) { partym.add(chr); } } } return partym; } public class respawnMaps implements Runnable { @Override public void run() { for (Entry<Integer, MapleMap> map : mapFactory.getMaps().entrySet()) { map.getValue().respawn(); } } } public Map<Integer, HiredMerchant> getHiredMerchants() { return hiredMerchants; } public void addHiredMerchant(int chrid, HiredMerchant hm) { WriteLock wlock = merchant_lock.writeLock(); wlock.lock(); try { hiredMerchants.put(chrid, hm); } finally { wlock.unlock(); } } public void removeHiredMerchant(int chrid) { WriteLock wlock = merchant_lock.writeLock(); wlock.lock(); try { hiredMerchants.remove(chrid); } finally { wlock.unlock(); } } public int[] multiBuddyFind(int charIdFrom, int[] characterIds) { List<Integer> ret = new ArrayList<Integer>(characterIds.length); PlayerStorage playerStorage = getPlayerStorage(); for (int characterId : characterIds) { MapleCharacter chr = playerStorage.getCharacterById(characterId); if (chr != null) { if (chr.getBuddylist().containsVisible(charIdFrom)) { ret.add(characterId); } } } int[] retArr = new int[ret.size()]; int pos = 0; for (Integer i : ret) { retArr[pos++] = i.intValue(); } return retArr; } public boolean hasExpedition(MapleExpeditionType type) { return expeditions.containsKey(type); } public void addExpedition(MapleExpeditionType type, MapleExpedition exped) { expeditions.put(type, exped); } public MapleExpedition getExpedition(MapleExpeditionType type) { return expeditions.get(type); } public boolean isConnected(String name) { return getPlayerStorage().getCharacterByName(name) != null; } public boolean finishedShutdown() { return finishedShutdown; } public void setServerMessage(String message) { this.serverMessage = message; broadcastPacket(MaplePacketCreator.serverMessage(message)); } }