/* * This file is part of the OdinMS Maple Story Server Copyright (C) 2008 ~ 2010 * 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 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 <http://www.gnu.org/licenses/>. */ package javastory.scripting; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import javastory.channel.ChannelCharacter; import javastory.channel.ChannelServer; import javastory.channel.Party; import javastory.channel.PartyMember; import javastory.channel.life.Monster; import javastory.channel.maps.GameMap; import javastory.channel.maps.GameMapFactory; import javastory.channel.server.CarnivalParty; import javastory.channel.server.Squad; import javastory.game.quest.QuestStatus; import javastory.server.TimerManager; import javastory.tools.packets.ChannelPackets; import javax.script.ScriptException; import com.google.common.collect.Lists; import com.google.common.collect.Maps; public class EventInstanceManager { private List<ChannelCharacter> chars = Lists.newLinkedList(); private List<Monster> mobs = Lists.newLinkedList(); private Map<ChannelCharacter, Integer> killCount = Maps.newHashMap(); private EventManager eventManager; private GameMapFactory mapFactory; private final String name; private Properties props = new Properties(); private long timeStarted = 0; private long eventTime = 0; private List<Integer> mapIds = Lists.newLinkedList(); private ScheduledFuture<?> eventTimer; private final Lock mutex = new ReentrantLock(); public EventInstanceManager(final EventManager em, final String name, final GameMapFactory factory) { this.eventManager = em; this.name = name; this.mapFactory = factory; } public void registerPlayer(final ChannelCharacter chr) { try { this.mutex.lock(); try { this.chars.add(chr); } finally { this.mutex.unlock(); } chr.setEventInstance(this); this.eventManager.getInvocable().invokeFunction("playerEntry", this, chr); } catch (ScriptException | NoSuchMethodException ex) { ex.printStackTrace(); } } public void changedMap(final ChannelCharacter chr, final int mapid) { try { this.eventManager.getInvocable().invokeFunction("changedMap", this, chr, mapid); } catch (final NullPointerException npe) { } catch (final ScriptException ex) { ex.printStackTrace(); } catch (final NoSuchMethodException ex) { ex.printStackTrace(); } } public void timeOut(final long delay, final EventInstanceManager eim) { this.eventTimer = TimerManager.getInstance().schedule(new Runnable() { @Override public void run() { try { EventInstanceManager.this.eventManager.getInvocable().invokeFunction("scheduledTimeout", eim); } catch (final NullPointerException npe) { } catch (final ScriptException ex) { ex.printStackTrace(); } catch (final NoSuchMethodException ex) { ex.printStackTrace(); } } }, delay); } public void stopEventTimer() { this.eventTime = 0; this.timeStarted = 0; if (this.eventTimer != null) { this.eventTimer.cancel(false); } } public void restartEventTimer(final long time) { this.timeStarted = System.currentTimeMillis(); this.eventTime = time; if (this.eventTimer != null) { this.eventTimer.cancel(false); } this.eventTimer = null; final int timesend = (int) time / 1000; this.mutex.lock(); try { for (final ChannelCharacter chr : this.chars) { chr.getClient().write(ChannelPackets.getClock(timesend)); } } finally { this.mutex.unlock(); } this.timeOut(time, this); } public void startEventTimer(final long time) { this.timeStarted = System.currentTimeMillis(); this.eventTime = time; final int timesend = (int) time / 1000; this.mutex.lock(); try { for (final ChannelCharacter chr : this.chars) { chr.getClient().write(ChannelPackets.getClock(timesend)); } } finally { this.mutex.unlock(); } this.timeOut(time, this); } public boolean isTimerStarted() { return this.eventTime > 0 && this.timeStarted > 0; } public long getTimeLeft() { return this.eventTime - (System.currentTimeMillis() - this.timeStarted); } public void registerParty(final Party party, final GameMap map) { for (final PartyMember pc : party.getMembers()) { final ChannelCharacter c = map.getCharacterById_InMap(pc.getCharacterId()); this.registerPlayer(c); } } public void unregisterPlayer(final ChannelCharacter chr) { this.mutex.lock(); try { this.chars.remove(chr); } finally { this.mutex.unlock(); } chr.setEventInstance(null); } public final boolean disposeIfPlayerBelow(final byte size, final int towarp) { GameMap map = null; if (towarp != 0) { map = this.getMapFactory().getMap(towarp); } this.mutex.lock(); try { if (this.chars.size() <= size) { ChannelCharacter chr; for (int i = 0; i < this.chars.size(); i++) { chr = this.chars.get(i); this.unregisterPlayer(chr); if (towarp != 0) { chr.changeMap(map, map.getPortal(0)); } } this.dispose(); return true; } } finally { this.mutex.unlock(); } return false; } public final void saveBossQuest(final int points) { this.mutex.lock(); try { for (final ChannelCharacter chr : this.chars) { final QuestStatus record = chr.getQuestStatus(150001); final String customData = record.getCustomData(); if (customData != null) { record.setCustomData(String.valueOf(points + Integer.parseInt(customData))); } else { record.setCustomData(String.valueOf(points)); // First time } } } finally { this.mutex.unlock(); } } public List<ChannelCharacter> getPlayers() { return Collections.unmodifiableList(this.chars); } public final int getPlayerCount() { return this.chars.size(); } public void registerMonster(final Monster mob) { this.mobs.add(mob); mob.setEventInstance(this); } public void unregisterMonster(final Monster mob) { this.mobs.remove(mob); mob.setEventInstance(null); if (this.mobs.isEmpty()) { try { this.eventManager.getInvocable().invokeFunction("allMonstersDead", this); } catch (ScriptException | NoSuchMethodException ex) { ex.printStackTrace(); } } } public void playerKilled(final ChannelCharacter chr) { try { this.eventManager.getInvocable().invokeFunction("playerDead", this, chr); } catch (ScriptException | NoSuchMethodException ex) { ex.printStackTrace(); } } public boolean revivePlayer(final ChannelCharacter chr) { try { final Object b = this.eventManager.getInvocable().invokeFunction("playerRevive", this, chr); if (b instanceof Boolean) { return (Boolean) b; } } catch (ScriptException | NoSuchMethodException ex) { ex.printStackTrace(); } return true; } public void playerDisconnected(final ChannelCharacter chr) { try { byte ret = ((Double) this.eventManager.getInvocable().invokeFunction("playerDisconnected", this, chr)).byteValue(); if (ret == 0) { this.unregisterPlayer(chr); if (this.getPlayerCount() <= 0) { this.dispose(); } } else { this.mutex.lock(); try { if (ret > 0) { this.unregisterPlayer(chr); if (this.getPlayerCount() < ret) { for (final ChannelCharacter player : this.chars) { this.removePlayer(player); } this.dispose(); } } else { this.unregisterPlayer(chr); ret *= -1; if (this.isLeader(chr)) { for (final ChannelCharacter player : this.chars) { this.removePlayer(player); } this.dispose(); } else { if (this.getPlayerCount() < ret) { for (final ChannelCharacter player : this.chars) { this.removePlayer(player); } this.dispose(); } } } } finally { this.mutex.unlock(); } } } catch (ScriptException | NoSuchMethodException ex) { ex.printStackTrace(); } } /** * * @param chr * @param mob */ public void monsterKilled(final ChannelCharacter chr, final Monster mob) { try { Integer kc = this.killCount.get(chr); final int inc = ((Double) this.eventManager.getInvocable().invokeFunction("monsterValue", this, mob.getId())).intValue(); if (kc == null) { kc = inc; } else { kc += inc; } this.killCount.put(chr, kc); if (chr.getCarnivalParty() != null) { this.eventManager.getInvocable().invokeFunction("monsterKilled", this, chr, mob.getStats().getCP()); } } catch (ScriptException | NoSuchMethodException ex) { ex.printStackTrace(); } } public int getKillCount(final ChannelCharacter chr) { final Integer kc = this.killCount.get(chr); if (kc == null) { return 0; } else { return kc; } } public void dispose() { this.mutex.lock(); try { this.chars.clear(); this.chars = null; } finally { this.mutex.unlock(); } this.mobs.clear(); this.mobs = null; this.killCount.clear(); this.killCount = null; this.timeStarted = 0; this.eventTime = 0; this.props.clear(); this.props = null; for (final Integer i : this.mapIds) { this.mapFactory.removeInstanceMap(i); } this.mapIds = null; this.mapFactory = null; this.eventManager.disposeInstance(this.name); this.eventManager = null; } public final void broadcastPlayerMsg(final int type, final String msg) { this.mutex.lock(); try { for (final ChannelCharacter chr : this.chars) { chr.getClient().write(ChannelPackets.serverNotice(type, msg)); } } finally { this.mutex.unlock(); } } public final GameMap createInstanceMap(final int mapid) { final int assignedid = ChannelServer.getInstance().getEventSM().getNewInstanceMapId(); this.mapIds.add(assignedid); return this.mapFactory.createInstanceMap(mapid, true, true, true, assignedid); } public final GameMap createInstanceMapS(final int mapid) { final int assignedid = ChannelServer.getInstance().getEventSM().getNewInstanceMapId(); this.mapIds.add(assignedid); return this.mapFactory.createInstanceMap(mapid, false, false, false, assignedid); } public final GameMapFactory getMapFactory() { return this.mapFactory; } public final GameMap getMapInstance(final int args) { final GameMap map = this.mapFactory.getInstanceMap(this.mapIds.get(args)); // in case reactors need shuffling and we are actually loading the map if (!this.mapFactory.isInstanceMapLoaded(this.mapIds.get(args))) { if (this.eventManager.getProperty("shuffleReactors") != null && this.eventManager.getProperty("shuffleReactors").equals("true")) { map.shuffleReactors(); } } return map; } public final void schedule(final String methodName, final long delay) { TimerManager.getInstance().schedule(new Runnable() { @Override public void run() { try { EventInstanceManager.this.eventManager.getInvocable().invokeFunction(methodName, EventInstanceManager.this); } catch (final NullPointerException npe) { } catch (final ScriptException ex) { ex.printStackTrace(); } catch (final NoSuchMethodException ex) { ex.printStackTrace(); } } }, delay); } public final String getName() { return this.name; } public final void setProperty(final String key, final String value) { this.props.setProperty(key, value); } public final Object setProperty(final String key, final String value, final boolean prev) { return this.props.setProperty(key, value); } public final String getProperty(final String key) { return this.props.getProperty(key); } public final void leftParty(final ChannelCharacter chr) { try { this.eventManager.getInvocable().invokeFunction("leftParty", this, chr); } catch (ScriptException | NoSuchMethodException ex) { ex.printStackTrace(); } } public final void disbandParty() { try { this.eventManager.getInvocable().invokeFunction("disbandParty", this); } catch (ScriptException | NoSuchMethodException ex) { ex.printStackTrace(); } } //Separate function to warp players to a "finish" map, if applicable public final void finishPQ() { try { this.eventManager.getInvocable().invokeFunction("clearPQ", this); } catch (ScriptException | NoSuchMethodException ex) { ex.printStackTrace(); } } public final void removePlayer(final ChannelCharacter chr) { try { this.eventManager.getInvocable().invokeFunction("playerExit", this, chr); } catch (ScriptException | NoSuchMethodException ex) { ex.printStackTrace(); } } public final void registerCarnivalParty(final ChannelCharacter leader, final GameMap map, final byte team) { leader.clearCarnivalRequests(); final List<ChannelCharacter> characters = Lists.newLinkedList(); final Party party = leader.getParty(); if (party == null) { return; } for (final PartyMember pc : party.getMembers()) { final ChannelCharacter c = map.getCharacterById_InMap(pc.getCharacterId()); characters.add(c); this.registerPlayer(c); c.resetCP(); } final CarnivalParty carnivalParty = new CarnivalParty(leader, characters, team); try { this.eventManager.getInvocable().invokeFunction("registerCarnivalParty", this, carnivalParty); } catch (ScriptException | NoSuchMethodException ex) { ex.printStackTrace(); } } public void onMapLoad(final ChannelCharacter chr) { try { this.eventManager.getInvocable().invokeFunction("onMapLoad", this, chr); } catch (final ScriptException ex) { ex.printStackTrace(); } catch (final NoSuchMethodException ex) { // Ignore, we don't want to update this for all events. } } public boolean isLeader(final ChannelCharacter chr) { return chr.getParty().getLeader().getCharacterId() == chr.getId(); } public void registerSquad(final Squad squad, final GameMap map) { final int mapid = map.getId(); for (final ChannelCharacter player : squad.getMembers()) { if (player != null && player.getMapId() == mapid) { this.registerPlayer(player); } } } }