package javastory.channel.maps; import java.awt.Point; import java.awt.Rectangle; import java.rmi.RemoteException; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import javastory.channel.ChannelCharacter; import javastory.channel.ChannelClient; import javastory.channel.ChannelServer; import javastory.channel.Party; import javastory.channel.PartyMember; import javastory.channel.client.BuffStat; import javastory.channel.client.MonsterStatus; import javastory.channel.client.MonsterStatusEffect; import javastory.channel.client.Pet; import javastory.channel.life.LifeFactory; import javastory.channel.life.Monster; import javastory.channel.life.Npc; import javastory.channel.life.SpawnPointAreaBoss; import javastory.channel.life.Spawnable; import javastory.channel.packet.MobPacket; import javastory.channel.packet.PetPacket; import javastory.channel.server.InventoryManipulator; import javastory.channel.server.Portal; import javastory.channel.server.StatEffect; import javastory.game.Equip; import javastory.game.GameConstants; import javastory.game.IdQuantityEntry; import javastory.game.InventoryType; import javastory.game.Item; import javastory.game.Jobs; import javastory.game.SpawnPoint; import javastory.game.data.ItemInfoProvider; import javastory.game.data.MobDropInfo; import javastory.game.data.MobDropInfoProvider; import javastory.game.data.MobGlobalDropInfo; import javastory.io.GamePacket; import javastory.scripting.EventInstanceManager; import javastory.tools.LogUtil; import javastory.tools.Randomizer; import javastory.tools.packets.ChannelPackets; import javastory.world.core.PartyOperation; import com.google.common.collect.Lists; import com.google.common.collect.Maps; public class GameMap { private final Map<Integer, GameMapObject> mapObjects; private final Collection<Spawnable> monsterSpawn; private final List<ChannelCharacter> characters; private final Map<Integer, Portal> portals; private final List<Rectangle> areas; private FootholdTree footholds = null; private final float monsterRate; private float recoveryRate; private GameMapEffect mapEffect; private short decHP = 0, createMobInterval = 9000; private int protectItem = 0; private final int mapId; private final int returnMapId; private int timeLimit; private int fieldLimit; private int maxRegularSpawn = 0; private int runningOid = 100000, forcedReturnMap = 999999999; private boolean town, clock, personalShop, isEverlast = false, dropsDisabled = false; private String onUserEnter, onFirstUserEnter; private final AtomicInteger spawnedMonstersOnMap = new AtomicInteger(0); private final Lock mutex = new ReentrantLock(); public GameMap(final int mapId, final int returnMapId, final float monsterRate) { this.mapId = mapId; this.returnMapId = returnMapId; this.monsterRate = monsterRate; this.mapObjects = Maps.newHashMap(); this.monsterSpawn = Lists.newLinkedList(); this.characters = Lists.newArrayList(); this.portals = Maps.newHashMap(); this.areas = Lists.newArrayList(); } public final void toggleDrops() { this.dropsDisabled = !this.dropsDisabled; } public final int getId() { return this.mapId; } public final GameMap getReturnMap() { return ChannelServer.getMapFactory().getMap(this.returnMapId); } public final int getReturnMapId() { return this.returnMapId; } public final int getForcedReturnId() { return this.forcedReturnMap; } public final GameMap getForcedReturnMap() { return ChannelServer.getMapFactory().getMap(this.forcedReturnMap); } public final void setForcedReturnMap(final int map) { this.forcedReturnMap = map; } public final float getRecoveryRate() { return this.recoveryRate; } public final void setRecoveryRate(final float recoveryRate) { this.recoveryRate = recoveryRate; } public final int getFieldLimit() { return this.fieldLimit; } public final void setFieldLimit(final int fieldLimit) { this.fieldLimit = fieldLimit; } public final void setCreateMobInterval(final short createMobInterval) { this.createMobInterval = createMobInterval; } public final void setTimeLimit(final int timeLimit) { this.timeLimit = timeLimit; } public final void setFirstUserEnter(final String onFirstUserEnter) { this.onFirstUserEnter = onFirstUserEnter; } public final void setUserEnter(final String onUserEnter) { this.onUserEnter = onUserEnter; } public final boolean hasClock() { return this.clock; } public final void setClock(final boolean hasClock) { this.clock = hasClock; } public final boolean isTown() { return this.town; } public final void setTown(final boolean town) { this.town = town; } public final boolean allowPersonalShop() { return this.personalShop; } public final void setPersonalShop(final boolean personalShop) { this.personalShop = personalShop; } public final void setEverlast(final boolean everlast) { this.isEverlast = everlast; } public final boolean isEverlast() { return this.isEverlast; } public final int getHPDec() { return this.decHP; } public final void setHPDec(final int delta) { this.decHP = (short) delta; } public final int getHPDecProtect() { return this.protectItem; } public final void setHPDecProtect(final int delta) { this.protectItem = delta; } public final int getCurrentPartyId() { this.mutex.lock(); try { final Iterator<ChannelCharacter> ltr = this.characters.iterator(); ChannelCharacter chr; while (ltr.hasNext()) { chr = ltr.next(); final PartyMember member = chr.getPartyMembership(); if (member != null) { return member.getPartyId(); } } } finally { this.mutex.unlock(); } return -1; } public final void addMapObject(final GameMapObject mapobject) { this.mutex.lock(); try { this.runningOid++; mapobject.setObjectId(this.runningOid); this.mapObjects.put(this.runningOid, mapobject); } finally { this.mutex.unlock(); } } private void spawnAndAddRangedMapObject(final GameMapObject mapobject, final DelayedPacketCreation packetbakery, final SpawnCondition condition) { this.mutex.lock(); try { this.runningOid++; mapobject.setObjectId(this.runningOid); this.mapObjects.put(this.runningOid, mapobject); final Iterator<ChannelCharacter> ltr = this.characters.iterator(); ChannelCharacter chr; while (ltr.hasNext()) { chr = ltr.next(); if (condition == null || condition.canSpawn(chr)) { if (chr.getPosition().distanceSq(mapobject.getPosition()) <= GameConstants.maxViewRangeSq()) { packetbakery.sendPackets(chr.getClient()); chr.addVisibleMapObject(mapobject); } } } } finally { this.mutex.unlock(); } } public final void removeMapObject(final int num) { this.mutex.lock(); try { this.mapObjects.remove(Integer.valueOf(num)); } finally { this.mutex.unlock(); } } public final void removeMapObject(final GameMapObject obj) { this.mutex.lock(); try { this.mapObjects.remove(Integer.valueOf(obj.getObjectId())); } finally { this.mutex.unlock(); } } public final Point calcPointBelow(final Point initial) { final Foothold fh = this.footholds.findBelow(initial); if (fh == null) { return null; } int dropY = fh.getY1(); if (!fh.isWall() && fh.getY1() != fh.getY2()) { final double s1 = Math.abs(fh.getY2() - fh.getY1()); final double s2 = Math.abs(fh.getX2() - fh.getX1()); if (fh.getY2() < fh.getY1()) { dropY = fh.getY1() - (int) (Math.cos(Math.atan(s2 / s1)) * (Math.abs(initial.x - fh.getX1()) / Math.cos(Math.atan(s1 / s2)))); } else { dropY = fh.getY1() + (int) (Math.cos(Math.atan(s2 / s1)) * (Math.abs(initial.x - fh.getX1()) / Math.cos(Math.atan(s1 / s2)))); } } return new Point(initial.x, dropY); } private Point calcDropPos(final Point initial, final Point fallback) { final Point ret = this.calcPointBelow(new Point(initial.x, initial.y - 50)); if (ret == null) { return fallback; } return ret; } private void dropFromMonster(final ChannelCharacter chr, final Monster mob) { if (this.dropsDisabled || mob.dropsDisabled()) { return; } final ItemInfoProvider ii = ItemInfoProvider.getInstance(); final ItemDropType droptype; if (mob.getStats().isExplosiveReward()) { droptype = ItemDropType.EXPLOSIVE; } else if (mob.getStats().isFreeForAllLoot()) { droptype = ItemDropType.FREE_FOR_ALL; } else if (chr.hasParty()) { droptype = ItemDropType.PARTY; } else { droptype = ItemDropType.DEFAULT; } final int mobpos = mob.getPosition().x; final float globalItemRate = ChannelServer.getInstance().getItemRate(); byte d = 1; final Point pos = new Point(0, mob.getPosition().y); final MobDropInfoProvider mi = MobDropInfoProvider.getInstance(); final List<MobDropInfo> dropEntry = Lists.newArrayList(mi.retrieveDrop(mob.getId())); Collections.shuffle(dropEntry); for (final MobDropInfo de : dropEntry) { if (Randomizer.nextInt(999999) < de.Chance * globalItemRate) { if (droptype == ItemDropType.EXPLOSIVE) { pos.x = mobpos + (d % 2 == 0 ? 40 * (d + 1) / 2 : -(40 * (d / 2))); } else { pos.x = mobpos + (d % 2 == 0 ? 25 * (d + 1) / 2 : -(25 * (d / 2))); } if (de.ItemId == 0) { // meso int mesos = Randomizer.nextInt(de.Maximum - de.Minimum) + de.Minimum; if (mesos > 0) { if (chr.getBuffedValue(BuffStat.MESOUP) != null) { mesos = (int) (mesos * chr.getBuffedValue(BuffStat.MESOUP).doubleValue() / 100.0); } final float mesoRate = ChannelServer.getInstance().getMesoRate(); this.spawnMobMesoDrop((int) (mesos * mesoRate), this.calcDropPos(pos, mob.getPosition()), mob, chr, false, droptype); } } else { final Item idrop; if (GameConstants.getInventoryType(de.ItemId) == InventoryType.EQUIP) { idrop = ii.randomizeStats((Equip) ii.getEquipById(de.ItemId)); } else { final short quantity = (short) (de.Maximum != 1 ? Randomizer.nextInt(de.Maximum - de.Minimum) + de.Minimum : 1); idrop = new Item(de.ItemId, (byte) 0, quantity, (byte) 0); } this.spawnMobDrop(idrop, this.calcDropPos(pos, mob.getPosition()), mob, chr, droptype, de.QuestId); } d++; } } final List<MobGlobalDropInfo> globalEntry = mi.getGlobalDrop(); // Global Drops for (final MobGlobalDropInfo de : globalEntry) { if (Randomizer.nextInt(999999) < de.Chance) { if (droptype == ItemDropType.EXPLOSIVE) { pos.x = mobpos + (d % 2 == 0 ? 40 * (d + 1) / 2 : -(40 * (d / 2))); } else { pos.x = mobpos + (d % 2 == 0 ? 25 * (d + 1) / 2 : -(25 * (d / 2))); } if (de.ItemId == 0) { // Random Cash xD int cashGain; cashGain = (int) (Math.random() * 100); if (cashGain < 20) { cashGain = 20; chr.modifyCSPoints(1, cashGain, true); } else { chr.modifyCSPoints(1, cashGain, true); } } else { final Item idrop; if (GameConstants.getInventoryType(de.ItemId) == InventoryType.EQUIP) { idrop = ii.randomizeStats((Equip) ii.getEquipById(de.ItemId)); } else { final short quantity = (short) (de.Maximum != 1 ? Randomizer.nextInt(de.Maximum - de.Minimum) + de.Minimum : 1); idrop = new Item(de.ItemId, (byte) 0, quantity, (byte) 0); } this.spawnMobDrop(idrop, this.calcDropPos(pos, mob.getPosition()), mob, chr, droptype, de.QuestId); } d++; } } } private void killMonster(final Monster monster) { // For mobs with removeAfter this.spawnedMonstersOnMap.decrementAndGet(); monster.setHp(0); monster.spawnRevives(this); this.broadcastMessage(MobPacket.killMonster(monster.getObjectId(), 1)); this.removeMapObject(monster); } public final void killMonster(final Monster monster, final ChannelCharacter chr, final boolean withDrops, final boolean second, final byte animation) { if (monster.getId() == 8810018 && !second) { MapTimer.getInstance().schedule(new Runnable() { @Override public void run() { GameMap.this.killMonster(monster, chr, true, true, (byte) 1); GameMap.this.killAllMonsters(true); } }, 3000); return; } this.spawnedMonstersOnMap.decrementAndGet(); this.removeMapObject(monster); ChannelCharacter dropOwner = monster.killBy(chr); this.broadcastMessage(MobPacket.killMonster(monster.getObjectId(), animation)); if (monster.getBuffToGive() > -1) { final int buffid = monster.getBuffToGive(); final StatEffect buff = ItemInfoProvider.getInstance().getItemEffect(buffid); for (final GameMapObject mmo : this.getAllPlayer()) { final ChannelCharacter c = (ChannelCharacter) mmo; if (c.isAlive()) { buff.applyTo(c); switch (monster.getId()) { case 8810018: case 8820001: // HT nine spirit c.getClient().write(ChannelPackets.showOwnBuffEffect(buffid, 11)); this.broadcastMessage(c, ChannelPackets.showBuffeffect(c.getId(), buffid, 11), false); break; } } } } final int mobid = monster.getId(); if (mobid == 8810018) { try { ChannelServer.getWorldInterface().broadcastMessage( ChannelPackets.serverNotice(6, "To the crew that have finally conquered Horned Tail after numerous attempts, I salute thee! You are the true heroes of Leafre!!")); } catch (final RemoteException e) { ChannelServer.pingWorld(); } LogUtil.log(LogUtil.Horntail_Log, this.MapDebug_Log()); } else if (mobid == 8820001) { try { ChannelServer.getWorldInterface().broadcastMessage( ChannelPackets.serverNotice(6, "Expedition who defeated Pink Bean with invicible passion! You are the true timeless hero!")); } catch (final RemoteException e) { ChannelServer.pingWorld(); } LogUtil.log(LogUtil.Pinkbean_Log, this.MapDebug_Log()); } else if (mobid >= 8800003 && mobid <= 8800010) { boolean makeZakReal = true; final Collection<GameMapObject> objects = this.getAllMonster(); for (final GameMapObject object : objects) { final Monster mons = (Monster) object; if (mons.getId() >= 8800003 && mons.getId() <= 8800010) { makeZakReal = false; break; } } if (makeZakReal) { for (final GameMapObject object : objects) { final Monster mons = (Monster) object; if (mons.getId() == 8800000) { final Point pos = mons.getPosition(); this.killAllMonsters(true); this.spawnMonsterOnGroundBelow(LifeFactory.getMonster(8800000), pos); break; } } } } if (withDrops) { if (dropOwner == null) { dropOwner = chr; } this.dropFromMonster(dropOwner, monster); } } public final void killAllMonsters(final boolean animate) { for (final GameMapObject monstermo : this.getAllMonster()) { final Monster monster = (Monster) monstermo; this.spawnedMonstersOnMap.decrementAndGet(); monster.setHp(0); this.broadcastMessage(MobPacket.killMonster(monster.getObjectId(), animate ? 1 : 0)); this.removeMapObject(monster); } } public final void killMonster(final int monsId) { for (final GameMapObject mmo : this.getAllMonster()) { if (((Monster) mmo).getId() == monsId) { this.spawnedMonstersOnMap.decrementAndGet(); this.removeMapObject(mmo); this.broadcastMessage(MobPacket.killMonster(mmo.getObjectId(), 1)); break; } } } private String MapDebug_Log() { final StringBuilder sb = new StringBuilder("Defeat time : "); sb.append(LogUtil.CurrentReadable_Time()); sb.append(" | Mapid : ").append(this.mapId); final List<GameMapObject> players = this.getAllPlayer(); sb.append(" Users [").append(players.size()).append("] | "); final Iterator<GameMapObject> itr = players.iterator(); while (itr.hasNext()) { sb.append(((ChannelCharacter) itr.next()).getName()).append(", "); } return sb.toString(); } public final void destroyReactor(final int oid) { final Reactor reactor = this.getReactorByOid(oid); this.broadcastMessage(ChannelPackets.destroyReactor(reactor)); reactor.setAlive(false); this.removeMapObject(reactor); reactor.setTimerActive(false); if (reactor.getDelay() > 0) { MapTimer.getInstance().schedule(new Runnable() { @Override public final void run() { GameMap.this.respawnReactor(reactor); } }, reactor.getDelay()); } } /* * command to reset all item-reactors in a map to state 0 for GM/NPC use - * not tested (broken reactors get removed from mapobjects when destroyed) * Should create instances for multiple copies of non-respawning reactors... */ public final void resetReactors() { for (final GameMapObject obj : this.getAllReactor()) { final Reactor reactor = (Reactor) obj; reactor.setStateId((byte) 0); reactor.setTimerActive(false); this.broadcastMessage(ChannelPackets.triggerReactor(reactor, 0)); } } public final void setReactorState() { for (final GameMapObject obj : this.getAllReactor()) { final Reactor reactor = (Reactor) obj; reactor.setStateId((byte) 1); reactor.setTimerActive(false); this.broadcastMessage(ChannelPackets.triggerReactor(reactor, 1)); } } /* * command to shuffle the positions of all reactors in a map for PQ purposes * (such as ZPQ/LMPQ) */ public final void shuffleReactors() { final List<Point> points = Lists.newArrayList(); for (final GameMapObject obj : this.getAllReactor()) { final Reactor reactor = (Reactor) obj; points.add(reactor.getPosition()); } Collections.shuffle(points); for (final GameMapObject obj : this.getAllReactor()) { final Reactor reactor = (Reactor) obj; reactor.setPosition(points.remove(points.size() - 1)); } } /** * Automagically finds a new controller for the given monster from the chars * on the map... * * @param monster */ public final void updateMonsterController(final Monster monster) { if (!monster.isAlive()) { return; } if (monster.getController() != null) { if (monster.getController().getMap() != this) { monster.getController().stopControllingMonster(monster); } else { // Everything is fine :) return; } } int mincontrolled = -1; ChannelCharacter newController = null; this.mutex.lock(); try { final Iterator<ChannelCharacter> ltr = this.characters.iterator(); ChannelCharacter chr; while (ltr.hasNext()) { chr = ltr.next(); if (!chr.isHidden() && (chr.getControlledMonsters().size() < mincontrolled || mincontrolled == -1)) { mincontrolled = chr.getControlledMonsters().size(); newController = chr; } } } finally { this.mutex.unlock(); } if (newController != null) { if (monster.isFirstAttack()) { newController.controlMonster(monster, true); monster.setControllerHasAggro(true); monster.setControllerKnowsAboutAggro(true); } else { newController.controlMonster(monster, false); } } } /* * public Collection<MapleMapObject> getMapObjects() { return * Collections.unmodifiableCollection(mapobjects.values()); } */ public final GameMapObject getMapObject(final int oid) { return this.mapObjects.get(oid); } public final int containsNPC(final int npcid) { for (final GameMapObject obj : this.getAllNPC()) { if (((Npc) obj).getId() == npcid) { return obj.getObjectId(); } } return -1; } /** * returns a monster with the given oid, if no such monster exists returns * null * * @param oid * @return */ public final Monster getMonsterByOid(final int oid) { final GameMapObject obj = this.getMapObject(oid); if (obj == null) { return null; } if (obj.getType() == GameMapObjectType.MONSTER) { return (Monster) obj; } return null; } public final Npc getNPCByOid(final int oid) { final GameMapObject obj = this.getMapObject(oid); if (obj == null) { return null; } if (obj.getType() == GameMapObjectType.NPC) { return (Npc) obj; } return null; } public final Reactor getReactorByOid(final int oid) { final GameMapObject obj = this.getMapObject(oid); if (obj == null) { return null; } if (obj.getType() == GameMapObjectType.REACTOR) { return (Reactor) obj; } return null; } public final Reactor getReactorByName(final String name) { for (final GameMapObject obj : this.getAllReactor()) { final Reactor reactor = (Reactor) obj; if (reactor.getName().equals(name)) { return reactor; } } return null; } public final void spawnNpc(final int id, final Point pos) { final Npc npc = LifeFactory.getNpc(id); npc.setPosition(pos); npc.setCy(pos.y); npc.setRx0(pos.x + 50); npc.setRx1(pos.x - 50); npc.setFoothold(this.getFootholds().findBelow(pos).getId()); npc.setCustom(true); this.addMapObject(npc); this.broadcastMessage(ChannelPackets.spawnNpc(npc, true)); } public final void removeNpc(final int id) { final List<GameMapObject> npcs = this.getAllNPC(); for (final GameMapObject npcmo : npcs) { final Npc npc = (Npc) npcmo; if (npc.isCustom() && npc.getId() == id) { this.broadcastMessage(ChannelPackets.removeNpc(npc.getObjectId())); this.removeMapObject(npc.getObjectId()); } } } public final void spawnMonster_sSack(final Monster mob, final Point pos, final int spawnType) { final Point spos = this.calcPointBelow(new Point(pos.x, pos.y - 1)); mob.setPosition(spos); this.spawnMonster(mob, spawnType); } public final void spawnMonsterOnGroundBelow(final Monster mob, final Point pos) { final Point spos = this.calcPointBelow(new Point(pos.x, pos.y - 1)); mob.setPosition(spos); this.spawnMonster(mob, -2); } public final void spawnZakum(final Point pos) { final Monster mainb = LifeFactory.getMonster(8800000); final Point spos = this.calcPointBelow(new Point(pos.x, pos.y - 1)); mainb.setPosition(spos); mainb.setFake(true); // Might be possible to use the map object for reference in future. this.spawnFakeMonster(mainb); final int[] zakpart = { 8800003, 8800004, 8800005, 8800006, 8800007, 8800008, 8800009, 8800010 }; for (final int i : zakpart) { final Monster part = LifeFactory.getMonster(i); part.setPosition(spos); this.spawnMonster(part, -2); } } public final void spawnFakeMonsterOnGroundBelow(final Monster mob, final Point pos) { Point spos = new Point(pos.x, pos.y - 1); spos = this.calcPointBelow(spos); spos.y -= 1; mob.setPosition(spos); this.spawnFakeMonster(mob); } private void checkRemoveAfter(final Monster monster) { final int ra = monster.getStats().getRemoveAfter(); if (ra > 0) { MapTimer.getInstance().schedule(new Runnable() { @Override public final void run() { if (monster != null) { GameMap.this.killMonster(monster); } } }, ra * 1000); } } public final void spawnRevives(final Monster monster, final int oid) { monster.setMap(this); this.checkRemoveAfter(monster); this.spawnAndAddRangedMapObject(monster, new MonsterSpawnTask(oid, monster), null); this.updateMonsterController(monster); this.spawnedMonstersOnMap.incrementAndGet(); } public final void spawnMonster(final Monster monster, final int spawnType) { monster.setMap(this); this.checkRemoveAfter(monster); this.spawnAndAddRangedMapObject(monster, new DelayedPacketCreation() { @Override public final void sendPackets(final ChannelClient c) { c.write(MobPacket.spawnMonster(monster, spawnType, 0, 0)); } }, null); this.updateMonsterController(monster); this.spawnedMonstersOnMap.incrementAndGet(); } public final void spawnMonsterWithEffect(final Monster monster, final int effect, final Point pos) { try { monster.setMap(this); monster.setPosition(pos); this.spawnAndAddRangedMapObject(monster, new DelayedPacketCreation() { @Override public final void sendPackets(final ChannelClient c) { c.write(MobPacket.spawnMonster(monster, -2, effect, 0)); } }, null); this.updateMonsterController(monster); this.spawnedMonstersOnMap.incrementAndGet(); } catch (final Exception e) { } } public final void spawnFakeMonster(final Monster monster) { monster.setMap(this); monster.setFake(true); this.spawnAndAddRangedMapObject(monster, new DelayedPacketCreation() { @Override public final void sendPackets(final ChannelClient c) { c.write(MobPacket.spawnMonster(monster, -2, 0xfc, 0)); // c.write(MobPacket.spawnFakeMonster(monster, 0)); } }, null); this.updateMonsterController(monster); this.spawnedMonstersOnMap.incrementAndGet(); } public final void spawnReactor(final Reactor reactor) { reactor.setMap(this); this.spawnAndAddRangedMapObject(reactor, new DelayedPacketCreation() { @Override public final void sendPackets(final ChannelClient c) { c.write(ChannelPackets.spawnReactor(reactor)); } }, null); } private void respawnReactor(final Reactor reactor) { reactor.setStateId((byte) 0); reactor.setAlive(true); this.spawnReactor(reactor); } public final void spawnDoor(final Door door) { this.spawnAndAddRangedMapObject(door, new DelayedPacketCreation() { @Override public final void sendPackets(final ChannelClient c) { final ChannelCharacter doorOwner = door.getOwner(); c.write(ChannelPackets.spawnDoor(doorOwner.getId(), door.getTargetPosition(), false)); final ChannelCharacter clientPlayer = c.getPlayer(); final boolean isOwner = doorOwner == clientPlayer; final PartyMember ownerMember = doorOwner.getPartyMembership(); final PartyMember clientMember = clientPlayer.getPartyMembership(); final boolean isSameParty = ownerMember != null && clientMember != null && ownerMember.getPartyId() == clientMember.getPartyId(); if (isOwner || isSameParty) { c.write(ChannelPackets.partyPortal(door.getTown().getId(), door.getTarget().getId(), door.getTargetPosition())); } c.write(ChannelPackets.spawnPortal(door.getTown().getId(), door.getTarget().getId(), door.getTargetPosition())); c.write(ChannelPackets.enableActions()); } }, new SpawnCondition() { @Override public final boolean canSpawn(final ChannelCharacter chr) { return chr.getMapId() == door.getTarget().getId() || chr == door.getOwner() && chr.hasParty(); } }); } public final void spawnDragon(final Dragon summon) { this.spawnAndAddRangedMapObject(summon, new DelayedPacketCreation() { @Override public void sendPackets(final ChannelClient c) { c.write(ChannelPackets.spawnDragon(summon)); } }, null); } public final void spawnSummon(final Summon summon) { this.spawnAndAddRangedMapObject(summon, new DelayedPacketCreation() { @Override public void sendPackets(final ChannelClient c) { c.write(ChannelPackets.spawnSummon(summon, summon.getSkillLevel(), true)); } }, null); } public final void spawnMist(final Mist mist, final int duration, final boolean poison, final boolean fake) { this.spawnAndAddRangedMapObject(mist, new DelayedPacketCreation() { @Override public void sendPackets(final ChannelClient c) { c.write(ChannelPackets.spawnMist(mist)); } }, null); final MapTimer timer = MapTimer.getInstance(); final ScheduledFuture<?> poisonSchedule; if (poison) { poisonSchedule = timer.register(new Runnable() { @Override public void run() { for (final GameMapObject mo : GameMap.this.getMapObjectsInRect(mist.getBox(), Collections.singletonList(GameMapObjectType.MONSTER))) { if (mist.makeChanceResult()) { ((Monster) mo).applyStatus(mist.getOwner(), new MonsterStatusEffect(Collections.singletonMap(MonsterStatus.POISON, 1), mist .getSourceSkill(), null, false), true, duration, false); } } } }, 2000, 2500); } else { poisonSchedule = null; } timer.schedule(new Runnable() { @Override public void run() { GameMap.this.broadcastMessage(ChannelPackets.removeMist(mist.getObjectId())); GameMap.this.removeMapObject(mist); if (poisonSchedule != null) { poisonSchedule.cancel(false); } } }, duration); } public final void disappearingItemDrop(final GameMapObject dropper, final ChannelCharacter owner, final Item item, final Point pos) { final Point droppos = this.calcDropPos(pos, pos); final GameMapItem drop = GameMapItem.item(item, droppos, dropper, owner.getId(), ItemDropType.PARTY, false); this.broadcastMessage(ChannelPackets.dropItemFromMapObject(drop, dropper.getPosition(), droppos, (byte) 3), drop.getPosition()); } public final void spawnMesoDrop(final int meso, final Point position, final GameMapObject dropper, final ChannelCharacter owner, final boolean playerDrop, final ItemDropType droptype) { final Point droppos = this.calcDropPos(position, position); final GameMapItem mdrop = GameMapItem.meso(meso, droppos, dropper, owner.getId(), droptype, playerDrop); this.spawnAndAddRangedMapObject(mdrop, new DelayedPacketCreation() { @Override public void sendPackets(final ChannelClient c) { c.write(ChannelPackets.dropItemFromMapObject(mdrop, dropper.getPosition(), droppos, (byte) 1)); } }, null); if (!this.isEverlast) { MapTimer.getInstance().schedule(new ExpireMapItemJob(mdrop), 180000); } } public final void spawnMobMesoDrop(final int meso, final Point position, final GameMapObject dropper, final ChannelCharacter owner, final boolean playerDrop, final ItemDropType droptype) { final GameMapItem mdrop = GameMapItem.meso(meso, position, dropper, owner.getId(), droptype, playerDrop); this.spawnAndAddRangedMapObject(mdrop, new DelayedPacketCreation() { @Override public void sendPackets(final ChannelClient c) { c.write(ChannelPackets.dropItemFromMapObject(mdrop, dropper.getPosition(), position, (byte) 1)); } }, null); MapTimer.getInstance().schedule(new ExpireMapItemJob(mdrop), 180000); } private void spawnMobDrop(final Item item, final Point dropPos, final Monster mob, final ChannelCharacter owner, final ItemDropType droptype, final short questId) { final GameMapItem mdrop = GameMapItem.questItem(item, dropPos, mob, owner.getId(), droptype, false, questId); this.spawnAndAddRangedMapObject(mdrop, new DelayedPacketCreation() { @Override public void sendPackets(final ChannelClient c) { if (questId <= 0 || c.getPlayer().getQuestCompletionStatus(questId) == 1) { c.write(ChannelPackets.dropItemFromMapObject(mdrop, mob.getPosition(), dropPos, (byte) 1)); } } }, null); MapTimer.getInstance().schedule(new ExpireMapItemJob(mdrop), 180000); this.activateItemReactors(mdrop, owner.getClient()); } public final void spawnItemDrop(final GameMapObject dropper, final ChannelCharacter owner, final Item item, final Point pos, final boolean ffaDrop, final boolean playerDrop) { final Point droppos = this.calcDropPos(pos, pos); final GameMapItem drop = GameMapItem.item(item, droppos, dropper, owner.getId(), ItemDropType.DEFAULT, false); this.spawnAndAddRangedMapObject(drop, new DelayedPacketCreation() { @Override public void sendPackets(final ChannelClient c) { c.write(ChannelPackets.dropItemFromMapObject(drop, dropper.getPosition(), droppos, (byte) 1)); } }, null); this.broadcastMessage(ChannelPackets.dropItemFromMapObject(drop, dropper.getPosition(), droppos, (byte) 0)); if (!this.isEverlast) { MapTimer.getInstance().schedule(new ExpireMapItemJob(drop), 180000); this.activateItemReactors(drop, owner.getClient()); } } private void activateItemReactors(final GameMapItem drop, final ChannelClient c) { final Item item = drop.getItem(); for (final GameMapObject o : this.getAllReactor()) { final Reactor react = (Reactor) o; if (react.getReactorType() == 100) { final IdQuantityEntry itemCluster = react.getReactItem(); if (itemCluster.Id == item.getItemId() && react.getReactItem().Quantity == item.getQuantity()) { if (react.getArea().contains(drop.getPosition())) { if (!react.isTimerActive()) { MapTimer.getInstance().schedule(new ActivateItemReactor(drop, react, c), 5000); react.setTimerActive(true); break; } } } } } } public final void AriantPQStart() { int i = 1; this.mutex.lock(); try { final Iterator<ChannelCharacter> ltr = this.characters.iterator(); ChannelCharacter chars; while (ltr.hasNext()) { chars = ltr.next(); this.broadcastMessage(ChannelPackets.updateAriantPQRanking(chars.getName(), 0, false)); this.broadcastMessage(ChannelPackets.serverNotice(0, ChannelPackets.updateAriantPQRanking(chars.getName(), 0, false).toString())); if (this.getCharacterCount() > i) { this.broadcastMessage(ChannelPackets.updateAriantPQRanking(null, 0, true)); this.broadcastMessage(ChannelPackets.serverNotice(0, ChannelPackets.updateAriantPQRanking(chars.getName(), 0, true).toString())); } i++; } } finally { this.mutex.unlock(); } } public final void returnEverLastItem(final ChannelCharacter chr) { for (final GameMapObject o : this.getAllItems()) { final GameMapItem item = (GameMapItem) o; if (item.getOwner() == chr.getId()) { item.setPickedUp(true); this.broadcastMessage(ChannelPackets.removeItemFromMap(item.getObjectId(), 2, chr.getId()), item.getPosition()); if (item.getMeso() > 0) { chr.gainMeso(item.getMeso(), false); } else { InventoryManipulator.addFromDrop(chr.getClient(), item.getItem(), false); } this.removeMapObject(item); } } } public final void startMapEffect(final String msg, final int itemId) { if (this.mapEffect != null) { return; } this.mapEffect = new GameMapEffect(msg, itemId); this.broadcastMessage(this.mapEffect.makeStartData()); MapTimer.getInstance().schedule(new Runnable() { @Override public void run() { GameMap.this.broadcastMessage(GameMap.this.mapEffect.makeDestroyData()); GameMap.this.mapEffect = null; } }, 30000); } public final void addPlayer(final ChannelCharacter character) { this.mutex.lock(); try { this.characters.add(character); this.mapObjects.put(character.getObjectId(), character); } finally { this.mutex.unlock(); } if (!character.isHidden()) { this.broadcastMessage(ChannelPackets.spawnPlayerMapObject(character)); } this.sendObjectPlacement(character); final ChannelClient client = character.getClient(); client.write(ChannelPackets.spawnPlayerMapObject(character)); if (!this.onFirstUserEnter.equals("")) { if (this.getCharacterCount() == 1) { MapScriptMethods.startScript_FirstUser(client, this.onFirstUserEnter); } } if (!this.onUserEnter.equals("")) { MapScriptMethods.startScript_User(client, this.onUserEnter); } for (final Pet pet : character.getPets()) { if (pet.isSummoned()) { final GamePacket packet = PetPacket.showPet(character, pet); this.broadcastMessage(character, packet, false); } } switch (this.mapId) { case 809000101: case 809000201: client.write(ChannelPackets.showEquipEffect()); break; } if (this.getHPDec() > 0) { character.startHurtHp(); } final PartyMember member = character.getPartyMembership(); if (member != null) { try { final Party party = ChannelServer.getWorldInterface().getParty(member.getPartyId()); character.silentPartyUpdate(); client.write(ChannelPackets.updateParty(client.getChannelId(), party, PartyOperation.SILENT_UPDATE, null)); character.updatePartyMemberHP(); character.receivePartyMemberHP(); } catch (final RemoteException ex) { ex.printStackTrace(); } } final StatEffect stat = character.getStatForBuff(BuffStat.SUMMON); if (stat != null) { final Summon summon = character.getSummons().get(stat.getSourceId()); summon.setPosition(character.getPosition()); character.addVisibleMapObject(summon); this.spawnSummon(summon); } if (this.mapEffect != null) { this.mapEffect.sendStartData(client); } if (this.timeLimit > 0 && this.getForcedReturnMap() != null) { character.startMapTimeLimitTask(this.timeLimit, this.getForcedReturnMap()); } if (character.getBuffedValue(BuffStat.MONSTER_RIDING) != null) { if (FieldLimitType.Mount.check(this.fieldLimit)) { character.cancelBuffStats(BuffStat.MONSTER_RIDING); } } final EventInstanceManager eventInstance = character.getEventInstance(); if (eventInstance != null && eventInstance.isTimerStarted()) { client.write(ChannelPackets.getClock((int) (eventInstance.getTimeLeft() / 1000))); } if (this.hasClock()) { final Calendar calendar = Calendar.getInstance(); client.write(ChannelPackets.getClockTime(calendar)); } if (character.getCarnivalParty() != null && eventInstance != null) { eventInstance.onMapLoad(character); } final int jobId = character.getJobId(); if (Jobs.isEvan(jobId) && jobId >= 2200 && character.getBuffedValue(BuffStat.MONSTER_RIDING) == null) { if (character.getDragon() == null) { character.makeDragon(); } this.spawnDragon(character.getDragon()); this.updateMapObjectVisibility(character, character.getDragon()); } } public final void removePlayer(final ChannelCharacter character) { // log.warn("[dc] [level2] Player {} leaves map {}", new Object[] { // chr.getName(), mapid }); if (this.isEverlast) { this.returnEverLastItem(character); } this.mutex.lock(); try { this.characters.remove(character); } finally { this.mutex.unlock(); } this.removeMapObject(Integer.valueOf(character.getObjectId())); this.broadcastMessage(ChannelPackets.removePlayerFromMap(character.getId())); for (final Monster monster : character.getControlledMonsters()) { monster.setController(null); monster.setControllerHasAggro(false); monster.setControllerKnowsAboutAggro(false); this.updateMonsterController(monster); } character.leaveMap(); character.cancelMapTimeLimitTask(); for (final Summon summon : character.getSummons().values()) { if (summon.isPuppet()) { character.cancelBuffStats(BuffStat.PUPPET); character.cancelBuffStats(BuffStat.MIRROR_TARGET); } else { this.removeMapObject(summon); } } if (character.getDragon() != null) { this.removeMapObject(character.getDragon()); } } public final void broadcastMessage(final GamePacket packet) { this.broadcastMessage(null, packet, Double.POSITIVE_INFINITY, null); } public final void broadcastMessage(final ChannelCharacter source, final GamePacket packet, final boolean repeatToSource) { this.broadcastMessage(repeatToSource ? null : source, packet, Double.POSITIVE_INFINITY, source.getPosition()); } /* * public void broadcastMessage(GameCharacter source, GamePacket packet, * boolean repeatToSource, boolean ranged) { broadcastMessage(repeatToSource * ? null : source, packet, ranged ? GameCharacter.MAX_VIEW_RANGE_SQ : * Double.POSITIVE_INFINITY, source.getPosition()); } */ public final void broadcastMessage(final GamePacket packet, final Point rangedFrom) { this.broadcastMessage(null, packet, GameConstants.maxViewRangeSq(), rangedFrom); } public final void broadcastMessage(final ChannelCharacter source, final GamePacket packet, final Point rangedFrom) { this.broadcastMessage(source, packet, GameConstants.maxViewRangeSq(), rangedFrom); } private void broadcastMessage(final ChannelCharacter source, final GamePacket packet, final double rangeSq, final Point rangedFrom) { this.mutex.lock(); try { final Iterator<ChannelCharacter> ltr = this.characters.iterator(); ChannelCharacter chr; while (ltr.hasNext()) { chr = ltr.next(); if (chr != source) { if (rangeSq < Double.POSITIVE_INFINITY) { if (rangedFrom.distanceSq(chr.getPosition()) <= rangeSq) { chr.getClient().write(packet); } } else { chr.getClient().write(packet); } } } } finally { this.mutex.unlock(); } } private void sendObjectPlacement(final ChannelCharacter c) { if (c == null) { return; } for (final GameMapObject o : this.getAllMonster()) { this.updateMonsterController((Monster) o); } for (final GameMapObject o : this.getMapObjectsInRange(c.getPosition(), GameConstants.maxViewRangeSq(), GameConstants.rangedMapObjectTypes)) { if (o.getType() == GameMapObjectType.REACTOR) { if (!((Reactor) o).isAlive()) { continue; } } o.sendSpawnData(c.getClient()); c.addVisibleMapObject(o); } } public final List<GameMapObject> getMapObjectsInRange(final Point from, final double rangeSq) { final List<GameMapObject> ret = Lists.newLinkedList(); this.mutex.lock(); try { final Iterator<GameMapObject> ltr = this.mapObjects.values().iterator(); GameMapObject obj; while (ltr.hasNext()) { obj = ltr.next(); if (from.distanceSq(obj.getPosition()) <= rangeSq) { ret.add(obj); } } } finally { this.mutex.unlock(); } return ret; } public final List<GameMapObject> getMapObjectsInRange(final Point from, final double rangeSq, final List<GameMapObjectType> MapObject_types) { final List<GameMapObject> ret = Lists.newLinkedList(); this.mutex.lock(); try { final Iterator<GameMapObject> ltr = this.mapObjects.values().iterator(); GameMapObject obj; while (ltr.hasNext()) { obj = ltr.next(); if (MapObject_types.contains(obj.getType())) { if (from.distanceSq(obj.getPosition()) <= rangeSq) { ret.add(obj); } } } } finally { this.mutex.unlock(); } return ret; } public final List<GameMapObject> getMapObjectsInRect(final Rectangle box, final List<GameMapObjectType> MapObject_types) { final List<GameMapObject> ret = Lists.newLinkedList(); this.mutex.lock(); try { final Iterator<GameMapObject> ltr = this.mapObjects.values().iterator(); GameMapObject obj; while (ltr.hasNext()) { obj = ltr.next(); if (MapObject_types.contains(obj.getType())) { if (box.contains(obj.getPosition())) { ret.add(obj); } } } } finally { this.mutex.unlock(); } return ret; } public final List<ChannelCharacter> getPlayersInRect(final Rectangle box, final List<ChannelCharacter> CharacterList) { final List<ChannelCharacter> character = Lists.newLinkedList(); this.mutex.lock(); try { final Iterator<ChannelCharacter> ltr = this.characters.iterator(); ChannelCharacter a; while (ltr.hasNext()) { a = ltr.next(); if (CharacterList.contains(a.getClient().getPlayer())) { if (box.contains(a.getPosition())) { character.add(a); } } } } finally { this.mutex.unlock(); } return character; } public final List<GameMapObject> getAllItems() { return this.getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(GameMapObjectType.ITEM)); } public final List<GameMapObject> getAllNPC() { return this.getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(GameMapObjectType.NPC)); } public final List<GameMapObject> getAllReactor() { return this.getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(GameMapObjectType.REACTOR)); } public final List<GameMapObject> getAllPlayer() { return this.getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(GameMapObjectType.PLAYER)); } public final List<GameMapObject> getAllMonster() { return this.getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(GameMapObjectType.MONSTER)); } public final List<GameMapObject> getAllDoor() { return this.getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(GameMapObjectType.DOOR)); } public final void addPortal(final Portal myPortal) { this.portals.put(myPortal.getId(), myPortal); } public final Portal getPortal(final String portalname) { for (final Portal port : this.portals.values()) { if (port.getName().equals(portalname)) { return port; } } return null; } public final Portal getPortal(final int portalid) { return this.portals.get(portalid); } public final void addArea(final Rectangle rec) { this.areas.add(rec); } public final List<Rectangle> getAreas() { return Lists.newArrayList(this.areas); } public final Rectangle getArea(final int index) { return this.areas.get(index); } public final void setFootholds(final FootholdTree footholds) { this.footholds = footholds; } public final FootholdTree getFootholds() { return this.footholds; } public final void loadMonsterRate(final boolean first) { final int spawnSize = this.monsterSpawn.size(); /* * if (spawnSize >= 25 || monsterRate > 1.5) { maxRegularSpawn = * Math.round(spawnSize / monsterRate); } else { maxRegularSpawn = * Math.round(spawnSize * monsterRate); } */ this.maxRegularSpawn = Math.round(spawnSize * this.monsterRate); if (this.maxRegularSpawn < 2) { this.maxRegularSpawn = 2; } else if (this.maxRegularSpawn > spawnSize) { this.maxRegularSpawn = spawnSize - spawnSize / 15; } final Collection<Spawnable> newSpawn = Lists.newLinkedList(); final Collection<Spawnable> newBossSpawn = Lists.newLinkedList(); for (final Spawnable s : this.monsterSpawn) { if (s.getCarnivalTeam() >= 2) { continue; // Remove carnival spawned mobs } if (s.getMonster().getStats().isBoss()) { newBossSpawn.add(s); } else { newSpawn.add(s); } } this.monsterSpawn.clear(); this.monsterSpawn.addAll(newBossSpawn); this.monsterSpawn.addAll(newSpawn); if (first && spawnSize > 0) { MapTimer.getInstance().register(new Runnable() { @Override public void run() { GameMap.this.respawn(false); } }, this.createMobInterval); } } public final void addMonsterSpawn(final Monster monster, final int mobTime, final byte carnivalTeam, final String msg) { final Point newpos = this.calcPointBelow(monster.getPosition()); newpos.y -= 1; this.monsterSpawn.add(new SpawnPoint(monster, newpos, mobTime, carnivalTeam, msg)); } public final void addAreaMonsterSpawn(final Monster monster, Point pos1, Point pos2, Point pos3, final int mobTime, final String msg) { pos1 = this.calcPointBelow(pos1); pos2 = this.calcPointBelow(pos2); pos3 = this.calcPointBelow(pos3); pos1.y -= 1; pos2.y -= 1; pos3.y -= 1; this.monsterSpawn.add(new SpawnPointAreaBoss(monster, pos1, pos2, pos3, mobTime, msg)); } public final Collection<ChannelCharacter> getCharacters() { final List<ChannelCharacter> chars = Lists.newArrayList(); this.mutex.lock(); try { final Iterator<ChannelCharacter> ltr = this.characters.iterator(); while (ltr.hasNext()) { chars.add(ltr.next()); } } finally { this.mutex.unlock(); } return chars; } public final ChannelCharacter getCharacterById_InMap(final int id) { this.mutex.lock(); try { final Iterator<ChannelCharacter> ltr = this.characters.iterator(); ChannelCharacter c; while (ltr.hasNext()) { c = ltr.next(); if (c.getId() == id) { return c; } } } finally { this.mutex.unlock(); } return null; } private void updateMapObjectVisibility(final ChannelCharacter chr, final GameMapObject mo) { if (!chr.isMapObjectVisible(mo)) { // monster entered view range if (mo.getType() == GameMapObjectType.SUMMON || mo.getPosition().distanceSq(chr.getPosition()) <= GameConstants.maxViewRangeSq()) { chr.addVisibleMapObject(mo); mo.sendSpawnData(chr.getClient()); } } else { // monster left view range if (mo.getType() != GameMapObjectType.SUMMON && mo.getPosition().distanceSq(chr.getPosition()) > GameConstants.maxViewRangeSq()) { chr.removeVisibleMapObject(mo); mo.sendDestroyData(chr.getClient()); } } } public void moveMonster(final Monster monster, final Point reportedPos) { monster.setPosition(reportedPos); this.mutex.lock(); try { final Iterator<ChannelCharacter> ltr = this.characters.iterator(); while (ltr.hasNext()) { this.updateMapObjectVisibility(ltr.next(), monster); } } finally { this.mutex.unlock(); } } public void movePlayer(final ChannelCharacter player, final Point newPosition) { player.setPosition(newPosition); final Collection<GameMapObject> visibleObjects = player.getVisibleMapObjects(); final GameMapObject[] visibleObjectsNow = visibleObjects.toArray(new GameMapObject[visibleObjects.size()]); for (final GameMapObject mo : visibleObjectsNow) { if (this.getMapObject(mo.getObjectId()) == mo) { this.updateMapObjectVisibility(player, mo); } else { player.removeVisibleMapObject(mo); } } for (final GameMapObject mo : this.getMapObjectsInRange(player.getPosition(), GameConstants.maxViewRangeSq())) { if (!player.isMapObjectVisible(mo)) { mo.sendSpawnData(player.getClient()); player.addVisibleMapObject(mo); } } } public Portal findClosestSpawnpoint(final Point from) { Portal closest = null; double distance, shortestDistance = Double.POSITIVE_INFINITY; for (final Portal portal : this.portals.values()) { distance = portal.getPosition().distanceSq(from); if (portal.getType() >= 0 && portal.getType() <= 2 && distance < shortestDistance && portal.getTargetMapId() == 999999999) { closest = portal; shortestDistance = distance; } } return closest; } public String spawnDebug() { final StringBuilder sb = new StringBuilder("Mapobjects in map : "); sb.append(this.getMapObjectCount()); sb.append(" spawnedMonstersOnMap: "); sb.append(this.spawnedMonstersOnMap); sb.append(" spawnpoints: "); sb.append(this.monsterSpawn.size()); sb.append(" maxRegularSpawn: "); sb.append(this.maxRegularSpawn); sb.append(" actual monsters: "); sb.append(this.getAllMonster().size()); return sb.toString(); } public final int getMapObjectCount() { return this.mapObjects.size(); } public final int getCharacterCount() { return this.characters.size(); } public Collection<Portal> getPortals() { return Collections.unmodifiableCollection(this.portals.values()); } public int getSpawnedMonstersOnMap() { return this.spawnedMonstersOnMap.get(); } private static final class MonsterSpawnTask implements DelayedPacketCreation { private final int oid; private final Monster monster; private MonsterSpawnTask(final int oid, final Monster monster) { this.oid = oid; this.monster = monster; } @Override public final void sendPackets(final ChannelClient c) { // TODO: effect c.write(MobPacket.spawnMonster(this.monster, -2, 0, this.oid)); } } private class ExpireMapItemJob implements Runnable { private final GameMapItem mapitem; public ExpireMapItemJob(final GameMapItem mapitem) { this.mapitem = mapitem; } @Override public void run() { if (this.mapitem != null && this.mapitem == GameMap.this.getMapObject(this.mapitem.getObjectId())) { if (this.mapitem.isPickedUp()) { return; } this.mapitem.setPickedUp(true); GameMap.this.broadcastMessage(ChannelPackets.removeItemFromMap(this.mapitem.getObjectId(), 0, 0)); GameMap.this.removeMapObject(this.mapitem); } } } private class ActivateItemReactor implements Runnable { private final GameMapItem mapitem; private final Reactor reactor; private final ChannelClient c; public ActivateItemReactor(final GameMapItem mapitem, final Reactor reactor, final ChannelClient c) { this.mapitem = mapitem; this.reactor = reactor; this.c = c; } @Override public void run() { if (this.mapitem != null && this.mapitem == GameMap.this.getMapObject(this.mapitem.getObjectId())) { if (this.mapitem.isPickedUp()) { this.reactor.setTimerActive(false); return; } this.mapitem.setPickedUp(true); GameMap.this.broadcastMessage(ChannelPackets.removeItemFromMap(this.mapitem.getObjectId(), 0, 0)); GameMap.this.removeMapObject(this.mapitem); this.reactor.hitReactor(this.c); this.reactor.setTimerActive(false); if (this.reactor.getDelay() > 0) { MapTimer.getInstance().schedule(new Runnable() { @Override public void run() { ActivateItemReactor.this.reactor.setStateId((byte) 0); GameMap.this.broadcastMessage(ChannelPackets.triggerReactor(ActivateItemReactor.this.reactor, 0)); } }, this.reactor.getDelay()); } } } } public void respawn(final boolean force) { if (force) { final int numShouldSpawn = this.monsterSpawn.size() - this.spawnedMonstersOnMap.get(); if (numShouldSpawn > 0) { int spawned = 0; for (final Spawnable spawnPoint : this.monsterSpawn) { spawnPoint.spawnMonster(this); spawned++; if (spawned >= numShouldSpawn) { break; } } } } else { if (this.getCharacterCount() <= 0) { return; } final int numShouldSpawn = this.maxRegularSpawn - this.spawnedMonstersOnMap.get(); if (numShouldSpawn > 0) { int spawned = 0; final List<Spawnable> randomSpawn = Lists.newArrayList(this.monsterSpawn); Collections.shuffle(randomSpawn); for (final Spawnable spawnPoint : randomSpawn) { if (spawnPoint.shouldSpawn()) { spawnPoint.spawnMonster(this); spawned++; } if (spawned >= numShouldSpawn) { break; } } } } } private static interface DelayedPacketCreation { void sendPackets(ChannelClient c); } private static interface SpawnCondition { boolean canSpawn(ChannelCharacter chr); } }