/*
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 server.maps;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.Calendar;
import client.Equip;
import client.IItem;
import client.Item;
import client.MapleBuffStat;
import client.MapleCharacter;
import client.MapleClient;
import client.MapleInventoryType;
import client.MaplePet;
import client.status.MonsterStatus;
import client.status.MonsterStatusEffect;
import constants.ItemConstants;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import tools.Output;
import tools.Randomizer;
import net.MaplePacket;
import net.server.Channel;
import net.server.Server;
import scripting.map.MapScriptManager;
import server.MapleItemInformationProvider;
import server.MaplePortal;
import server.MapleStatEffect;
import server.TimerManager;
import server.life.MapleMonster;
import server.life.MapleNPC;
import server.life.SpawnPoint;
import tools.MaplePacketCreator;
import server.events.gm.MapleCoconut;
import server.events.gm.MapleFitness;
import server.events.gm.MapleOla;
import server.events.gm.MapleOxQuiz;
import server.events.gm.MapleSnowball;
import server.partyquest.MonsterCarnival;
import server.partyquest.MonsterCarnivalParty;
import server.life.CoolDamageEntry;
import server.life.MapleLifeFactory;
import server.life.MapleLifeFactory.selfDestruction;
import server.life.MapleMonsterInformationProvider;
import server.life.MonsterDropEntry;
import server.life.MonsterGlobalDropEntry;
import server.partyquest.Pyramid;
public class MapleMap {
private static final List<MapleMapObjectType> rangedMapobjectTypes = Arrays.asList(MapleMapObjectType.SHOP, MapleMapObjectType.ITEM, MapleMapObjectType.NPC, MapleMapObjectType.MONSTER, MapleMapObjectType.DOOR, MapleMapObjectType.SUMMON, MapleMapObjectType.REACTOR);
private Map<Integer, MapleMapObject> mapobjects = new LinkedHashMap<Integer, MapleMapObject>();
private Collection<SpawnPoint> monsterSpawn = Collections.synchronizedList(new LinkedList<SpawnPoint>());
private AtomicInteger spawnedMonstersOnMap = new AtomicInteger(0);
private Collection<MapleCharacter> characters = new LinkedHashSet<MapleCharacter>();
private Map<Integer, MaplePortal> portals = new HashMap<Integer, MaplePortal>();
private List<Rectangle> areas = new ArrayList<Rectangle>();
private MapleFootholdTree footholds = null;
private int mapid;
private int runningOid = 100;
private int returnMapId;
private byte channel, world;
private byte monsterRate;
private boolean clock;
private boolean boat;
private boolean docked;
private String mapName;
private String streetName;
private MapleMapEffect mapEffect = null;
private boolean everlast = false;
private int forcedReturnMap = 999999999;
private long timeLimit;
private int decHP = 0;
private int protectItem = 0;
private boolean town;
private MapleOxQuiz ox;
private boolean isOxQuiz = false;
private boolean dropsOn = true;
private String onFirstUserEnter;
private String onUserEnter;
private int fieldType;
private int fieldLimit = 0;
private int mobCapacity = -1;
private ScheduledFuture<?> mapMonitor = null;
private TimeMobEntry timeMob = null;
private short mobInterval = 5000;
// HPQ
private int riceCakeNum = 0; // bad place to put this (why is it in here
// then)
private boolean allowHPQSummon = false; // bad place to put this
// events
private boolean eventstarted = false;
private MapleSnowball snowball0 = null;
private MapleSnowball snowball1 = null;
private MapleCoconut coconut;
// locks
private final ReadLock chrRLock;
private final WriteLock chrWLock;
private final ReadLock objectRLock;
private final WriteLock objectWLock;
public MapleMap(int mapid, byte world, byte channel, int returnMapId, float monsterRate) {
this.mapid = mapid;
this.channel = channel;
this.world = world;
this.returnMapId = returnMapId;
this.monsterRate = (byte) Math.round(monsterRate);
if (this.monsterRate == 0) {
this.monsterRate = 1;
}
final ReentrantReadWriteLock chrLock = new ReentrantReadWriteLock(true);
chrRLock = chrLock.readLock();
chrWLock = chrLock.writeLock();
final ReentrantReadWriteLock objectLock = new ReentrantReadWriteLock(true);
objectRLock = objectLock.readLock();
objectWLock = objectLock.writeLock();
}
public void broadcastMessage(MapleCharacter source, MaplePacket packet) {
chrRLock.lock();
try {
for (MapleCharacter chr : characters) {
if (chr != source) {
chr.getClient().announce(packet);
}
}
} finally {
chrRLock.unlock();
}
}
public void broadcastGMMessage(MapleCharacter source, MaplePacket packet) {
chrRLock.lock();
try {
for (MapleCharacter chr : characters) {
if (chr != source && (chr.gmLevel() > source.gmLevel())) {
chr.getClient().announce(packet);
}
}
} finally {
chrRLock.unlock();
}
}
public void toggleDrops() {
this.dropsOn = !dropsOn;
}
public List<MapleMapObject> getMapObjectsInRect(Rectangle box, List<MapleMapObjectType> types) {
objectRLock.lock();
final List<MapleMapObject> ret = new LinkedList<MapleMapObject>();
try {
for (MapleMapObject l : mapobjects.values()) {
if (types.contains(l.getType())) {
if (box.contains(l.getPosition())) {
ret.add(l);
}
}
}
} finally {
objectRLock.unlock();
}
return ret;
}
public int getId() {
return mapid;
}
public MapleMap getReturnMap() {
return Server.getInstance().getWorld(world).getChannel(channel).getMapFactory().getMap(returnMapId);
}
public int getReturnMapId() {
return returnMapId;
}
public void setReactorState() {
objectRLock.lock();
try {
for (MapleMapObject o : mapobjects.values()) {
if (o.getType() == MapleMapObjectType.REACTOR) {
if (((MapleReactor) o).getState() < 1) {
((MapleReactor) o).setState((byte) 1);
broadcastMessage(MaplePacketCreator.triggerReactor((MapleReactor) o, 1));
}
}
}
} finally {
objectRLock.unlock();
}
}
public int getForcedReturnId() {
return forcedReturnMap;
}
public MapleMap getForcedReturnMap() {
return Server.getInstance().getWorld(world).getChannel(channel).getMapFactory().getMap(forcedReturnMap);
}
public void setForcedReturnMap(int map) {
this.forcedReturnMap = map;
}
public long getTimeLimit() {
return timeLimit;
}
public void setTimeLimit(int timeLimit) {
this.timeLimit = timeLimit;
}
public int getTimeLeft() {
return (int) ((timeLimit - System.currentTimeMillis()) / 1000);
}
public int getCurrentPartyId() {
for (MapleCharacter chr : this.getCharacters()) {
if (chr.getPartyId() != -1) {
return chr.getPartyId();
}
}
return -1;
}
public void addMapObject(MapleMapObject mapobject) {
objectWLock.lock();
try {
mapobject.setObjectId(runningOid);
this.mapobjects.put(Integer.valueOf(runningOid), mapobject);
incrementRunningOid();
} finally {
objectWLock.unlock();
}
}
private void spawnAndAddRangedMapObject(MapleMapObject mapobject, DelayedPacketCreation packetbakery) {
spawnAndAddRangedMapObject(mapobject, packetbakery, null);
}
private void spawnAndAddRangedMapObject(MapleMapObject mapobject, DelayedPacketCreation packetbakery, SpawnCondition condition) {
chrRLock.lock();
try {
mapobject.setObjectId(runningOid);
for (MapleCharacter chr : characters) {
if (condition == null || condition.canSpawn(chr)) {
if (chr.getPosition().distanceSq(mapobject.getPosition()) <= 722500) {
packetbakery.sendPackets(chr.getClient());
chr.addVisibleMapObject(mapobject);
}
}
}
} finally {
chrRLock.unlock();
}
objectWLock.lock();
try {
this.mapobjects.put(Integer.valueOf(runningOid), mapobject);
} finally {
objectWLock.unlock();
}
incrementRunningOid();
}
private void incrementRunningOid() {
runningOid++;
if (runningOid >= 30000) {
runningOid = 1000;// Lol, like there are monsters with the same oid
// NO
}
objectRLock.lock();
try {
if (!this.mapobjects.containsKey(Integer.valueOf(runningOid))) {
return;
}
} finally {
objectRLock.unlock();
}
throw new RuntimeException("Out of OIDs on map " + mapid + " (channel: " + channel + ")");
}
public void removeMapObject(int num) {
objectWLock.lock();
try {
this.mapobjects.remove(Integer.valueOf(num));
} finally {
objectWLock.unlock();
}
}
public void removeMapObject(final MapleMapObject obj) {
removeMapObject(obj.getObjectId());
}
private Point calcPointBelow(Point initial) {
MapleFoothold fh = footholds.findBelow(initial);
if (fh == null) {
return null;
}
int dropY = fh.getY1();
if (!fh.isWall() && fh.getY1() != fh.getY2()) {
double s1 = Math.abs(fh.getY2() - fh.getY1());
double s2 = Math.abs(fh.getX2() - fh.getX1());
double s5 = Math.cos(Math.atan(s2 / s1)) * (Math.abs(initial.x - fh.getX1()) / Math.cos(Math.atan(s1 / s2)));
if (fh.getY2() < fh.getY1()) {
dropY = fh.getY1() - (int) s5;
} else {
dropY = fh.getY1() + (int) s5;
}
}
return new Point(initial.x, dropY);
}
public Point calcDropPos(Point initial, Point fallback) {
Point ret = calcPointBelow(new Point(initial.x, initial.y - 50));
if (ret == null) {
return fallback;
}
return ret;
}
private void dropFromMonster(final MapleCharacter chr, final MapleMonster mob) {
if (mob.dropsDisabled() || !dropsOn) {
return;
}
final MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
final byte droptype = (byte) (mob.getStats().isExplosiveReward() ? 3 : mob.getStats().isFfaLoot() ? 2 : chr.getParty() != null ? 1 : 0);
final int mobpos = mob.getPosition().x;
int chServerrate = chr.getDropRate();
IItem idrop;
byte d = 1;
Point pos = new Point(0, mob.getPosition().y);
Map<MonsterStatus, MonsterStatusEffect> stati = mob.getStati();
if (stati.containsKey(MonsterStatus.SHOWDOWN)) {
chServerrate *= (stati.get(MonsterStatus.SHOWDOWN).getStati().get(MonsterStatus.SHOWDOWN).doubleValue() / 100.0 + 1.0);
}
final MapleMonsterInformationProvider mi = MapleMonsterInformationProvider.getInstance();
final List<MonsterDropEntry> dropEntry = new ArrayList<MonsterDropEntry>(mi.retrieveDrop(mob.getId()));
Collections.shuffle(dropEntry);
for (final MonsterDropEntry de : dropEntry) {
if (Randomizer.nextInt(999999) < de.chance * chServerrate) {
if (droptype == 3) {
pos.x = (int) (mobpos + (d % 2 == 0 ? (40 * (d + 1) / 2) : -(40 * (d / 2))));
} else {
pos.x = (int) (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(MapleBuffStat.MESOUP) != null) {
mesos = (int) (mesos * chr.getBuffedValue(MapleBuffStat.MESOUP).doubleValue() / 100.0);
}
spawnMesoDrop(mesos * chr.getMesoRate(), calcDropPos(pos, mob.getPosition()), mob, chr, false, droptype);
}
} else {
if (ItemConstants.getInventoryType(de.itemId) == MapleInventoryType.EQUIP) {
idrop = ii.randomizeStats((Equip) ii.getEquipById(de.itemId));
} else {
idrop = new Item(de.itemId, (byte) 0, (short) (de.Maximum != 1 ? Randomizer.nextInt(de.Maximum - de.Minimum) + de.Minimum : 1));
}
spawnDrop(idrop, calcDropPos(pos, mob.getPosition()), mob, chr, droptype, de.questid);
}
d++;
}
}
final List<MonsterGlobalDropEntry> globalEntry = mi.getGlobalDrop();
// Global Drops
for (final MonsterGlobalDropEntry de : globalEntry) {
if (Randomizer.nextInt(999999) < de.chance) {
if (droptype == 3) {
pos.x = (int) (mobpos + (d % 2 == 0 ? (40 * (d + 1) / 2) : -(40 * (d / 2))));
} else {
pos.x = (int) (mobpos + ((d % 2 == 0) ? (25 * (d + 1) / 2) : -(25 * (d / 2))));
}
if (de.itemId == 0) {
// chr.getCashShop().gainCash(1, 80);
} else {
if (ItemConstants.getInventoryType(de.itemId) == MapleInventoryType.EQUIP) {
idrop = ii.randomizeStats((Equip) ii.getEquipById(de.itemId));
} else {
idrop = new Item(de.itemId, (byte) 0, (short) (de.Maximum != 1 ? Randomizer.nextInt(de.Maximum - de.Minimum) + de.Minimum : 1));
}
spawnDrop(idrop, calcDropPos(pos, mob.getPosition()), mob, chr, droptype, de.questid);
d++;
}
}
}
}
private void spawnDrop(final IItem idrop, final Point dropPos, final MapleMonster mob, final MapleCharacter chr, final byte droptype, final short questid) {
final MapleMapItem mdrop = new MapleMapItem(idrop, dropPos, mob, chr, droptype, false, questid);
spawnAndAddRangedMapObject(mdrop, new DelayedPacketCreation() {
@Override
public void sendPackets(MapleClient c) {
if (questid <= 0 || (c.getPlayer().getQuestStatus(questid) == 1 && c.getPlayer().needQuestItem(questid, idrop.getItemId()))) {
c.getSession().write(MaplePacketCreator.dropItemFromMapObject(mdrop, mob.getPosition(), dropPos, (byte) 1));
}
}
}, null);
TimerManager.getInstance().schedule(new ExpireMapItemJob(mdrop), 180000);
activateItemReactors(mdrop, chr.getClient());
}
public final void spawnMesoDrop(final int meso, final Point position, final MapleMapObject dropper, final MapleCharacter owner, final boolean playerDrop, final byte droptype) {
final Point droppos = calcDropPos(position, position);
final MapleMapItem mdrop = new MapleMapItem(meso, droppos, dropper, owner, droptype, playerDrop);
spawnAndAddRangedMapObject(mdrop, new DelayedPacketCreation() {
@Override
public void sendPackets(MapleClient c) {
c.getSession().write(MaplePacketCreator.dropItemFromMapObject(mdrop, dropper.getPosition(), droppos, (byte) 1));
}
}, null);
TimerManager.getInstance().schedule(new ExpireMapItemJob(mdrop), 180000);
}
public final void disappearingItemDrop(final MapleMapObject dropper, final MapleCharacter owner, final IItem item, final Point pos) {
final Point droppos = calcDropPos(pos, pos);
final MapleMapItem drop = new MapleMapItem(item, droppos, dropper, owner, (byte) 1, false);
broadcastMessage(MaplePacketCreator.dropItemFromMapObject(drop, dropper.getPosition(), droppos, (byte) 3), drop.getPosition());
}
public MapleMonster getMonsterById(int id) {
objectRLock.lock();
try {
for (MapleMapObject obj : mapobjects.values()) {
if (obj.getType() == MapleMapObjectType.MONSTER) {
if (((MapleMonster) obj).getId() == id) {
return (MapleMonster) obj;
}
}
}
} finally {
objectRLock.unlock();
}
return null;
}
public int countMonster(int id) {
int count = 0;
for (MapleMapObject m : getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.MONSTER))) {
MapleMonster mob = (MapleMonster) m;
if (mob.getId() == id) {
count++;
}
}
return count;
}
public boolean damageMonster(final MapleCharacter chr, final MapleMonster monster, final int damage) {
if (monster.getId() == 8800000) {
for (MapleMapObject object : chr.getMap().getMapObjects()) {
MapleMonster mons = chr.getMap().getMonsterByOid(object.getObjectId());
if (mons != null) {
if (mons.getId() >= 8800003 && mons.getId() <= 8800010) {
return true;
}
}
}
}
if (monster.isAlive()) {
boolean killed = false;
monster.monsterLock.lock();
try {
if (!monster.isAlive()) {
return false;
}
CoolDamageEntry cool = monster.getStats().getCool();
if (cool != null) {
Pyramid pq = (Pyramid) chr.getPartyQuest();
if (pq != null) {
if (damage > 0) {
if (damage >= cool.damage) {
if ((Math.random() * 100) < cool.probability) {
pq.cool();
} else {
pq.kill();
}
} else {
pq.kill();
}
} else {
pq.miss();
}
killed = true;
}
}
if (damage > 0) {
monster.damage(chr, damage, true);
if (!monster.isAlive()) { // monster just died
// killMonster(monster, chr, true);
killed = true;
}
} else if (monster.getId() >= 8810002 && monster.getId() <= 8810009) {
for (MapleMapObject object : chr.getMap().getMapObjects()) {
MapleMonster mons = chr.getMap().getMonsterByOid(object.getObjectId());
if (mons != null) {
if (mons.getId() == 8810018) {
damageMonster(chr, mons, damage);
}
}
}
}
} finally {
monster.monsterLock.unlock();
}
if (killed && monster != null) {
killMonster(monster, chr, true);
monster.empty();
nullifyObject(monster);
}
return true;
}
return false;
}
public void killMonster(final MapleMonster monster, final MapleCharacter chr, final boolean withDrops) {
killMonster(monster, chr, withDrops, false, 1);
}
public void killMonster(final MapleMonster monster, final MapleCharacter chr, final boolean withDrops, final boolean secondTime, int animation) {
if (monster.getId() == 8810018 && !secondTime) {
TimerManager.getInstance().schedule(new Runnable() {
@Override
public void run() {
killMonster(monster, chr, withDrops, true, 1);
killAllMonsters();
}
}, 3000);
return;
}
if (chr == null) {
spawnedMonstersOnMap.decrementAndGet();
monster.setHp(0);
broadcastMessage(MaplePacketCreator.killMonster(monster.getObjectId(), animation), monster.getPosition());
removeMapObject(monster);
monster.empty();
nullifyObject(monster);
return;
}
/*
* if (chr.getQuest(MapleQuest.getInstance(29400)).getStatus().equals(
* MapleQuestStatus.Status.STARTED)) { if (chr.getLevel() >= 120 &&
* monster.getStats().getLevel() >= 120) { //FIX MEDAL SHET } else if
* (monster.getStats().getLevel() >= chr.getLevel()) { } }
*/
int buff = monster.getBuffToGive();
if (buff > -1) {
MapleItemInformationProvider mii = MapleItemInformationProvider.getInstance();
for (MapleMapObject mmo : this.getAllPlayer()) {
MapleCharacter character = (MapleCharacter) mmo;
if (character.isAlive()) {
MapleStatEffect statEffect = mii.getItemEffect(buff);
character.getClient().announce(MaplePacketCreator.showOwnBuffEffect(buff, 1));
broadcastMessage(character, MaplePacketCreator.showBuffeffect(character.getId(), buff, 1), false);
statEffect.applyTo(character);
}
}
}
if (monster.getId() == 8810018) {
for (Channel cserv : Server.getInstance().getWorld(world).getChannels()) {
for (MapleCharacter player : cserv.getPlayerStorage().getAllCharacters()) {
if (player.getMapId() == 240000000) {
player.message("Mysterious power arose as I heard the powerful cry of the Nine Spirit Baby Dragon.");
} else {
player.dropMessage("To the crew that have finally conquered Horned Tail after numerous attempts, I salute thee! You are the true heroes of Leafre!!");
if (player.isGM()) {
player.message("[GM-Message] Horntail was killed by : " + chr.getName());
}
}
}
}
}
spawnedMonstersOnMap.decrementAndGet();
monster.setHp(0);
broadcastMessage(MaplePacketCreator.killMonster(monster.getObjectId(), animation), monster.getPosition());
if (monster.getStats().selfDestruction() == null) {// FUU BOMBS D:
removeMapObject(monster);
}
if (monster.getCP() > 0 && chr.getCarnival() != null) {
chr.getCarnivalParty().addCP(chr, monster.getCP());
chr.announce(MaplePacketCreator.updateCP(chr.getCP(), chr.getObtainedCP()));
broadcastMessage(MaplePacketCreator.updatePartyCP(chr.getCarnivalParty()));
// they drop items too ):
}
if (monster.getId() >= 8800003 && monster.getId() <= 8800010) {
boolean makeZakReal = true;
Collection<MapleMapObject> objects = getMapObjects();
for (MapleMapObject object : objects) {
MapleMonster mons = getMonsterByOid(object.getObjectId());
if (mons != null) {
if (mons.getId() >= 8800003 && mons.getId() <= 8800010) {
makeZakReal = false;
break;
}
}
}
if (makeZakReal) {
for (MapleMapObject object : objects) {
MapleMonster mons = chr.getMap().getMonsterByOid(object.getObjectId());
if (mons != null) {
if (mons.getId() == 8800000) {
makeMonsterReal(mons);
updateMonsterController(mons);
break;
}
}
}
}
}
MapleCharacter dropOwner = monster.killBy(chr);
if (withDrops && !monster.dropsDisabled()) {
if (dropOwner == null) {
dropOwner = chr;
}
dropFromMonster(dropOwner, monster);
}
}
public void killMonster(int monsId) {
for (MapleMapObject mmo : getMapObjects()) {
if (mmo instanceof MapleMonster) {
if (((MapleMonster) mmo).getId() == monsId) {
this.killMonster((MapleMonster) mmo, (MapleCharacter) getAllPlayer().get(0), false);
}
}
}
}
public void killAllMonsters() {
for (MapleMapObject monstermo : getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.MONSTER))) {
MapleMonster monster = (MapleMonster) monstermo;
spawnedMonstersOnMap.decrementAndGet();
monster.setHp(0);
broadcastMessage(MaplePacketCreator.killMonster(monster.getObjectId(), true), monster.getPosition());
removeMapObject(monster);
monster.empty();
nullifyObject(monster);
}
}
public List<MapleMapObject> getAllPlayer() {
return getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.PLAYER));
}
public void destroyReactor(int oid) {
final MapleReactor reactor = getReactorByOid(oid);
TimerManager tMan = TimerManager.getInstance();
broadcastMessage(MaplePacketCreator.destroyReactor(reactor));
reactor.setAlive(false);
removeMapObject(reactor);
reactor.setTimerActive(false);
if (reactor.getDelay() > 0) {
tMan.schedule(new Runnable() {
@Override
public void run() {
respawnReactor(reactor);
}
}, reactor.getDelay());
}
}
public void resetReactors() {
objectRLock.lock();
try {
for (MapleMapObject o : mapobjects.values()) {
if (o.getType() == MapleMapObjectType.REACTOR) {
final MapleReactor r = ((MapleReactor) o);
r.setState((byte) 0);
r.setTimerActive(false);
broadcastMessage(MaplePacketCreator.triggerReactor(r, 0));
}
}
} finally {
objectRLock.unlock();
}
}
public void shuffleReactors() {
List<Point> points = new ArrayList<Point>();
objectRLock.lock();
try {
for (MapleMapObject o : mapobjects.values()) {
if (o.getType() == MapleMapObjectType.REACTOR) {
points.add(((MapleReactor) o).getPosition());
}
}
Collections.shuffle(points);
for (MapleMapObject o : mapobjects.values()) {
if (o.getType() == MapleMapObjectType.REACTOR) {
((MapleReactor) o).setPosition(points.remove(points.size() - 1));
}
}
} finally {
objectRLock.unlock();
}
}
public MapleReactor getReactorById(int Id) {
objectRLock.lock();
try {
for (MapleMapObject obj : mapobjects.values()) {
if (obj.getType() == MapleMapObjectType.REACTOR) {
if (((MapleReactor) obj).getId() == Id) {
return (MapleReactor) obj;
}
}
}
return null;
} finally {
objectRLock.unlock();
}
}
/**
* Automagically finds a new controller for the given monster from the chars
* on the map...
*
* @param monster
*/
public void updateMonsterController(MapleMonster monster) {
monster.monsterLock.lock();
try {
if (!monster.isAlive()) {
return;
}
if (monster.getController() != null) {
if (monster.getController().getMap() != this) {
monster.getController().stopControllingMonster(monster);
} else {
return;
}
}
int mincontrolled = -1;
MapleCharacter newController = null;
chrRLock.lock();
try {
for (MapleCharacter chr : characters) {
if (!chr.isHidden() && (chr.getControlledMonsters().size() < mincontrolled || mincontrolled == -1)) {
mincontrolled = chr.getControlledMonsters().size();
newController = chr;
}
}
} finally {
chrRLock.unlock();
}
if (newController != null) {// was a new controller found? (if not
// no one is on the map)
if (monster.isFirstAttack()) {
newController.controlMonster(monster, true);
monster.setControllerHasAggro(true);
monster.setControllerKnowsAboutAggro(true);
} else {
newController.controlMonster(monster, false);
}
}
} finally {
monster.monsterLock.unlock();
}
}
public Collection<MapleMapObject> getMapObjects() {
return Collections.unmodifiableCollection(mapobjects.values());
}
public boolean containsNPC(int npcid) {
if (npcid == 9000066) {
return true;
}
objectRLock.lock();
try {
for (MapleMapObject obj : mapobjects.values()) {
if (obj.getType() == MapleMapObjectType.NPC) {
if (((MapleNPC) obj).getId() == npcid) {
return true;
}
}
}
} finally {
objectRLock.unlock();
}
return false;
}
public MapleMapObject getMapObject(int oid) {
return mapobjects.get(oid);
}
/**
* returns a monster with the given oid, if no such monster exists returns
* null
*
* @param oid
* @return
*/
public MapleMonster getMonsterByOid(int oid) {
MapleMapObject mmo = getMapObject(oid);
if (mmo == null) {
return null;
}
if (mmo.getType() == MapleMapObjectType.MONSTER) {
return (MapleMonster) mmo;
}
return null;
}
public MapleReactor getReactorByOid(int oid) {
MapleMapObject mmo = getMapObject(oid);
if (mmo == null) {
return null;
}
return mmo.getType() == MapleMapObjectType.REACTOR ? (MapleReactor) mmo : null;
}
public MapleReactor getReactorByName(String name) {
objectRLock.lock();
try {
for (MapleMapObject obj : mapobjects.values()) {
if (obj.getType() == MapleMapObjectType.REACTOR) {
if (((MapleReactor) obj).getName().equals(name)) {
return (MapleReactor) obj;
}
}
}
} finally {
objectRLock.unlock();
}
return null;
}
public void spawnMonsterOnGroudBelow(MapleMonster mob, Point pos) {
spawnMonsterOnGroundBelow(mob, pos);
}
public void spawnMonsterOnGroundBelow(MapleMonster mob, Point pos) {
Point spos = new Point(pos.x, pos.y - 1);
spos = calcPointBelow(spos);
spos.y--;
mob.setPosition(spos);
spawnMonster(mob);
}
public void spawnCPQMonster(MapleMonster mob, Point pos, int team) {
Point spos = new Point(pos.x, pos.y - 1);
spos = calcPointBelow(spos);
spos.y--;
mob.setPosition(spos);
mob.setTeam(team);
spawnMonster(mob);
}
private void monsterItemDrop(final MapleMonster m, final IItem item, long delay) {
final ScheduledFuture<?> monsterItemDrop = TimerManager.getInstance().register(new Runnable() {
@Override
public void run() {
if (MapleMap.this.getMonsterById(m.getId()) != null) {
if (item.getItemId() == 4001101) {
MapleMap.this.broadcastMessage(MaplePacketCreator.serverNotice(6, "The Moon Bunny made rice cake number " + (MapleMap.this.riceCakeNum + 1)));
}
spawnItemDrop(m, null, item, m.getPosition(), true, true);
}
}
}, delay, delay);
if (getMonsterById(m.getId()) == null) {
monsterItemDrop.cancel(true);
}
}
public void spawnFakeMonsterOnGroundBelow(MapleMonster mob, Point pos) {
Point spos = getGroundBelow(pos);
mob.setPosition(spos);
spawnFakeMonster(mob);
}
public Point getGroundBelow(Point pos) {
Point spos = new Point(pos.x, pos.y - 1);
spos = calcPointBelow(spos);
spos.y--;
return spos;
}
public void spawnRevives(final MapleMonster monster) {
monster.setMap(this);
spawnAndAddRangedMapObject(monster, new DelayedPacketCreation() {
@Override
public void sendPackets(MapleClient c) {
c.announce(MaplePacketCreator.spawnMonster(monster, false));
}
});
updateMonsterController(monster);
spawnedMonstersOnMap.incrementAndGet();
}
public void spawnMonster(final MapleMonster monster) {
if (mobCapacity != -1 && mobCapacity == spawnedMonstersOnMap.get()) {
return;// PyPQ
}
monster.setMap(this);
spawnAndAddRangedMapObject(monster, new DelayedPacketCreation() {
@Override
public void sendPackets(MapleClient c) {
c.announce(MaplePacketCreator.spawnMonster(monster, true));
}
}, null);
updateMonsterController(monster);
if (monster.getDropPeriodTime() > 0) { // 9300102 - Watchhog, 9300061 -
// Moon Bunny (HPQ)
if (monster.getId() == 9300102) {
monsterItemDrop(monster, new Item(4031507, (byte) 0, (short) 1), monster.getDropPeriodTime());
} else if (monster.getId() == 9300061) {
monsterItemDrop(monster, new Item(4001101, (byte) 0, (short) 1), monster.getDropPeriodTime() / 3);
} else {
Output.print("UNCODED TIMED MOB DETECTED: " + monster.getId());
}
}
spawnedMonstersOnMap.incrementAndGet();
final selfDestruction selfDestruction = monster.getStats().selfDestruction();
if (monster.getStats().removeAfter() > 0 || selfDestruction != null) {
if (selfDestruction == null) {
TimerManager.getInstance().schedule(new Runnable() {
@Override
public void run() {
killMonster(monster, (MapleCharacter) getAllPlayer().get(0), false);
}
}, monster.getStats().removeAfter() * 1000);
} else {
TimerManager.getInstance().schedule(new Runnable() {
@Override
public void run() {
killMonster(monster, (MapleCharacter) getAllPlayer().get(0), false, false, selfDestruction.getAction());
}
}, selfDestruction.removeAfter() * 1000);
}
}
if (mapid == 910110000 && !this.allowHPQSummon) { // HPQ make monsters
// invisible
this.broadcastMessage(MaplePacketCreator.makeMonsterInvisible(monster));
}
}
public void spawnDojoMonster(final MapleMonster monster) {
Point[] pts = {new Point(140, 0), new Point(190, 7), new Point(187, 7)};
spawnMonsterWithEffect(monster, 15, pts[Randomizer.nextInt(3)]);
}
public void spawnMonsterWithEffect(final MapleMonster monster, final int effect, Point pos) {
monster.setMap(this);
Point spos = new Point(pos.x, pos.y - 1);
spos = calcPointBelow(spos);
spos.y--;
monster.setPosition(spos);
if (mapid < 925020000 || mapid > 925030000) {
monster.disableDrops();
}
spawnAndAddRangedMapObject(monster, new DelayedPacketCreation() {
@Override
public void sendPackets(MapleClient c) {
c.announce(MaplePacketCreator.spawnMonster(monster, true, effect));
}
});
if (monster.hasBossHPBar()) {
broadcastMessage(monster.makeBossHPBarPacket(), monster.getPosition());
}
updateMonsterController(monster);
spawnedMonstersOnMap.incrementAndGet();
}
public void spawnFakeMonster(final MapleMonster monster) {
monster.setMap(this);
monster.setFake(true);
spawnAndAddRangedMapObject(monster, new DelayedPacketCreation() {
@Override
public void sendPackets(MapleClient c) {
c.announce(MaplePacketCreator.spawnFakeMonster(monster, 0));
}
});
spawnedMonstersOnMap.incrementAndGet();
}
public void makeMonsterReal(final MapleMonster monster) {
monster.setFake(false);
broadcastMessage(MaplePacketCreator.makeMonsterReal(monster));
updateMonsterController(monster);
}
public void spawnReactor(final MapleReactor reactor) {
reactor.setMap(this);
spawnAndAddRangedMapObject(reactor, new DelayedPacketCreation() {
@Override
public void sendPackets(MapleClient c) {
c.announce(reactor.makeSpawnData());
}
});
}
private void respawnReactor(final MapleReactor reactor) {
reactor.setState((byte) 0);
reactor.setAlive(true);
spawnReactor(reactor);
}
public void spawnDoor(final MapleDoor door) {
spawnAndAddRangedMapObject(door, new DelayedPacketCreation() {
@Override
public void sendPackets(MapleClient c) {
c.announce(MaplePacketCreator.spawnDoor(door.getOwner().getId(), door.getTargetPosition(), false));
if (door.getOwner().getParty() != null && (door.getOwner() == c.getPlayer() || door.getOwner().getParty().containsMembers(c.getPlayer().getMPC()))) {
c.announce(MaplePacketCreator.partyPortal(door.getTown().getId(), door.getTarget().getId(), door.getTargetPosition()));
}
c.announce(MaplePacketCreator.spawnPortal(door.getTown().getId(), door.getTarget().getId(), door.getTargetPosition()));
c.announce(MaplePacketCreator.enableActions());
}
}, new SpawnCondition() {
@Override
public boolean canSpawn(MapleCharacter chr) {
return chr.getMapId() == door.getTarget().getId() || chr == door.getOwner() && chr.getParty() == null;
}
});
}
public List<MapleCharacter> getPlayersInRange(Rectangle box, List<MapleCharacter> chr) {
List<MapleCharacter> character = new LinkedList<MapleCharacter>();
chrRLock.lock();
try {
for (MapleCharacter a : characters) {
if (chr.contains(a.getClient().getPlayer())) {
if (box.contains(a.getPosition())) {
character.add(a);
}
}
}
return character;
} finally {
chrRLock.unlock();
}
}
public void spawnSummon(final MapleSummon summon) {
spawnAndAddRangedMapObject(summon, new DelayedPacketCreation() {
@Override
public void sendPackets(MapleClient c) {
if (summon != null) {
c.announce(MaplePacketCreator.spawnSummon(summon, true));
}
}
}, null);
}
public void spawnMist(final MapleMist mist, final int duration, boolean poison, boolean fake) {
addMapObject(mist);
broadcastMessage(fake ? mist.makeFakeSpawnData(30) : mist.makeSpawnData());
TimerManager tMan = TimerManager.getInstance();
final ScheduledFuture<?> poisonSchedule;
if (poison) {
Runnable poisonTask = new Runnable() {
@Override
public void run() {
List<MapleMapObject> affectedMonsters = getMapObjectsInBox(mist.getBox(), Collections.singletonList(MapleMapObjectType.MONSTER));
for (MapleMapObject mo : affectedMonsters) {
if (mist.makeChanceResult()) {
MonsterStatusEffect poisonEffect = new MonsterStatusEffect(Collections.singletonMap(MonsterStatus.POISON, 1), mist.getSourceSkill(), null, false);
((MapleMonster) mo).applyStatus(mist.getOwner(), poisonEffect, true, duration);
}
}
}
};
poisonSchedule = tMan.register(poisonTask, 2000, 2500);
} else {
poisonSchedule = null;
}
tMan.schedule(new Runnable() {
@Override
public void run() {
removeMapObject(mist);
if (poisonSchedule != null) {
poisonSchedule.cancel(false);
}
broadcastMessage(mist.makeDestroyData());
}
}, duration);
}
public final void spawnItemDrop(final MapleMapObject dropper, final MapleCharacter owner, final IItem item, Point pos, final boolean ffaDrop, final boolean playerDrop) {
final Point droppos = calcDropPos(pos, pos);
final MapleMapItem drop = new MapleMapItem(item, droppos, dropper, owner, (byte) (ffaDrop ? 2 : 0), playerDrop);
spawnAndAddRangedMapObject(drop, new DelayedPacketCreation() {
@Override
public void sendPackets(MapleClient c) {
c.getSession().write(MaplePacketCreator.dropItemFromMapObject(drop, dropper.getPosition(), droppos, (byte) 1));
}
}, null);
broadcastMessage(MaplePacketCreator.dropItemFromMapObject(drop, dropper.getPosition(), droppos, (byte) 0));
if (!everlast) {
TimerManager.getInstance().schedule(new ExpireMapItemJob(drop), 180000);
activateItemReactors(drop, owner.getClient());
}
}
private void activateItemReactors(final MapleMapItem drop, final MapleClient c) {
final IItem item = drop.getItem();
for (final MapleMapObject o : getAllReactor()) {
final MapleReactor react = (MapleReactor) o;
if (react.getReactorType() == 100) {
if (react.getReactItem((byte) 0).itemId == item.getItemId() && react.getReactItem((byte) 0).quantity == item.getQuantity()) {
if (react.getArea().contains(drop.getPosition())) {
if (!react.isTimerActive()) {
TimerManager.getInstance().schedule(new ActivateItemReactor(drop, react, c), 5000);
react.setTimerActive(true);
break;
}
}
}
}
}
}
public final List<MapleMapObject> getAllReactor() {
return getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.REACTOR));
}
public void startMapEffect(String msg, int itemId) {
startMapEffect(msg, itemId, 30000);
}
public void startMapEffect(String msg, int itemId, long time) {
if (mapEffect != null) {
return;
}
mapEffect = new MapleMapEffect(msg, itemId);
broadcastMessage(mapEffect.makeStartData());
TimerManager.getInstance().schedule(new Runnable() {
@Override
public void run() {
broadcastMessage(mapEffect.makeDestroyData());
mapEffect = null;
}
}, time);
}
public void addPlayer(final MapleCharacter chr) {
chrWLock.lock();
try {
this.characters.add(chr);
} finally {
chrWLock.unlock();
}
if (onFirstUserEnter.length() != 0 && !chr.hasEntered(onFirstUserEnter, mapid) && MapScriptManager.getInstance().scriptExists(onFirstUserEnter, true)) {
if (getAllPlayer().size() <= 1) {
chr.enteredScript(onFirstUserEnter, mapid);
MapScriptManager.getInstance().getMapScript(chr.getClient(), onFirstUserEnter, true);
}
}
if (onUserEnter.length() != 0) {
if (onUserEnter.equals("cygnusTest") && (mapid < 913040000 || mapid > 913040006)) {
chr.saveLocation("INTRO");
}
MapScriptManager.getInstance().getMapScript(chr.getClient(), onUserEnter, false);
}
if (FieldLimit.CANNOTUSEMOUNTS.check(fieldLimit) && chr.getBuffedValue(MapleBuffStat.MONSTER_RIDING) != null) {
chr.cancelEffectFromBuffStat(MapleBuffStat.MONSTER_RIDING);
chr.cancelBuffStats(MapleBuffStat.MONSTER_RIDING);
}
if (mapid == 923010000 && getMonsterById(9300102) == null) { // Kenta's
// Mount
// Quest
spawnMonsterOnGroundBelow(MapleLifeFactory.getMonster(9300102), new Point(77, 426));
} else if (mapid == 910110000) { // Henesys Party Quest
chr.getClient().announce(MaplePacketCreator.getClock(15 * 60));
TimerManager.getInstance().register(new Runnable() {
@Override
public void run() {
if (mapid == 910110000) {
chr.getClient().getPlayer().changeMap(chr.getClient().getChannelServer().getMapFactory().getMap(925020000));
}
}
}, 15 * 60 * 1000 + 3000);
}
MaplePet[] pets = chr.getPets();
for (int i = 0; i < chr.getPets().length; i++) {
if (pets[i] != null) {
pets[i].setPos(getGroundBelow(chr.getPosition()));
chr.announce(MaplePacketCreator.showPet(chr, pets[i], false, false));
} else {
break;
}
}
if (chr.isHidden()) {
broadcastGMMessage(chr, MaplePacketCreator.spawnPlayerMapobject(chr), false);
chr.announce(MaplePacketCreator.getGMEffect(0x10, (byte) 1));
} else {
broadcastMessage(chr, MaplePacketCreator.spawnPlayerMapobject(chr), false);
}
sendObjectPlacement(chr.getClient());
if (isStartingEventMap() && !eventStarted()) {
chr.getMap().getPortal("join00").setPortalStatus(false);
}
if (hasForcedEquip()) {
chr.getClient().announce(MaplePacketCreator.showForcedEquip(-1));
}
if (specialEquip()) {
chr.getClient().announce(MaplePacketCreator.coconutScore(0, 0));
chr.getClient().announce(MaplePacketCreator.showForcedEquip(chr.getTeam()));
}
objectWLock.lock();
try {
this.mapobjects.put(Integer.valueOf(chr.getObjectId()), chr);
} finally {
objectWLock.unlock();
}
if (chr.getPlayerShop() != null) {
addMapObject(chr.getPlayerShop());
}
MapleStatEffect summonStat = chr.getStatForBuff(MapleBuffStat.SUMMON);
if (summonStat != null) {
MapleSummon summon = chr.getSummons().get(summonStat.getSourceId());
summon.setPosition(chr.getPosition());
chr.getMap().spawnSummon(summon);
updateMapObjectVisibility(chr, summon);
}
if (mapEffect != null) {
mapEffect.sendStartData(chr.getClient());
}
if (mapid == 914000200 || mapid == 914000210 || mapid == 914000220) {
TimerManager.getInstance().schedule(new Runnable() {
@Override
public void run() {
chr.getClient().announce(MaplePacketCreator.aranGodlyStats());
}
}, 1500);
}
if (chr.getEventInstance() != null && chr.getEventInstance().isTimerStarted()) {
chr.getClient().announce(MaplePacketCreator.getClock((int) (chr.getEventInstance().getTimeLeft() / 1000)));
}
if (chr.getFitness() != null && chr.getFitness().isTimerStarted()) {
chr.getClient().announce(MaplePacketCreator.getClock((int) (chr.getFitness().getTimeLeft() / 1000)));
}
if (chr.getOla() != null && chr.getOla().isTimerStarted()) {
chr.getClient().announce(MaplePacketCreator.getClock((int) (chr.getOla().getTimeLeft() / 1000)));
}
if (mapid == 109060000) {
chr.announce(MaplePacketCreator.rollSnowBall(true, 0, null, null));
}
MonsterCarnival carnival = chr.getCarnival();
MonsterCarnivalParty cparty = chr.getCarnivalParty();
if (carnival != null && cparty != null && (mapid == 980000101 || mapid == 980000201 || mapid == 980000301 || mapid == 980000401 || mapid == 980000501 || mapid == 980000601)) {
chr.getClient().announce(MaplePacketCreator.getClock((int) (carnival.getTimeLeft() / 1000)));
chr.getClient().announce(MaplePacketCreator.startCPQ(chr, carnival.oppositeTeam(cparty)));
}
if (hasClock()) {
Calendar cal = Calendar.getInstance();
chr.getClient().announce((MaplePacketCreator.getClockTime(cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND))));
}
if (hasBoat() == 2) {
chr.getClient().announce((MaplePacketCreator.boatPacket(true)));
} else if (hasBoat() == 1 && (chr.getMapId() != 200090000 || chr.getMapId() != 200090010)) {
chr.getClient().announce(MaplePacketCreator.boatPacket(false));
}
chr.receivePartyMemberHP();
}
public MaplePortal findClosestPortal(Point from) {
MaplePortal closest = null;
double shortestDistance = Double.POSITIVE_INFINITY;
for (MaplePortal portal : portals.values()) {
double distance = portal.getPosition().distanceSq(from);
if (distance < shortestDistance) {
closest = portal;
shortestDistance = distance;
}
}
return closest;
}
public MaplePortal getRandomSpawnpoint() {
List<MaplePortal> spawnPoints = new ArrayList<MaplePortal>();
for (MaplePortal portal : portals.values()) {
if (portal.getType() >= 0 && portal.getType() <= 2) {
spawnPoints.add(portal);
}
}
MaplePortal portal = spawnPoints.get(new Random().nextInt(spawnPoints.size()));
return portal != null ? portal : getPortal(0);
}
public void removePlayer(MapleCharacter chr) {
chrWLock.lock();
try {
characters.remove(chr);
} finally {
chrWLock.unlock();
}
removeMapObject(Integer.valueOf(chr.getObjectId()));
if (!chr.isHidden()) {
broadcastMessage(MaplePacketCreator.removePlayerFromMap(chr.getId()));
} else {
broadcastGMMessage(MaplePacketCreator.removePlayerFromMap(chr.getId()));
}
for (MapleMonster monster : chr.getControlledMonsters()) {
monster.setController(null);
monster.setControllerHasAggro(false);
monster.setControllerKnowsAboutAggro(false);
updateMonsterController(monster);
}
chr.leaveMap();
chr.cancelMapTimeLimitTask();
for (MapleSummon summon : chr.getSummons().values()) {
if (summon.isStationary()) {
chr.cancelBuffStats(MapleBuffStat.PUPPET);
} else {
removeMapObject(summon);
}
}
}
public void broadcastMessage(MaplePacket packet) {
broadcastMessage(null, packet, Double.POSITIVE_INFINITY, null);
}
public void broadcastGMMessage(MaplePacket packet) {
broadcastGMMessage(null, packet, Double.POSITIVE_INFINITY, null);
}
/**
* Nonranged. Repeat to source according to parameter.
*
* @param source
* @param packet
* @param repeatToSource
*/
public void broadcastMessage(MapleCharacter source, MaplePacket packet, boolean repeatToSource) {
broadcastMessage(repeatToSource ? null : source, packet, Double.POSITIVE_INFINITY, source.getPosition());
}
/**
* Ranged and repeat according to parameters.
*
* @param source
* @param packet
* @param repeatToSource
* @param ranged
*/
public void broadcastMessage(MapleCharacter source, MaplePacket packet, boolean repeatToSource, boolean ranged) {
broadcastMessage(repeatToSource ? null : source, packet, ranged ? 722500 : Double.POSITIVE_INFINITY, source.getPosition());
}
/**
* Always ranged from Point.
*
* @param packet
* @param rangedFrom
*/
public void broadcastMessage(MaplePacket packet, Point rangedFrom) {
broadcastMessage(null, packet, 722500, rangedFrom);
}
/**
* Always ranged from point. Does not repeat to source.
*
* @param source
* @param packet
* @param rangedFrom
*/
public void broadcastMessage(MapleCharacter source, MaplePacket packet, Point rangedFrom) {
broadcastMessage(source, packet, 722500, rangedFrom);
}
private void broadcastMessage(MapleCharacter source, MaplePacket packet, double rangeSq, Point rangedFrom) {
chrRLock.lock();
try {
for (MapleCharacter chr : characters) {
if (chr != source) {
if (rangeSq < Double.POSITIVE_INFINITY) {
if (rangedFrom.distanceSq(chr.getPosition()) <= rangeSq) {
chr.getClient().announce(packet);
}
} else {
chr.getClient().announce(packet);
}
}
}
} finally {
chrRLock.unlock();
}
}
private boolean isNonRangedType(MapleMapObjectType type) {
switch (type) {
case NPC:
case PLAYER:
case HIRED_MERCHANT:
case PLAYER_NPC:
case MIST:
return true;
}
return false;
}
private void sendObjectPlacement(MapleClient mapleClient) {
MapleCharacter chr = mapleClient.getPlayer();
objectRLock.lock();
try {
for (MapleMapObject o : mapobjects.values()) {
if (o.getType() == MapleMapObjectType.SUMMON) {
MapleSummon summon = (MapleSummon) o;
if (summon.getOwner() == chr) {
if (chr.getSummons().isEmpty() || !chr.getSummons().containsValue(summon)) {
objectWLock.lock();
try {
mapobjects.remove(o);
} finally {
objectWLock.unlock();
}
continue;
}
}
}
if (isNonRangedType(o.getType())) {
o.sendSpawnData(mapleClient);
} else if (o.getType() == MapleMapObjectType.MONSTER) {
updateMonsterController((MapleMonster) o);
}
}
} finally {
objectRLock.unlock();
}
if (chr != null) {
for (MapleMapObject o : getMapObjectsInRange(chr.getPosition(), 722500, rangedMapobjectTypes)) {
if (o.getType() == MapleMapObjectType.REACTOR) {
if (((MapleReactor) o).isAlive()) {
o.sendSpawnData(chr.getClient());
chr.addVisibleMapObject(o);
}
} else {
o.sendSpawnData(chr.getClient());
chr.addVisibleMapObject(o);
}
}
}
}
public List<MapleMapObject> getMapObjectsInRange(Point from, double rangeSq, List<MapleMapObjectType> types) {
List<MapleMapObject> ret = new LinkedList<MapleMapObject>();
objectRLock.lock();
try {
for (MapleMapObject l : mapobjects.values()) {
if (types.contains(l.getType())) {
if (from.distanceSq(l.getPosition()) <= rangeSq) {
ret.add(l);
}
}
}
return ret;
} finally {
objectRLock.unlock();
}
}
public List<MapleMapObject> getMapObjectsInBox(Rectangle box, List<MapleMapObjectType> types) {
List<MapleMapObject> ret = new LinkedList<MapleMapObject>();
objectRLock.lock();
try {
for (MapleMapObject l : mapobjects.values()) {
if (types.contains(l.getType())) {
if (box.contains(l.getPosition())) {
ret.add(l);
}
}
}
return ret;
} finally {
objectRLock.unlock();
}
}
public void addPortal(MaplePortal myPortal) {
portals.put(myPortal.getId(), myPortal);
}
public MaplePortal getPortal(String portalname) {
for (MaplePortal port : portals.values()) {
if (port.getName().equals(portalname)) {
return port;
}
}
return null;
}
public MaplePortal getPortal(int portalid) {
return portals.get(portalid);
}
public void addMapleArea(Rectangle rec) {
areas.add(rec);
}
public List<Rectangle> getAreas() {
return new ArrayList<Rectangle>(areas);
}
public Rectangle getArea(int index) {
return areas.get(index);
}
public void setFootholds(MapleFootholdTree footholds) {
this.footholds = footholds;
}
public MapleFootholdTree getFootholds() {
return footholds;
}
/**
* it's threadsafe, gtfo :D
*
* @param monster
* @param mobTime
*/
public void addMonsterSpawn(MapleMonster monster, int mobTime, int team) {
Point newpos = calcPointBelow(monster.getPosition());
newpos.y -= 1;
SpawnPoint sp = new SpawnPoint(monster.getId(), newpos, !monster.isMobile(), mobTime, mobInterval, team);
monsterSpawn.add(sp);
if (sp.shouldSpawn() || mobTime == -1) {// -1 does not respawn and
// should not either but force
// ONE spawn
spawnMonster(sp.getMonster());
}
}
public float getMonsterRate() {
return monsterRate;
}
public Collection<MapleCharacter> getCharacters() {
return Collections.unmodifiableCollection(this.characters);
}
public MapleCharacter getCharacterById(int id) {
chrRLock.lock();
try {
for (MapleCharacter c : this.characters) {
if (c.getId() == id) {
return c;
}
}
} finally {
chrRLock.unlock();
}
return null;
}
private void updateMapObjectVisibility(MapleCharacter chr, MapleMapObject mo) {
if (!chr.isMapObjectVisible(mo)) { // monster entered view range
if (mo.getType() == MapleMapObjectType.SUMMON || mo.getPosition().distanceSq(chr.getPosition()) <= 722500) {
chr.addVisibleMapObject(mo);
mo.sendSpawnData(chr.getClient());
}
} else if (mo.getType() != MapleMapObjectType.SUMMON && mo.getPosition().distanceSq(chr.getPosition()) > 722500) {
chr.removeVisibleMapObject(mo);
mo.sendDestroyData(chr.getClient());
}
}
public void moveMonster(MapleMonster monster, Point reportedPos) {
monster.setPosition(reportedPos);
chrRLock.lock();
try {
for (MapleCharacter chr : characters) {
updateMapObjectVisibility(chr, monster);
}
} finally {
chrRLock.unlock();
}
}
public void movePlayer(MapleCharacter player, Point newPosition) {
player.setPosition(newPosition);
Collection<MapleMapObject> visibleObjects = player.getVisibleMapObjects();
MapleMapObject[] visibleObjectsNow = visibleObjects.toArray(new MapleMapObject[visibleObjects.size()]);
try {
for (MapleMapObject mo : visibleObjectsNow) {
if (mo != null) {
if (mapobjects.get(mo.getObjectId()) == mo) {
updateMapObjectVisibility(player, mo);
} else {
player.removeVisibleMapObject(mo);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
for (MapleMapObject mo : getMapObjectsInRange(player.getPosition(), 722500, rangedMapobjectTypes)) {
if (!player.isMapObjectVisible(mo)) {
mo.sendSpawnData(player.getClient());
player.addVisibleMapObject(mo);
}
}
}
public MaplePortal findClosestSpawnpoint(Point from) {
MaplePortal closest = null;
double shortestDistance = Double.POSITIVE_INFINITY;
for (MaplePortal portal : portals.values()) {
double distance = portal.getPosition().distanceSq(from);
if (portal.getType() >= 0 && portal.getType() <= 2 && distance < shortestDistance && portal.getTargetMapId() == 999999999) {
closest = portal;
shortestDistance = distance;
}
}
return closest;
}
public Collection<MaplePortal> getPortals() {
return Collections.unmodifiableCollection(portals.values());
}
public String getMapName() {
return mapName;
}
public void setMapName(String mapName) {
this.mapName = mapName;
}
public String getStreetName() {
return streetName;
}
public void setClock(boolean hasClock) {
this.clock = hasClock;
}
public boolean hasClock() {
return clock;
}
public void setTown(boolean isTown) {
this.town = isTown;
}
public boolean isTown() {
return town;
}
public void setStreetName(String streetName) {
this.streetName = streetName;
}
public void setEverlast(boolean everlast) {
this.everlast = everlast;
}
public boolean getEverlast() {
return everlast;
}
public int getSpawnedMonstersOnMap() {
return spawnedMonstersOnMap.get();
}
public void setMobCapacity(int capacity) {
this.mobCapacity = capacity;
}
public void nullifyObject(MapleMapObject mmobj) {// nice one Simon (: thanks
// <3
mmobj.nullifyPosition();
mmobj = null;
}
private class ExpireMapItemJob implements Runnable {
private MapleMapItem mapitem;
public ExpireMapItemJob(MapleMapItem mapitem) {
this.mapitem = mapitem;
}
@Override
public void run() {
if (mapitem != null && mapitem == getMapObject(mapitem.getObjectId())) {
mapitem.itemLock.lock();
try {
if (mapitem.isPickedUp()) {
return;
}
MapleMap.this.broadcastMessage(MaplePacketCreator.removeItemFromMap(mapitem.getObjectId(), 0, 0), mapitem.getPosition());
mapitem.setPickedUp(true);
} finally {
mapitem.itemLock.unlock();
MapleMap.this.removeMapObject(mapitem);
}
}
}
}
private class ActivateItemReactor implements Runnable {
private MapleMapItem mapitem;
private MapleReactor reactor;
private MapleClient c;
public ActivateItemReactor(MapleMapItem mapitem, MapleReactor reactor, MapleClient c) {
this.mapitem = mapitem;
this.reactor = reactor;
this.c = c;
}
@Override
public void run() {
if (mapitem != null && mapitem == getMapObject(mapitem.getObjectId())) {
mapitem.itemLock.lock();
try {
TimerManager tMan = TimerManager.getInstance();
if (mapitem.isPickedUp()) {
return;
}
MapleMap.this.broadcastMessage(MaplePacketCreator.removeItemFromMap(mapitem.getObjectId(), 0, 0), mapitem.getPosition());
MapleMap.this.removeMapObject(mapitem);
reactor.hitReactor(c);
reactor.setTimerActive(false);
if (reactor.getDelay() > 0) {
tMan.schedule(new Runnable() {
@Override
public void run() {
reactor.setState((byte) 0);
broadcastMessage(MaplePacketCreator.triggerReactor(reactor, 0));
}
}, reactor.getDelay());
}
} finally {
mapitem.itemLock.unlock();
}
}
}
}
public void respawn() {
if (characters.isEmpty()) {
return;
}
short numShouldSpawn = (short) ((monsterSpawn.size() - spawnedMonstersOnMap.get()) * monsterRate);// Fking
// lol'd
if (numShouldSpawn > 0) {
List<SpawnPoint> randomSpawn = new ArrayList<SpawnPoint>(monsterSpawn);
Collections.shuffle(randomSpawn);
short spawned = 0;
for (SpawnPoint spawnPoint : randomSpawn) {
if (spawnPoint.shouldSpawn()) {
spawnMonster(spawnPoint.getMonster());
spawned++;
}
if (spawned >= numShouldSpawn) {
break;
}
}
}
}
private static interface DelayedPacketCreation {
void sendPackets(MapleClient c);
}
private static interface SpawnCondition {
boolean canSpawn(MapleCharacter chr);
}
public int getHPDec() {
return decHP;
}
public void setHPDec(int delta) {
decHP = delta;
}
public int getHPDecProtect() {
return protectItem;
}
public void setHPDecProtect(int delta) {
this.protectItem = delta;
}
private int hasBoat() {
return docked ? 2 : (boat ? 1 : 0);
}
public void setBoat(boolean hasBoat) {
this.boat = hasBoat;
}
public void setDocked(boolean isDocked) {
this.docked = isDocked;
}
public void broadcastGMMessage(MapleCharacter source, MaplePacket packet, boolean repeatToSource) {
broadcastGMMessage(repeatToSource ? null : source, packet, Double.POSITIVE_INFINITY, source.getPosition());
}
private void broadcastGMMessage(MapleCharacter source, MaplePacket packet, double rangeSq, Point rangedFrom) {
chrRLock.lock();
try {
for (MapleCharacter chr : characters) {
if (chr != source && chr.isGM()) {
if (rangeSq < Double.POSITIVE_INFINITY) {
if (rangedFrom.distanceSq(chr.getPosition()) <= rangeSq) {
chr.getClient().announce(packet);
}
} else {
chr.getClient().announce(packet);
}
}
}
} finally {
chrRLock.unlock();
}
}
public void broadcastNONGMMessage(MapleCharacter source, MaplePacket packet, boolean repeatToSource) {
chrRLock.lock();
try {
for (MapleCharacter chr : characters) {
if (chr != source && !chr.isGM()) {
chr.getClient().announce(packet);
}
}
} finally {
chrRLock.unlock();
}
}
public MapleOxQuiz getOx() {
return ox;
}
public void setOx(MapleOxQuiz set) {
this.ox = set;
}
public void setOxQuiz(boolean b) {
this.isOxQuiz = b;
}
public boolean isOxQuiz() {
return isOxQuiz;
}
public void setOnUserEnter(String onUserEnter) {
this.onUserEnter = onUserEnter;
}
public String getOnUserEnter() {
return onUserEnter;
}
public void setOnFirstUserEnter(String onFirstUserEnter) {
this.onFirstUserEnter = onFirstUserEnter;
}
public String getOnFirstUserEnter() {
return onFirstUserEnter;
}
private boolean hasForcedEquip() {
return fieldType == 81 || fieldType == 82;
}
public void setFieldType(int fieldType) {
this.fieldType = fieldType;
}
public void clearDrops(MapleCharacter player) {
List<MapleMapObject> items = player.getMap().getMapObjectsInRange(player.getPosition(), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.ITEM));
for (MapleMapObject i : items) {
player.getMap().removeMapObject(i);
player.getMap().broadcastMessage(MaplePacketCreator.removeItemFromMap(i.getObjectId(), 0, player.getId()));
}
}
public void clearDrops() {
for (MapleMapObject i : getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.ITEM))) {
removeMapObject(i);
}
}
public void addMapTimer(int time) {
timeLimit = System.currentTimeMillis() + (time * 1000);
broadcastMessage(MaplePacketCreator.getClock(time));
mapMonitor = TimerManager.getInstance().register(new Runnable() {
@Override
public void run() {
if (timeLimit != 0 && timeLimit < System.currentTimeMillis()) {
warpEveryone(getForcedReturnId());
}
if (getCharacters().isEmpty()) {
resetReactors();
killAllMonsters();
clearDrops();
timeLimit = 0;
if (mapid >= 922240100 && mapid <= 922240119) {
toggleHiddenNPC(9001108);
}
mapMonitor.cancel(true);
mapMonitor = null;
}
}
}, 1000);
}
public void setFieldLimit(int fieldLimit) {
this.fieldLimit = fieldLimit;
}
public int getFieldLimit() {
return fieldLimit;
}
public void resetRiceCakes() {
this.riceCakeNum = 0;
}
public void setAllowHPQSummon(boolean b) {
this.allowHPQSummon = b;
}
public void warpEveryone(int to) {
for (MapleCharacter chr : getCharacters()) {
chr.changeMap(to);
}
}
// BEGIN EVENTS
public void setSnowball(int team, MapleSnowball ball) {
switch (team) {
case 0:
this.snowball0 = ball;
break;
case 1:
this.snowball1 = ball;
break;
default:
break;
}
}
public MapleSnowball getSnowball(int team) {
switch (team) {
case 0:
return snowball0;
case 1:
return snowball1;
default:
return null;
}
}
private boolean specialEquip() {// Maybe I shouldn't use fieldType :\
return fieldType == 4 || fieldType == 19;
}
public void setCoconut(MapleCoconut nut) {
this.coconut = nut;
}
public MapleCoconut getCoconut() {
return coconut;
}
public void warpOutByTeam(int team, int mapid) {
for (MapleCharacter chr : getCharacters()) {
if (chr != null) {
if (chr.getTeam() == team) {
chr.changeMap(mapid);
}
}
}
}
public void startEvent(final MapleCharacter chr) {
if (this.mapid == 109080000) {
setCoconut(new MapleCoconut(this));
coconut.startEvent();
} else if (this.mapid == 109040000) {
chr.setFitness(new MapleFitness(chr));
chr.getFitness().startFitness();
} else if (this.mapid == 109030001 || this.mapid == 109030101) {
chr.setOla(new MapleOla(chr));
chr.getOla().startOla();
} else if (this.mapid == 109020001 && getOx() == null) {
setOx(new MapleOxQuiz(this));
getOx().sendQuestion();
setOxQuiz(true);
} else if (this.mapid == 109060000 && getSnowball(chr.getTeam()) == null) {
setSnowball(0, new MapleSnowball(0, this));
setSnowball(1, new MapleSnowball(1, this));
getSnowball(chr.getTeam()).startEvent();
}
}
public boolean eventStarted() {
return eventstarted;
}
public void startEvent() {
this.eventstarted = true;
}
public void setEventStarted(boolean event) {
this.eventstarted = event;
}
public String getEventNPC() {
StringBuilder sb = new StringBuilder();
sb.append("Talk to ");
if (mapid == 60000) {
sb.append("Paul!");
} else if (mapid == 104000000) {
sb.append("Jean!");
} else if (mapid == 200000000) {
sb.append("Martin!");
} else if (mapid == 220000000) {
sb.append("Tony!");
} else {
return null;
}
return sb.toString();
}
public boolean hasEventNPC() {
return this.mapid == 60000 || this.mapid == 104000000 || this.mapid == 200000000 || this.mapid == 220000000;
}
public boolean isStartingEventMap() {
return this.mapid == 109040000 || this.mapid == 109020001 || this.mapid == 109010000 || this.mapid == 109030001 || this.mapid == 109030101;
}
public boolean isEventMap() {
return this.mapid >= 109010000 && this.mapid < 109050000 || this.mapid > 109050001 && this.mapid <= 109090000;
}
public void timeMob(int id, String msg) {
timeMob = new TimeMobEntry(id, msg);
}
public TimeMobEntry getTimeMob() {
return timeMob;
}
public void toggleHiddenNPC(int id) {
for (MapleMapObject obj : mapobjects.values()) {
if (obj.getType() == MapleMapObjectType.NPC) {
MapleNPC npc = (MapleNPC) obj;
if (npc.getId() == id) {
npc.setHide(!npc.isHidden());
if (!npc.isHidden()) // Should only be hidden upon changing
// maps
{
broadcastMessage(MaplePacketCreator.spawnNPC(npc));
}
}
}
}
}
public void setMobInterval(short interval) {
this.mobInterval = interval;
}
public short getMobInterval() {
return mobInterval;
}
}