package javastory.channel;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import javastory.channel.handling.ChannelPacketHandler;
import javastory.channel.maps.GameMapFactory;
import javastory.channel.maps.MapTimer;
import javastory.channel.rmi.ChannelWorldInterfaceImpl;
import javastory.channel.server.AutobanManager;
import javastory.channel.server.Squad;
import javastory.channel.shops.HiredMerchantStore;
import javastory.config.ChannelInfo;
import javastory.config.WorldConfig;
import javastory.config.WorldInfo;
import javastory.game.data.ItemInfoProvider;
import javastory.game.data.ItemMakerFactory;
import javastory.game.data.RandomRewards;
import javastory.game.data.SkillInfoProvider;
import javastory.io.GamePacket;
import javastory.registry.Nexus;
import javastory.registry.WorldRegistry;
import javastory.rmi.ChannelWorldInterface;
import javastory.rmi.WorldChannelInterface;
import javastory.scripting.EventScriptManager;
import javastory.server.GameService;
import javastory.server.TimerManager;
import javastory.server.channel.PlayerStorage;
import javastory.server.handling.PacketHandler;
import javastory.tools.packets.ChannelPackets;
import javastory.world.core.ServerStatus;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
public final class ChannelServer extends GameService {
private static ChannelServer instance;
private ChannelWorldInterface cwi;
private WorldChannelInterface wci = null;
private boolean MegaphoneMuteState = false;
private PlayerStorage players;
private final GameMapFactory mapFactory = new GameMapFactory();
private final Map<Integer, GuildSummary> gsStore = Maps.newHashMap();
private final Map<String, Squad> mapleSquads = Maps.newHashMap();
private final Map<Integer, HiredMerchantStore> merchants = Maps.newHashMap();
private String serverMessage;
private float expRate, mesoRate, itemRate;
private int channelId, currentMerchantId = 0;
private final ChannelInfo channelInfo;
private final Lock merchantMutex = new ReentrantLock();
private EventScriptManager eventManager;
private ChannelServer(final ChannelInfo info) {
super(info);
this.channelInfo = info;
}
public static synchronized boolean initialize(final ChannelInfo info) {
if (instance == null) {
instance = new ChannelServer(info);
return true;
} else {
return false;
}
}
public int getChannelId() {
return this.channelInfo.getId();
}
public ChannelInfo getChannelInfo() {
return this.channelInfo;
}
public static void pingWorld() {
try {
instance.wci.ping();
} catch (final RemoteException ex) {
if (instance.isWorldReady.compareAndSet(true, false)) {
instance.connectToWorld();
}
}
}
@Override
protected void connectToWorld() {
try {
final WorldRegistry worldRegistry = Nexus.getOrBindWorldRegistry();
// TODO: implement the interface in this class.
this.cwi = new ChannelWorldInterfaceImpl(this);
this.wci = worldRegistry.registerChannelServer(this.channelInfo, this.cwi);
this.wci.serverReady();
} catch (NotBoundException | RemoteException ex) {
Logger.getLogger(ChannelServer.class.getName()).log(Level.SEVERE, null, ex);
}
this.isWorldReady.compareAndSet(false, true);
}
@Override
protected final void loadSettings() {
final WorldInfo info = WorldConfig.load(this.channelInfo.getWorldId());
this.expRate = (float) info.getExpRate() / 100;
this.mesoRate = (float) info.getMesoRate() / 100;
this.itemRate = (float) info.getItemRate() / 100;
// TODO: do this one in the DB too.
this.serverMessage = "";
}
public void initialize() {
this.connectToWorld();
this.loadSettings();
final List<String> events = this.loadEventsFromDb();
this.eventManager = new EventScriptManager(events);
TimerManager.getInstance().start();
TimerManager.getInstance().register(AutobanManager.getInstance(), 60000);
MapTimer.getInstance().start();
ItemMakerFactory.getInstance();
ItemInfoProvider.getInstance();
RandomRewards.getInstance();
SkillInfoProvider.getSkill(99999999);
this.players = new PlayerStorage();
final PacketHandler serverHandler = new ChannelPacketHandler(this.channelId);
super.bind(serverHandler);
try {
this.wci.serverReady();
} catch (final RemoteException ex) {
Logger.getLogger(ChannelServer.class.getName()).log(Level.SEVERE, null, ex);
}
System.out.printf(":: Channel %d : Listening on port %d ::", this.getChannelId(), super.endpointInfo.getPort());
Runtime.getRuntime().addShutdownHook(new Thread(new ShutdownListener()));
this.eventManager.init();
}
public static GameMapFactory getMapFactory() {
return instance.mapFactory;
}
public static void addPlayer(final ChannelCharacter chr) {
instance.players.registerPlayer(chr);
chr.getClient().write(ChannelPackets.headerMessage(instance.serverMessage));
}
public static PlayerStorage getPlayerStorage() {
return instance.players;
}
public static void removePlayer(final ChannelCharacter chr) {
instance.players.deregisterPlayer(chr);
}
public String getServerMessage() {
return this.serverMessage;
}
public void setServerMessage(final String newMessage) {
this.serverMessage = newMessage;
this.broadcastPacket(ChannelPackets.headerMessage(this.serverMessage));
}
public void broadcastPacket(final GamePacket data) {
this.players.broadcastPacket(data);
}
public void broadcastSmegaPacket(final GamePacket data) {
this.players.broadcastSmegaPacket(data);
}
public void broadcastGMPacket(final GamePacket data) {
this.players.broadcastGMPacket(data);
}
public String getIP(final int channel) {
try {
return getWorldInterface().getIP(channel);
} catch (final RemoteException e) {
System.err.println("Lost connection to world server" + e);
throw new RuntimeException("Lost connection to world server");
}
}
public static WorldChannelInterface getWorldInterface() {
if (instance.isWorldReady.get()) {
return instance.wci;
}
return null;
}
public final void shutdownWorld(final int time) {
try {
getWorldInterface().shutdown(time);
} catch (final RemoteException e) {
pingWorld();
}
}
public final void shutdownLogin() {
try {
getWorldInterface().shutdownLogin();
} catch (final RemoteException e) {
pingWorld();
}
}
public final int getLoadedMaps() {
return getMapFactory().getLoadedMaps();
}
public final EventScriptManager getEventSM() {
return this.eventManager;
}
public final void reloadEvents() {
final List<String> events = this.loadEventsFromDb();
this.eventManager.cancel();
this.eventManager = new EventScriptManager(events);
this.eventManager.init();
}
private List<String> loadEventsFromDb() {
// TODO: load events from DB.
final List<String> events = Lists.newArrayList();
return events;
}
public final float getExpRate() {
return this.expRate;
}
public final void setExpRate(final float expRate) {
this.expRate = expRate;
}
public final float getMesoRate() {
return this.mesoRate;
}
public final void setMesoRate(final float mesoRate) {
this.mesoRate = mesoRate;
}
public final float getItemRate() {
return this.itemRate;
}
public final void setItemRate(final float dropRate) {
this.itemRate = dropRate;
}
public final Guild getGuild(final int guildId) {
Guild guild = null;
try {
guild = getWorldInterface().getGuild(guildId);
} catch (final RemoteException re) {
System.err.println("RemoteException while fetching MapleGuild." + re);
return null;
}
if (this.gsStore.get(guildId) == null) {
this.gsStore.put(guildId, new GuildSummary(guild));
}
return guild;
}
public final GuildSummary getGuildSummary(final int guildId) {
if (this.gsStore.containsKey(guildId)) {
return this.gsStore.get(guildId);
}
try {
final Guild guild = ChannelServer.getWorldInterface().getGuild(guildId);
if (guild != null) {
this.gsStore.put(guildId, new GuildSummary(guild));
}
return this.gsStore.get(guildId);
} catch (final RemoteException re) {
System.err.println("RemoteException while fetching GuildSummary." + re);
return null;
}
}
public final void updateGuildSummary(final int guildId, final GuildSummary summary) {
this.gsStore.put(guildId, summary);
}
public final Squad getMapleSquad(final String type) {
return this.mapleSquads.get(type);
}
public final boolean addMapleSquad(final Squad squad, final String type) {
if (this.mapleSquads.get(type) == null) {
this.mapleSquads.remove(type);
this.mapleSquads.put(type, squad);
return true;
}
return false;
}
public final boolean removeMapleSquad(final String type) {
if (this.mapleSquads.containsKey(type)) {
this.mapleSquads.remove(type);
return true;
}
return false;
}
public final void closeAllMerchant() {
this.merchantMutex.lock();
try {
final Iterator<HiredMerchantStore> iterator = this.merchants.values().iterator();
while (iterator.hasNext()) {
final HiredMerchantStore merchant = iterator.next();
merchant.closeShop(true, false);
iterator.remove();
}
} finally {
this.merchantMutex.unlock();
}
}
public final int addMerchant(final HiredMerchantStore merchant) {
this.merchantMutex.lock();
int id = 0;
try {
id = this.currentMerchantId;
this.merchants.put(id, merchant);
this.currentMerchantId++;
} finally {
this.merchantMutex.unlock();
}
return id;
}
public final void removeMerchant(final HiredMerchantStore merchant) {
this.merchantMutex.lock();
try {
this.merchants.remove(merchant.getStoreId());
} finally {
this.merchantMutex.unlock();
}
}
public final boolean hasMerchant(final int accountId) {
boolean contains = false;
this.merchantMutex.lock();
try {
final Iterator<HiredMerchantStore> iterator = this.merchants.values().iterator();
while (iterator.hasNext()) {
final HiredMerchantStore merchant = iterator.next();
if (merchant.getOwnerAccountId() == accountId) {
contains = true;
break;
}
}
} finally {
this.merchantMutex.unlock();
}
return contains;
}
public final void toggleMegaponeMuteState() {
this.MegaphoneMuteState = !this.MegaphoneMuteState;
}
public final boolean getMegaphoneMuteState() {
return this.MegaphoneMuteState;
}
public final List<ChannelCharacter> getPartyMembers(final int partyId) {
final List<ChannelCharacter> partym = Lists.newLinkedList();
try {
final Party party = ChannelServer.getWorldInterface().getParty(partyId);
for (final PartyMember partychar : party.getMembers()) {
if (partychar.getChannel() == this.getChannelId()) {
// Make sure the thing doesn't get duplicate plays due to
// ccing bug.
final ChannelCharacter chr = getPlayerStorage().getCharacterByName(partychar.getName());
if (chr != null) {
partym.add(chr);
}
}
}
} catch (final RemoteException ex) {
ex.printStackTrace();
}
return partym;
}
private class ShutdownListener implements Runnable {
@Override
public void run() {
ChannelServer.this.shutdown();
}
}
@Override
public final void shutdown() {
if (super.getStatus() != ServerStatus.ONLINE) {
throw new IllegalStateException("The server is not active.");
}
super.setStatus(ServerStatus.SHUTTING_DOWN);
System.out.printf("Channel %d, Saving hired merchants...", this.channelId);
this.closeAllMerchant();
System.out.printf("Channel %d, Saving characters...", this.channelId);
this.players.disconnectAll();
System.out.printf("Channel %d, Unbinding ports...", this.channelId);
super.unbind();
super.setStatus(ServerStatus.OFFLINE);
this.wci = null;
this.cwi = null;
}
public final void shutdown(final int time) {
TimerManager.getInstance().schedule(new ShutdownListener(), time);
}
public static ChannelServer getInstance() {
return instance;
}
}