2964 lines
105 KiB
Java
2964 lines
105 KiB
Java
/*
|
|
This file is part of the OdinMS Maple Story Server
|
|
Copyright (C) 2008 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 version 3 as published by
|
|
the Free Software Foundation. You may not use, modify or distribute
|
|
this program under any other version of the GNU Affero General Public
|
|
License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package server.maps;
|
|
|
|
import client.MapleBuffStat;
|
|
import client.MapleCharacter;
|
|
import client.MapleClient;
|
|
import client.autoban.AutobanFactory;
|
|
import client.inventory.Equip;
|
|
import client.inventory.Item;
|
|
import client.inventory.MapleInventoryType;
|
|
import client.inventory.MaplePet;
|
|
import client.status.MonsterStatus;
|
|
import client.status.MonsterStatusEffect;
|
|
import constants.ItemConstants;
|
|
import constants.ServerConstants;
|
|
import java.awt.Point;
|
|
import java.awt.Rectangle;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Calendar;
|
|
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.Set;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.Map;
|
|
import java.util.Map.Entry;
|
|
import java.util.Random;
|
|
import java.util.concurrent.ScheduledFuture;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
|
|
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
|
|
import net.server.Server;
|
|
import net.server.channel.Channel;
|
|
import scripting.map.MapScriptManager;
|
|
import server.MapleItemInformationProvider;
|
|
import server.MaplePortal;
|
|
import server.MapleStatEffect;
|
|
import server.TimerManager;
|
|
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.life.MapleLifeFactory;
|
|
import server.life.MapleLifeFactory.selfDestruction;
|
|
import server.life.MapleMonster;
|
|
import server.life.MapleMonsterInformationProvider;
|
|
import server.life.MapleNPC;
|
|
import server.life.MonsterDropEntry;
|
|
import server.life.MonsterGlobalDropEntry;
|
|
import server.life.SpawnPoint;
|
|
import server.partyquest.MonsterCarnival;
|
|
import server.partyquest.MonsterCarnivalParty;
|
|
import server.partyquest.Pyramid;
|
|
import scripting.event.EventInstanceManager;
|
|
import server.life.MonsterListener;
|
|
import tools.FilePrinter;
|
|
import tools.MaplePacketCreator;
|
|
import tools.Pair;
|
|
import tools.Randomizer;
|
|
|
|
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<>();
|
|
private Collection<SpawnPoint> monsterSpawn = Collections.synchronizedList(new LinkedList<SpawnPoint>());
|
|
private Collection<SpawnPoint> allMonsterSpawn = Collections.synchronizedList(new LinkedList<SpawnPoint>());
|
|
private AtomicInteger spawnedMonstersOnMap = new AtomicInteger(0);
|
|
private Collection<MapleCharacter> characters = new LinkedHashSet<>();
|
|
private Map<Integer, MaplePortal> portals = new HashMap<>();
|
|
private Map<Integer, Integer> backgroundTypes = new HashMap<>();
|
|
private Map<String, Integer> environment = new LinkedHashMap<String, Integer>();
|
|
private Set<Integer> usedDoors = new HashSet<>();
|
|
private List<Rectangle> areas = new ArrayList<>();
|
|
private MapleFootholdTree footholds = null;
|
|
private int mapid;
|
|
private AtomicInteger runningOid = new AtomicInteger(100);
|
|
private int returnMapId;
|
|
private int channel, world;
|
|
private byte monsterRate;
|
|
private boolean clock;
|
|
private boolean boat;
|
|
private boolean docked = false;
|
|
private EventInstanceManager event = null;
|
|
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 Pair<Integer, String> timeMob = null;
|
|
private short mobInterval = 5000;
|
|
private boolean allowSummons = true; // All maps should have this true at the beginning
|
|
// HPQ
|
|
private int riceCakes = 0;
|
|
private int bunnyDamage = 0;
|
|
// events
|
|
private boolean eventstarted = false, isMuted = 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, int world, int channel, int returnMapId, float monsterRate) {
|
|
this.mapid = mapid;
|
|
this.channel = channel;
|
|
this.world = world;
|
|
this.returnMapId = returnMapId;
|
|
this.monsterRate = (byte) Math.ceil(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 setEventInstance(EventInstanceManager eim) {
|
|
event = eim;
|
|
}
|
|
|
|
public EventInstanceManager getEventInstance() {
|
|
return event;
|
|
}
|
|
|
|
public void broadcastMessage(MapleCharacter source, final byte[] packet) {
|
|
chrRLock.lock();
|
|
try {
|
|
for (MapleCharacter chr : characters) {
|
|
if (chr != source) {
|
|
chr.getClient().announce(packet);
|
|
}
|
|
}
|
|
} finally {
|
|
chrRLock.unlock();
|
|
}
|
|
}
|
|
|
|
public void broadcastGMMessage(MapleCharacter source, final byte[] 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;
|
|
}
|
|
|
|
|
|
private double getRangedDistance() {
|
|
return(ServerConstants.USE_MAXRANGE ? Double.POSITIVE_INFINITY : 722500);
|
|
}
|
|
|
|
public List<MapleMapObject> getMapObjectsInRect(Rectangle box, List<MapleMapObjectType> types) {
|
|
objectRLock.lock();
|
|
final List<MapleMapObject> ret = new LinkedList<>();
|
|
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 mr = (MapleReactor) o;
|
|
mr.lockReactor();
|
|
try {
|
|
mr.setState((byte) 1);
|
|
mr.setShouldCollect(true);
|
|
} finally {
|
|
mr.unlockReactor();
|
|
}
|
|
|
|
broadcastMessage(MaplePacketCreator.triggerReactor((MapleReactor) o, 1));
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
objectRLock.unlock();
|
|
}
|
|
}
|
|
|
|
public final void limitReactor(final int rid, final int num) {
|
|
List<MapleReactor> toDestroy = new ArrayList<>();
|
|
Map<Integer, Integer> contained = new LinkedHashMap<>();
|
|
|
|
for (MapleMapObject obj : getReactors()) {
|
|
MapleReactor mr = (MapleReactor) obj;
|
|
if (contained.containsKey(mr.getId())) {
|
|
if (contained.get(mr.getId()) >= num) {
|
|
toDestroy.add(mr);
|
|
} else {
|
|
contained.put(mr.getId(), contained.get(mr.getId()) + 1);
|
|
}
|
|
} else {
|
|
contained.put(mr.getId(), 1);
|
|
}
|
|
}
|
|
|
|
for (MapleReactor mr : toDestroy) {
|
|
destroyReactor(mr.getObjectId());
|
|
}
|
|
}
|
|
|
|
public boolean isAllReactorState(final int reactorId, final int state) {
|
|
for (MapleMapObject mo : getReactors()) {
|
|
MapleReactor r = (MapleReactor) mo;
|
|
|
|
if (r.getId() == reactorId && r.getState() != state) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
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 {
|
|
int curOID = getUsableOID();
|
|
mapobject.setObjectId(curOID);
|
|
this.mapobjects.put(curOID, mapobject);
|
|
} 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();
|
|
objectWLock.lock();
|
|
try {
|
|
int curOID = getUsableOID();
|
|
mapobject.setObjectId(curOID);
|
|
this.mapobjects.put(curOID, mapobject);
|
|
for (MapleCharacter chr : characters) {
|
|
if (condition == null || condition.canSpawn(chr)) {
|
|
if (chr.getPosition().distanceSq(mapobject.getPosition()) <= getRangedDistance()) {
|
|
packetbakery.sendPackets(chr.getClient());
|
|
chr.addVisibleMapObject(mapobject);
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
chrRLock.unlock();
|
|
objectWLock.unlock();
|
|
}
|
|
}
|
|
|
|
private void spawnRangedMapObject(MapleMapObject mapobject, DelayedPacketCreation packetbakery, SpawnCondition condition) {
|
|
chrRLock.lock();
|
|
|
|
try {
|
|
int curOID = getUsableOID();
|
|
mapobject.setObjectId(curOID);
|
|
for (MapleCharacter chr : characters) {
|
|
if (condition == null || condition.canSpawn(chr)) {
|
|
if (chr.getPosition().distanceSq(mapobject.getPosition()) <= getRangedDistance()) {
|
|
packetbakery.sendPackets(chr.getClient());
|
|
chr.addVisibleMapObject(mapobject);
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
chrRLock.unlock();
|
|
}
|
|
}
|
|
|
|
private int getUsableOID() {
|
|
if (runningOid.incrementAndGet() > 2000000000) {
|
|
runningOid.set(1000);
|
|
}
|
|
objectRLock.lock();
|
|
try {
|
|
if (mapobjects.containsKey(runningOid.get())) {
|
|
while (mapobjects.containsKey(runningOid.incrementAndGet()));
|
|
}
|
|
} finally {
|
|
objectRLock.unlock();
|
|
}
|
|
|
|
return runningOid.get();
|
|
}
|
|
|
|
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 - 85));
|
|
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 chRate = chr.getDropRate();
|
|
Item idrop;
|
|
byte d = 1;
|
|
Point pos = new Point(0, mob.getPosition().y);
|
|
|
|
Map<MonsterStatus, MonsterStatusEffect> stati = mob.getStati();
|
|
if (stati.containsKey(MonsterStatus.SHOWDOWN)) {
|
|
chRate *= (stati.get(MonsterStatus.SHOWDOWN).getStati().get(MonsterStatus.SHOWDOWN).doubleValue() / 100.0 + 1.0);
|
|
}
|
|
|
|
final MapleMonsterInformationProvider mi = MapleMonsterInformationProvider.getInstance();
|
|
final List<MonsterDropEntry> dropEntry = new ArrayList<>(mi.retrieveDrop(mob.getId()));
|
|
|
|
Collections.shuffle(dropEntry);
|
|
for (final MonsterDropEntry de : dropEntry) {
|
|
if (Randomizer.nextInt(999999) < de.chance * chRate) {
|
|
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, (short) 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) {
|
|
if (ItemConstants.getInventoryType(de.itemId) == MapleInventoryType.EQUIP) {
|
|
idrop = ii.randomizeStats((Equip) ii.getEquipById(de.itemId));
|
|
} else {
|
|
idrop = new Item(de.itemId, (short) 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 Item 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);
|
|
mdrop.setDropTime(System.currentTimeMillis());
|
|
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.announce(MaplePacketCreator.dropItemFromMapObject(mdrop, mob.getPosition(), dropPos, (byte) 1));
|
|
}
|
|
}
|
|
}, null);
|
|
|
|
TimerManager.getInstance().schedule(new ExpireMapItemJob(mdrop), ServerConstants.ITEM_EXPIRE_TIME);
|
|
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);
|
|
mdrop.setDropTime(System.currentTimeMillis());
|
|
|
|
spawnAndAddRangedMapObject(mdrop, new DelayedPacketCreation() {
|
|
@Override
|
|
public void sendPackets(MapleClient c) {
|
|
c.announce(MaplePacketCreator.dropItemFromMapObject(mdrop, dropper.getPosition(), droppos, (byte) 1));
|
|
}
|
|
}, null);
|
|
|
|
TimerManager.getInstance().schedule(new ExpireMapItemJob(mdrop), ServerConstants.ITEM_EXPIRE_TIME);
|
|
}
|
|
|
|
public final void disappearingItemDrop(final MapleMapObject dropper, final MapleCharacter owner, final Item 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 int countMonsters() {
|
|
return getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.MONSTER)).size();
|
|
}
|
|
|
|
public int countReactors() {
|
|
return getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.REACTOR)).size();
|
|
}
|
|
|
|
public final List<MapleMapObject> getReactors() {
|
|
return getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.REACTOR));
|
|
}
|
|
|
|
public int countItems() {
|
|
return getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.ITEM)).size();
|
|
}
|
|
|
|
public final List<MapleMapObject> getItems() {
|
|
return getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.ITEM));
|
|
}
|
|
|
|
public int countPlayers() {
|
|
return getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.PLAYER)).size();
|
|
}
|
|
|
|
public List<MapleMapObject> getPlayers() {
|
|
return getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.PLAYER));
|
|
}
|
|
|
|
public List<MapleCharacter> getAllPlayers() {
|
|
List<MapleCharacter> character = new LinkedList<>();
|
|
chrRLock.lock();
|
|
try {
|
|
for (MapleCharacter a : characters) {
|
|
character.add(a);
|
|
}
|
|
} finally {
|
|
chrRLock.unlock();
|
|
}
|
|
|
|
return character;
|
|
}
|
|
|
|
public List<MapleCharacter> getPlayersInRange(Rectangle box, List<MapleCharacter> chr) {
|
|
List<MapleCharacter> character = new LinkedList<>();
|
|
chrRLock.lock();
|
|
try {
|
|
for (MapleCharacter a : characters) {
|
|
if (chr.contains(a.getClient().getPlayer())) {
|
|
if (box.contains(a.getPosition())) {
|
|
character.add(a);
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
chrRLock.unlock();
|
|
}
|
|
|
|
return character;
|
|
}
|
|
|
|
public int countAlivePlayers() {
|
|
int count = 0;
|
|
|
|
for(MapleCharacter mc: getAllPlayers()) {
|
|
if(mc.isAlive()) 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.lockMonster();
|
|
try {
|
|
if (!monster.isAlive()) {
|
|
return false;
|
|
}
|
|
Pair<Integer, Integer> cool = monster.getStats().getCool();
|
|
if (cool != null) {
|
|
Pyramid pq = (Pyramid) chr.getPartyQuest();
|
|
if (pq != null) {
|
|
if (damage > 0) {
|
|
if (damage >= cool.getLeft()) {
|
|
if ((Math.random() * 100) < cool.getRight()) {
|
|
pq.cool();
|
|
} else {
|
|
pq.kill();
|
|
}
|
|
} else {
|
|
pq.kill();
|
|
}
|
|
} else {
|
|
pq.miss();
|
|
}
|
|
killed = true;
|
|
}
|
|
}
|
|
if (damage > 0) {
|
|
monster.damage(chr, damage);
|
|
if (!monster.isAlive()) { // monster just died
|
|
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 (monster.isAlive() && (monster.getId() >= 8810010 && monster.getId() <= 8810017)) {
|
|
if (mons.getId() == 8810018) {
|
|
killMonster(mons, chr, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
monster.unlockMonster();
|
|
}
|
|
if (monster.getStats().selfDestruction() != null && monster.getStats().selfDestruction().getHp() > -1) {// should work ;p
|
|
if (monster.getHp() <= monster.getStats().selfDestruction().getHp()) {
|
|
killMonster(monster, chr, true, monster.getStats().selfDestruction().getAction());
|
|
return true;
|
|
}
|
|
}
|
|
if (killed) {
|
|
killMonster(monster, chr, true);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public List<MapleMonster> getMonsters() {
|
|
List<MapleMonster> mobs = new ArrayList<>();
|
|
for (MapleMapObject object : this.getMapObjects()) {
|
|
if(object instanceof MapleMonster) mobs.add((MapleMonster)object);
|
|
}
|
|
return mobs;
|
|
}
|
|
|
|
public void killMonster(final MapleMonster monster, final MapleCharacter chr, final boolean withDrops) {
|
|
killMonster(monster, chr, withDrops, 1);
|
|
}
|
|
|
|
public void killMonster(final MapleMonster monster, final MapleCharacter chr, final boolean withDrops, int animation) {
|
|
if(monster == null) return;
|
|
|
|
if (chr == null) {
|
|
spawnedMonstersOnMap.decrementAndGet();
|
|
monster.setHp(0);
|
|
removeMapObject(monster);
|
|
monster.dispatchMonsterKilled();
|
|
broadcastMessage(MaplePacketCreator.killMonster(monster.getObjectId(), animation), monster.getPosition());
|
|
return;
|
|
}
|
|
if (monster.getStats().getLevel() >= chr.getLevel() + 30 && !chr.isGM()) {
|
|
AutobanFactory.GENERAL.alert(chr, " for killing a " + monster.getName() + " which is over 30 levels higher.");
|
|
}
|
|
/*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.getPlayers()) {
|
|
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 && chr.getMapId() == 240060200) {
|
|
for (Channel cserv : Server.getInstance().getWorld(world).getChannels()) {
|
|
for (MapleCharacter player : cserv.getPlayerStorage().getAllCharacters()) {
|
|
player.dropMessage("To the crew that have finally conquered Horned Tail after numerous attempts, I salute thee! You are the true heroes of Leafre!!");
|
|
}
|
|
}
|
|
}
|
|
spawnedMonstersOnMap.decrementAndGet();
|
|
monster.setHp(0);
|
|
//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);
|
|
}
|
|
|
|
monster.dispatchMonsterKilled();
|
|
broadcastMessage(MaplePacketCreator.killMonster(monster.getObjectId(), animation), monster.getPosition());
|
|
}
|
|
|
|
public void killFriendlies(MapleMonster mob) {
|
|
this.killMonster(mob, (MapleCharacter) getPlayers().get(0), false);
|
|
}
|
|
|
|
public void killMonster(int monsId) {
|
|
for (MapleMapObject mmo : getMapObjects()) {
|
|
if (mmo instanceof MapleMonster) {
|
|
if (((MapleMonster) mmo).getId() == monsId) {
|
|
this.killMonster((MapleMonster) mmo, (MapleCharacter) getPlayers().get(0), false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void monsterCloakingDevice() {
|
|
for (MapleMapObject monstermo : getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.MONSTER))) {
|
|
MapleMonster monster = (MapleMonster) monstermo;
|
|
broadcastMessage(MaplePacketCreator.makeMonsterInvisible(monster));
|
|
}
|
|
}
|
|
|
|
public void softKillAllMonsters() {
|
|
for (SpawnPoint spawnPoint : monsterSpawn) {
|
|
spawnPoint.setDenySpawn(true);
|
|
}
|
|
|
|
for (MapleMapObject monstermo : getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.MONSTER))) {
|
|
MapleMonster monster = (MapleMonster) monstermo;
|
|
if (monster.getStats().isFriendly()) {
|
|
continue;
|
|
}
|
|
spawnedMonstersOnMap.decrementAndGet();
|
|
monster.setHp(0);
|
|
removeMapObject(monster);
|
|
}
|
|
}
|
|
|
|
public void killAllMonstersNotFriendly() {
|
|
for (SpawnPoint spawnPoint : monsterSpawn) {
|
|
spawnPoint.setDenySpawn(true);
|
|
}
|
|
|
|
for (MapleMapObject monstermo : getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.MONSTER))) {
|
|
MapleMonster monster = (MapleMonster) monstermo;
|
|
if (monster.getStats().isFriendly()) {
|
|
continue;
|
|
}
|
|
|
|
killMonster(monster, null, false, 1);
|
|
}
|
|
}
|
|
|
|
public void killAllMonsters() {
|
|
for (SpawnPoint spawnPoint : monsterSpawn) {
|
|
spawnPoint.setDenySpawn(true);
|
|
}
|
|
|
|
for (MapleMapObject monstermo : getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.MONSTER))) {
|
|
MapleMonster monster = (MapleMonster) monstermo;
|
|
|
|
killMonster(monster, null, false, 1);
|
|
}
|
|
}
|
|
|
|
public final void destroyReactors(final int first, final int last) {
|
|
List<MapleReactor> toDestroy = new ArrayList<>();
|
|
List<MapleMapObject> reactors = getReactors();
|
|
|
|
for (MapleMapObject obj : reactors) {
|
|
MapleReactor mr = (MapleReactor) obj;
|
|
if (mr.getId() >= first && mr.getId() <= last) {
|
|
toDestroy.add(mr);
|
|
}
|
|
}
|
|
|
|
for (MapleReactor mr : toDestroy) {
|
|
destroyReactor(mr.getObjectId());
|
|
}
|
|
}
|
|
|
|
public void destroyReactor(int oid) {
|
|
final MapleReactor reactor = getReactorByOid(oid);
|
|
TimerManager tMan = TimerManager.getInstance();
|
|
broadcastMessage(MaplePacketCreator.destroyReactor(reactor));
|
|
reactor.setAlive(false);
|
|
removeMapObject(reactor);
|
|
|
|
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.lockReactor();
|
|
try {
|
|
r.setState((byte) 0);
|
|
r.setShouldCollect(true);
|
|
} finally {
|
|
r.unlockReactor();
|
|
}
|
|
|
|
broadcastMessage(MaplePacketCreator.triggerReactor(r, 0));
|
|
}
|
|
}
|
|
} finally {
|
|
objectRLock.unlock();
|
|
}
|
|
}
|
|
|
|
public void shuffleReactors() {
|
|
List<Point> points = new ArrayList<>();
|
|
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 final void shuffleReactors(int first, int last) {
|
|
List<Point> points = new ArrayList<>();
|
|
List<MapleMapObject> reactors = getReactors();
|
|
List<MapleMapObject> targets = new LinkedList<>();
|
|
|
|
for (MapleMapObject obj : reactors) {
|
|
MapleReactor mr = (MapleReactor) obj;
|
|
if (mr.getId() >= first && mr.getId() <= last) {
|
|
points.add(mr.getPosition());
|
|
targets.add(obj);
|
|
}
|
|
}
|
|
Collections.shuffle(points);
|
|
for (MapleMapObject obj : targets) {
|
|
MapleReactor mr = (MapleReactor) obj;
|
|
mr.setPosition(points.remove(points.size() - 1));
|
|
}
|
|
}
|
|
|
|
public final void shuffleReactors(List<Object> list) {
|
|
List<Point> points = new ArrayList<>();
|
|
List<MapleMapObject> listObjects = new ArrayList<>();
|
|
|
|
List<MapleMapObject> reactors = getReactors();
|
|
List<MapleMapObject> targets = new LinkedList<>();
|
|
|
|
objectRLock.lock();
|
|
try {
|
|
for (Object obj : list) {
|
|
if(obj instanceof MapleMapObject) {
|
|
MapleMapObject mmo = (MapleMapObject) obj;
|
|
|
|
if(mapobjects.containsValue(mmo)) {
|
|
listObjects.add(mmo);
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
objectRLock.unlock();
|
|
}
|
|
|
|
for (MapleMapObject obj : listObjects) {
|
|
MapleReactor mr = (MapleReactor) obj;
|
|
|
|
points.add(mr.getPosition());
|
|
targets.add(obj);
|
|
}
|
|
Collections.shuffle(points);
|
|
for (MapleMapObject obj : targets) {
|
|
MapleReactor mr = (MapleReactor) obj;
|
|
mr.setPosition(points.remove(points.size() - 1));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Automagically finds a new controller for the given monster from the chars
|
|
* on the map...
|
|
*
|
|
* @param monster
|
|
*/
|
|
public void updateMonsterController(MapleMonster monster) {
|
|
monster.lockMonster();
|
|
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.unlockMonster();
|
|
}
|
|
}
|
|
|
|
public Collection<MapleMapObject> getMapObjects() {
|
|
objectRLock.lock();
|
|
try {
|
|
return Collections.unmodifiableCollection(mapobjects.values());
|
|
}
|
|
finally {
|
|
objectRLock.unlock();
|
|
}
|
|
}
|
|
|
|
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) {
|
|
objectRLock.lock();
|
|
try {
|
|
return mapobjects.get(oid);
|
|
} finally {
|
|
objectRLock.unlock();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
return (mmo != null && mmo.getType() == MapleMapObjectType.MONSTER) ? (MapleMonster) mmo : null;
|
|
}
|
|
|
|
public MapleReactor getReactorByOid(int oid) {
|
|
MapleMapObject mmo = getMapObject(oid);
|
|
return (mmo != null && mmo.getType() == MapleMapObjectType.REACTOR) ? (MapleReactor) mmo : null;
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
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 spawnMonsterOnGroundBelow(int id, int x, int y) {
|
|
MapleMonster mob = MapleLifeFactory.getMonster(id);
|
|
spawnMonsterOnGroundBelow(mob, new Point(x, y));
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
public void addBunnyHit() {
|
|
bunnyDamage++;
|
|
if (bunnyDamage > 5) {
|
|
broadcastMessage(MaplePacketCreator.serverNotice(6, "The Moon Bunny is feeling sick. Please protect it so it can make delicious rice cakes."));
|
|
bunnyDamage = 0;
|
|
}
|
|
}
|
|
|
|
private void monsterItemDrop(final MapleMonster m, final Item item, long delay) {
|
|
final ScheduledFuture<?> monsterItemDrop = TimerManager.getInstance().register(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
if (MapleMap.this.getMonsterById(m.getId()) != null && !MapleMap.this.getPlayers().isEmpty()) {
|
|
if (item.getItemId() == 4001101) {
|
|
MapleMap.this.riceCakes++;
|
|
MapleMap.this.broadcastMessage(MaplePacketCreator.serverNotice(6, "The Moon Bunny made rice cake number " + (MapleMap.this.riceCakes)));
|
|
}
|
|
spawnItemDrop(m, (MapleCharacter) getPlayers().get(0), item, m.getPosition(), false, false);
|
|
}
|
|
}
|
|
}, 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 - 3); // Using -3 fixes issues with spawning pets causing a lot of issues.
|
|
spos = calcPointBelow(spos);
|
|
spos.y--;//shouldn't be null!
|
|
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 spawnAllMonsterIdFromMapSpawnList(int id) {
|
|
spawnAllMonsterIdFromMapSpawnList(id, 1, false);
|
|
}
|
|
|
|
public void spawnAllMonsterIdFromMapSpawnList(int id, int difficulty, boolean isPq) {
|
|
for(SpawnPoint sp: allMonsterSpawn) {
|
|
if(sp.getMonsterId() == id) {
|
|
spawnMonster(sp.getMonster(), difficulty, isPq);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void spawnAllMonstersFromMapSpawnList() {
|
|
spawnAllMonstersFromMapSpawnList(1, false);
|
|
}
|
|
|
|
public void spawnAllMonstersFromMapSpawnList(int difficulty, boolean isPq) {
|
|
for(SpawnPoint sp: allMonsterSpawn) {
|
|
spawnMonster(sp.getMonster(), difficulty, isPq);
|
|
}
|
|
}
|
|
|
|
public void spawnMonster(final MapleMonster monster) {
|
|
spawnMonster(monster, 1, false);
|
|
}
|
|
|
|
public void spawnMonster(final MapleMonster monster, int difficulty, boolean isPq) {
|
|
if (mobCapacity != -1 && mobCapacity == spawnedMonstersOnMap.get()) {
|
|
return;//PyPQ
|
|
}
|
|
|
|
monster.changeDifficulty(difficulty, isPq);
|
|
|
|
monster.setMap(this);
|
|
if(getEventInstance() != null) getEventInstance().registerMonster(monster);
|
|
|
|
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), 9300093 - Tylus
|
|
if (monster.getId() == 9300102) {
|
|
monsterItemDrop(monster, new Item(4031507, (short) 0, (short) 1), monster.getDropPeriodTime());
|
|
} else if (monster.getId() == 9300061) {
|
|
monsterItemDrop(monster, new Item(4001101, (short) 0, (short) 1), monster.getDropPeriodTime() / 3);
|
|
} else if (monster.getId() == 9300093) {
|
|
monsterItemDrop(monster, new Item(4031495, (short) 0, (short) 1), monster.getDropPeriodTime());
|
|
} else {
|
|
FilePrinter.printError(FilePrinter.UNHANDLED_EVENT, "UNCODED TIMED MOB DETECTED: " + monster.getId());
|
|
}
|
|
}
|
|
spawnedMonstersOnMap.incrementAndGet();
|
|
final selfDestruction selfDestruction = monster.getStats().selfDestruction();
|
|
if (monster.getStats().removeAfter() > 0 || selfDestruction != null && selfDestruction.getHp() < 0) {
|
|
if (selfDestruction == null) {
|
|
TimerManager.getInstance().schedule(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
killMonster(monster, null, false);
|
|
}
|
|
}, monster.getStats().removeAfter() * 1000);
|
|
} else {
|
|
TimerManager.getInstance().schedule(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
killMonster(monster, null, false, selfDestruction.getAction());
|
|
}
|
|
}, selfDestruction.removeAfter() * 1000);
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
if(spos == null) return;
|
|
|
|
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.lockReactor();
|
|
try {
|
|
reactor.setShouldCollect(true);
|
|
reactor.setState((byte) 0);
|
|
reactor.setAlive(true);
|
|
} finally {
|
|
reactor.unlockReactor();
|
|
}
|
|
|
|
spawnReactor(reactor);
|
|
}
|
|
|
|
public void spawnDoor(final MapleDoorObject door) {
|
|
spawnAndAddRangedMapObject(door, new DelayedPacketCreation() {
|
|
@Override
|
|
public void sendPackets(MapleClient c) {
|
|
if (door.getFrom().getId() == c.getPlayer().getMapId()) {
|
|
c.announce(MaplePacketCreator.spawnPortal(door.getFrom().getId(), door.getTo().getId(), door.toPosition()));
|
|
if(!door.inTown()) c.announce(MaplePacketCreator.spawnDoor(door.getOwnerId(), door.getPosition(), false));
|
|
|
|
if (c.getPlayer().getParty() != null && (door.getOwnerId() == c.getPlayer().getId() || c.getPlayer().getParty().getMemberById(door.getOwnerId()) != null)) {
|
|
c.announce(MaplePacketCreator.partyPortal(door.getTown().getId(), door.getArea().getId(), door.getAreaPosition()));
|
|
}
|
|
}
|
|
|
|
c.announce(MaplePacketCreator.enableActions());
|
|
}
|
|
}, new SpawnCondition() {
|
|
@Override
|
|
public boolean canSpawn(MapleCharacter chr) {
|
|
return chr.getMapId() == door.getFrom().getId();
|
|
}
|
|
});
|
|
}
|
|
|
|
public boolean setUsingDoorPortal(MaplePortal port) {
|
|
objectWLock.lock();
|
|
try {
|
|
if(usedDoors.contains(port.getId())) return false;
|
|
|
|
usedDoors.add(port.getId());
|
|
return true;
|
|
} finally {
|
|
objectWLock.unlock();
|
|
}
|
|
}
|
|
|
|
public void setDisposeDoorPortal(MaplePortal port) {
|
|
objectWLock.lock();
|
|
try {
|
|
usedDoors.remove(port.getId());
|
|
} finally {
|
|
objectWLock.unlock();
|
|
}
|
|
}
|
|
|
|
public boolean getNotUsingDoorPortal() {
|
|
objectRLock.lock();
|
|
try {
|
|
return usedDoors.isEmpty();
|
|
} finally {
|
|
objectRLock.unlock();
|
|
}
|
|
}
|
|
|
|
public List<MaplePortal> getAvailableDoorPortals() {
|
|
objectRLock.lock();
|
|
try {
|
|
List<MaplePortal> availablePortals = new ArrayList<>();
|
|
|
|
for (MaplePortal port : getPortals()) {
|
|
if (port.getType() == MaplePortal.DOOR_PORTAL && !usedDoors.contains(port.getId())) {
|
|
availablePortals.add(port);
|
|
}
|
|
}
|
|
|
|
return availablePortals;
|
|
} finally {
|
|
objectRLock.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, boolean recovery) {
|
|
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 if (recovery) {
|
|
Runnable poisonTask = new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
List<MapleMapObject> players = getMapObjectsInBox(mist.getBox(), Collections.singletonList(MapleMapObjectType.PLAYER));
|
|
for (MapleMapObject mo : players) {
|
|
if (mist.makeChanceResult()) {
|
|
MapleCharacter chr = (MapleCharacter) mo;
|
|
if (mist.getOwner().getId() == chr.getId() || mist.getOwner().getParty() != null && mist.getOwner().getParty().containsMembers(chr.getMPC())) {
|
|
chr.addMP((int) mist.getSourceSkill().getEffect(chr.getSkillLevel(mist.getSourceSkill().getId())).getX() * chr.getMp() / 100);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
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 Item item, Point pos, final boolean ffaDrop, final boolean playerDrop) {
|
|
spawnItemDrop(dropper, owner, item, pos, (byte)(ffaDrop ? 2 : 0), playerDrop);
|
|
}
|
|
|
|
public final void spawnItemDrop(final MapleMapObject dropper, final MapleCharacter owner, final Item item, Point pos, final byte dropType, final boolean playerDrop) {
|
|
final Point droppos = calcDropPos(pos, pos);
|
|
final MapleMapItem drop = new MapleMapItem(item, droppos, dropper, owner, dropType, playerDrop);
|
|
drop.setDropTime(System.currentTimeMillis());
|
|
|
|
spawnAndAddRangedMapObject(drop, new DelayedPacketCreation() {
|
|
@Override
|
|
public void sendPackets(MapleClient c) {
|
|
c.announce(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), ServerConstants.ITEM_EXPIRE_TIME);
|
|
activateItemReactors(drop, owner.getClient());
|
|
}
|
|
}
|
|
|
|
public final void spawnItemDropList(List<Integer> list, final MapleMapObject dropper, final MapleCharacter owner, Point pos) {
|
|
spawnItemDropList(list, 1, 1, dropper, owner, pos, true, false);
|
|
}
|
|
|
|
public final void spawnItemDropList(List<Integer> list, int minCopies, int maxCopies, final MapleMapObject dropper, final MapleCharacter owner, Point pos) {
|
|
spawnItemDropList(list, minCopies, maxCopies, dropper, owner, pos, true, false);
|
|
}
|
|
|
|
// spawns item instances of all defined item ids on a list
|
|
public final void spawnItemDropList(List<Integer> list, int minCopies, int maxCopies, final MapleMapObject dropper, final MapleCharacter owner, Point pos, final boolean ffaDrop, final boolean playerDrop) {
|
|
int copies = (maxCopies - minCopies) + 1;
|
|
if(copies < 1) return;
|
|
|
|
Collections.shuffle(list);
|
|
|
|
MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
|
|
Random rnd = new Random();
|
|
|
|
final Point dropPos = new Point(pos);
|
|
dropPos.x -= (12 * list.size());
|
|
|
|
for(int i = 0; i < list.size(); i++) {
|
|
if(list.get(i) == 0) {
|
|
spawnMesoDrop(owner != null ? 10 * owner.getMesoRate() : 10, calcDropPos(dropPos, pos), dropper, owner, playerDrop, (byte) (ffaDrop ? 2 : 0));
|
|
}
|
|
else {
|
|
final Item drop;
|
|
int randomedId = list.get(i);
|
|
|
|
if (ii.getInventoryType(randomedId) != MapleInventoryType.EQUIP) {
|
|
drop = new Item(randomedId, (short) 0, (short) (rnd.nextInt(copies) + minCopies));
|
|
} else {
|
|
drop = ii.randomizeStats((Equip) ii.getEquipById(randomedId));
|
|
}
|
|
|
|
spawnItemDrop(dropper, owner, drop, calcDropPos(dropPos, pos), ffaDrop, playerDrop);
|
|
}
|
|
|
|
dropPos.x += 25;
|
|
}
|
|
}
|
|
|
|
private void activateItemReactors(final MapleMapItem drop, final MapleClient c) {
|
|
final Item item = drop.getItem();
|
|
|
|
for (final MapleMapObject o : getReactors()) {
|
|
final MapleReactor react = (MapleReactor) o;
|
|
|
|
if (react.getReactorType() == 100) {
|
|
if (react.getReactItem(react.getEventState()).getLeft() == item.getItemId() && react.getReactItem(react.getEventState()).getRight() == item.getQuantity()) {
|
|
|
|
if (react.getArea().contains(drop.getPosition())) {
|
|
TimerManager.getInstance().schedule(new ActivateItemReactor(drop, react, c), 5000);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void changeEnvironment(String mapObj, int newState) {
|
|
broadcastMessage(MaplePacketCreator.environmentChange(mapObj, newState));
|
|
}
|
|
|
|
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 {
|
|
characters.add(chr);
|
|
} finally {
|
|
chrWLock.unlock();
|
|
}
|
|
chr.setMapId(mapid);
|
|
if (onFirstUserEnter.length() != 0 && !chr.hasEntered(onFirstUserEnter, mapid) && MapScriptManager.getInstance().scriptExists(onFirstUserEnter, true)) {
|
|
if (countPlayers() <= 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 == 200090060) { // To Rien
|
|
chr.announce(MaplePacketCreator.getClock(60));
|
|
TimerManager.getInstance().schedule(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
if (chr.getMapId() == 200090060) {
|
|
chr.changeMap(140020300);
|
|
}
|
|
}
|
|
}, 60 * 1000);
|
|
} else if (mapid == 200090070) { // To Lith Harbor
|
|
chr.announce(MaplePacketCreator.getClock(60));
|
|
TimerManager.getInstance().schedule(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
if (chr.getMapId() == 200090070) {
|
|
chr.changeMap(104000000, 3);
|
|
}
|
|
}
|
|
}, 60 * 1000);
|
|
} else if (mapid == 200090030) { // To Ereve (SkyFerry)
|
|
chr.getClient().announce(MaplePacketCreator.getClock(60));
|
|
TimerManager.getInstance().schedule(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
if (chr.getMapId() == 200090030) {
|
|
chr.changeMap(130000210);
|
|
}
|
|
}
|
|
}, 60 * 1000);
|
|
} else if (mapid == 200090031) { // To Victoria Island (SkyFerry)
|
|
chr.getClient().announce(MaplePacketCreator.getClock(60));
|
|
TimerManager.getInstance().schedule(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
if (chr.getMapId() == 200090031) {
|
|
chr.changeMap(101000400);
|
|
}
|
|
}
|
|
}, 60 * 1000);
|
|
} else if (mapid == 200090021) { // To Orbis (SkyFerry)
|
|
chr.getClient().announce(MaplePacketCreator.getClock(60));
|
|
TimerManager.getInstance().schedule(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
if (chr.getMapId() == 200090021) {
|
|
chr.changeMap(200000161);
|
|
}
|
|
}
|
|
}, 60 * 1000);
|
|
} else if (mapid == 200090020) { // To Ereve From Orbis (SkyFerry)
|
|
chr.getClient().announce(MaplePacketCreator.getClock(60));
|
|
TimerManager.getInstance().schedule(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
if (chr.getMapId() == 200090020) {
|
|
chr.changeMap(130000210);
|
|
}
|
|
}
|
|
}, 60 * 1000);
|
|
} else if (mapid == 103040400) {
|
|
if (chr.getEventInstance() != null) {
|
|
chr.getEventInstance().movePlayer(chr);
|
|
}
|
|
} else if (MapleMiniDungeon.isDungeonMap(mapid)) {
|
|
final MapleMiniDungeon dungeon = MapleMiniDungeon.getDungeon(mapid);
|
|
chr.getClient().announce(MaplePacketCreator.getClock(30 * 60));
|
|
TimerManager.getInstance().schedule(new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
if (MapleMiniDungeon.isDungeonMap(chr.getMapId())) {
|
|
chr.changeMap(dungeon.getBase());
|
|
}
|
|
}
|
|
}, 30 * 60 * 1000);
|
|
}
|
|
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));
|
|
|
|
List<Pair<MapleBuffStat, Integer>> dsstat = Collections.singletonList(new Pair<MapleBuffStat, Integer>(MapleBuffStat.DARKSIGHT, 0));
|
|
broadcastGMMessage(chr, MaplePacketCreator.giveForeignBuff(chr.getId(), dsstat), false);
|
|
} 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());
|
|
}
|
|
|
|
final MapleDragon dragon = chr.getDragon();
|
|
if (dragon != null) {
|
|
dragon.setPosition(chr.getPosition());
|
|
this.addMapObject(dragon);
|
|
if (chr.isHidden()) {
|
|
this.broadcastGMMessage(chr, MaplePacketCreator.spawnDragon(dragon));
|
|
} else {
|
|
this.broadcastMessage(chr, MaplePacketCreator.spawnDragon(dragon));
|
|
}
|
|
}
|
|
|
|
MapleStatEffect summonStat = chr.getStatForBuff(MapleBuffStat.SUMMON);
|
|
if (summonStat != null) {
|
|
MapleSummon summon = chr.getSummonByKey(summonStat.getSourceId());
|
|
summon.setPosition(chr.getPosition());
|
|
chr.getMap().spawnSummon(summon);
|
|
updateMapObjectVisibility(chr, summon);
|
|
}
|
|
if (mapEffect != null) {
|
|
mapEffect.sendStartData(chr.getClient());
|
|
}
|
|
chr.getClient().announce(MaplePacketCreator.resetForcedStats());
|
|
if (mapid == 914000200 || mapid == 914000210 || mapid == 914000220) {
|
|
chr.getClient().announce(MaplePacketCreator.aranGodlyStats());
|
|
}
|
|
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() > 0) {
|
|
if(hasBoat() == 1) chr.getClient().announce((MaplePacketCreator.boatPacket(true)));
|
|
else chr.getClient().announce(MaplePacketCreator.boatPacket(false));
|
|
}
|
|
|
|
chr.receivePartyMemberHP();
|
|
}
|
|
|
|
public MaplePortal getRandomPlayerSpawnpoint() {
|
|
List<MaplePortal> spawnPoints = new ArrayList<>();
|
|
for (MaplePortal portal : portals.values()) {
|
|
if (portal.getType() >= 0 && portal.getType() <= 1) {
|
|
spawnPoints.add(portal);
|
|
}
|
|
}
|
|
MaplePortal portal = spawnPoints.get(new Random().nextInt(spawnPoints.size()));
|
|
return portal != null ? portal : getPortal(0);
|
|
}
|
|
|
|
public MaplePortal findClosestPlayerSpawnpoint(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() <= 1 && distance < shortestDistance && portal.getTargetMapId() == 999999999) {
|
|
closest = portal;
|
|
shortestDistance = distance;
|
|
}
|
|
}
|
|
return closest;
|
|
}
|
|
|
|
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 Collection<MaplePortal> getPortals() {
|
|
return Collections.unmodifiableCollection(portals.values());
|
|
}
|
|
|
|
public void removePlayer(MapleCharacter chr) {
|
|
chrWLock.lock();
|
|
try {
|
|
characters.remove(chr);
|
|
} finally {
|
|
chrWLock.unlock();
|
|
}
|
|
removeMapObject(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 : new ArrayList<>(chr.getSummonsValues())) {
|
|
if (summon.isStationary()) {
|
|
chr.cancelBuffStats(MapleBuffStat.PUPPET);
|
|
} else {
|
|
removeMapObject(summon);
|
|
}
|
|
}
|
|
|
|
if (chr.getDragon() != null) {
|
|
removeMapObject(chr.getDragon());
|
|
if (chr.isHidden()) {
|
|
this.broadcastGMMessage(chr, MaplePacketCreator.removeDragon(chr.getId()));
|
|
} else {
|
|
this.broadcastMessage(chr, MaplePacketCreator.removeDragon(chr.getId()));
|
|
}
|
|
}
|
|
}
|
|
|
|
public void broadcastStringMessage(int type, String message) {
|
|
broadcastMessage(MaplePacketCreator.serverNotice(type, message));
|
|
}
|
|
|
|
public void broadcastMessage(final byte[] packet) {
|
|
broadcastMessage(null, packet, Double.POSITIVE_INFINITY, null);
|
|
}
|
|
|
|
public void broadcastGMMessage(final byte[] 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, final byte[] 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, final byte[] packet, boolean repeatToSource, boolean ranged) {
|
|
broadcastMessage(repeatToSource ? null : source, packet, ranged ? getRangedDistance() : Double.POSITIVE_INFINITY, source.getPosition());
|
|
}
|
|
|
|
/**
|
|
* Always ranged from Point.
|
|
*
|
|
* @param packet
|
|
* @param rangedFrom
|
|
*/
|
|
public void broadcastMessage(final byte[] packet, Point rangedFrom) {
|
|
broadcastMessage(null, packet, getRangedDistance(), rangedFrom);
|
|
}
|
|
|
|
/**
|
|
* Always ranged from point. Does not repeat to source.
|
|
*
|
|
* @param source
|
|
* @param packet
|
|
* @param rangedFrom
|
|
*/
|
|
public void broadcastMessage(MapleCharacter source, final byte[] packet, Point rangedFrom) {
|
|
broadcastMessage(source, packet, getRangedDistance(), rangedFrom);
|
|
}
|
|
|
|
private void broadcastMessage(MapleCharacter source, final byte[] 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 DRAGON:
|
|
case MIST:
|
|
return true;
|
|
default:
|
|
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.isSummonsEmpty() || !chr.containsSummon(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(), getRangedDistance(), 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<>();
|
|
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<>();
|
|
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<>(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, 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 void addAllMonsterSpawn(MapleMonster monster, int mobTime, int team) {
|
|
Point newpos = calcPointBelow(monster.getPosition());
|
|
newpos.y -= 1;
|
|
SpawnPoint sp = new SpawnPoint(monster, newpos, !monster.isMobile(), mobTime, mobInterval, team);
|
|
allMonsterSpawn.add(sp);
|
|
}
|
|
|
|
public void reportMonsterSpawnPoints(MapleCharacter chr) {
|
|
chr.dropMessage(6, "Mob spawnpoints on map " + getId() + ", with available Mob SPs " + monsterSpawn.size() + ", used " + spawnedMonstersOnMap.get() + ":");
|
|
for(SpawnPoint sp: allMonsterSpawn) {
|
|
chr.dropMessage(6, " id: " + sp.getMonsterId() + " canSpawn: " + !sp.getDenySpawn() + " numSpawned: " + sp.getSpawned() + " x: " + sp.getPosition().getX() + " y: " + sp.getPosition().getY() + " time: " + sp.getMobTime() + " team: " + sp.getTeam());
|
|
}
|
|
}
|
|
|
|
public Collection<MapleCharacter> getCharacters() {
|
|
chrRLock.lock();
|
|
try {
|
|
return Collections.unmodifiableCollection(this.characters);
|
|
}
|
|
finally {
|
|
chrRLock.unlock();
|
|
}
|
|
}
|
|
|
|
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)) { // item entered view range
|
|
if (mo.getType() == MapleMapObjectType.SUMMON || mo.getPosition().distanceSq(chr.getPosition()) <= getRangedDistance()) {
|
|
chr.addVisibleMapObject(mo);
|
|
mo.sendSpawnData(chr.getClient());
|
|
}
|
|
} else if (mo.getType() != MapleMapObjectType.SUMMON && mo.getPosition().distanceSq(chr.getPosition()) > getRangedDistance()) {
|
|
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()]);
|
|
|
|
objectRLock.lock();
|
|
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();
|
|
} finally {
|
|
objectRLock.unlock();
|
|
}
|
|
|
|
for (MapleMapObject mo : getMapObjectsInRange(player.getPosition(), getRangedDistance(), rangedMapobjectTypes)) {
|
|
if (!player.isMapObjectVisible(mo)) {
|
|
mo.sendSpawnData(player.getClient());
|
|
player.addVisibleMapObject(mo);
|
|
}
|
|
}
|
|
}
|
|
|
|
public final void toggleEnvironment(final String ms) {
|
|
Map<String, Integer> env = getEnvironment();
|
|
|
|
if (env.containsKey(ms)) {
|
|
moveEnvironment(ms, env.get(ms) == 1 ? 2 : 1);
|
|
} else {
|
|
moveEnvironment(ms, 1);
|
|
}
|
|
}
|
|
|
|
public final void moveEnvironment(final String ms, final int type) {
|
|
broadcastMessage(MaplePacketCreator.environmentMove(ms, type));
|
|
|
|
objectWLock.lock();
|
|
try {
|
|
environment.put(ms, type);
|
|
} finally {
|
|
objectWLock.unlock();
|
|
}
|
|
}
|
|
|
|
public final Map<String, Integer> getEnvironment() {
|
|
objectRLock.lock();
|
|
try {
|
|
return Collections.unmodifiableMap(environment);
|
|
} finally {
|
|
objectRLock.unlock();
|
|
}
|
|
}
|
|
|
|
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 boolean isMuted() {
|
|
return isMuted;
|
|
}
|
|
|
|
public void setMuted(boolean mute) {
|
|
isMuted = mute;
|
|
}
|
|
|
|
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 setBackgroundTypes(HashMap<Integer, Integer> backTypes) {
|
|
backgroundTypes.putAll(backTypes);
|
|
}
|
|
|
|
// not really costly to keep generating imo
|
|
public void sendNightEffect(MapleCharacter mc) {
|
|
for (Entry<Integer, Integer> types : backgroundTypes.entrySet()) {
|
|
if (types.getValue() >= 3) { // 3 is a special number
|
|
mc.announce(MaplePacketCreator.changeBackgroundEffect(true, types.getKey(), 0));
|
|
}
|
|
}
|
|
}
|
|
|
|
public void broadcastNightEffect() {
|
|
chrRLock.lock();
|
|
try {
|
|
for (MapleCharacter c : characters) {
|
|
sendNightEffect(c);
|
|
}
|
|
} finally {
|
|
chrRLock.unlock();
|
|
}
|
|
}
|
|
|
|
public MapleCharacter getCharacterByName(String name) {
|
|
chrRLock.lock();
|
|
try {
|
|
for (MapleCharacter c : this.characters) {
|
|
if (c.getName().toLowerCase().equals(name.toLowerCase())) {
|
|
return c;
|
|
}
|
|
}
|
|
} finally {
|
|
chrRLock.unlock();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public void makeDisappearItemFromMap(MapleMapObject mapobj) {
|
|
if(mapobj instanceof MapleMapItem) {
|
|
makeDisappearItemFromMap((MapleMapItem) mapobj);
|
|
}
|
|
}
|
|
|
|
public void makeDisappearItemFromMap(MapleMapItem mapitem) {
|
|
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 ExpireMapItemJob implements Runnable {
|
|
|
|
private MapleMapItem mapitem;
|
|
|
|
public ExpireMapItemJob(MapleMapItem mapitem) {
|
|
this.mapitem = mapitem;
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
makeDisappearItemFromMap(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() {
|
|
reactor.lockReactor();
|
|
try {
|
|
if (reactor.getShouldCollect() == true && mapitem != null && mapitem == getMapObject(mapitem.getObjectId())) {
|
|
mapitem.itemLock.lock();
|
|
try {
|
|
TimerManager tMan = TimerManager.getInstance();
|
|
if (mapitem.isPickedUp()) {
|
|
return;
|
|
}
|
|
|
|
reactor.setShouldCollect(false);
|
|
MapleMap.this.broadcastMessage(MaplePacketCreator.removeItemFromMap(mapitem.getObjectId(), 0, 0), mapitem.getPosition());
|
|
MapleMap.this.removeMapObject(mapitem);
|
|
reactor.hitReactor(c);
|
|
|
|
if (reactor.getDelay() > 0) {
|
|
tMan.schedule(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
reactor.lockReactor();
|
|
try {
|
|
reactor.setState((byte) 0);
|
|
reactor.setShouldCollect(true);
|
|
} finally {
|
|
reactor.unlockReactor();
|
|
}
|
|
broadcastMessage(MaplePacketCreator.triggerReactor(reactor, 0));
|
|
}
|
|
}, reactor.getDelay());
|
|
}
|
|
} finally {
|
|
mapitem.itemLock.unlock();
|
|
}
|
|
}
|
|
} finally {
|
|
reactor.unlockReactor();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void instanceMapFirstSpawn(int difficulty, boolean isPq) {
|
|
for(SpawnPoint spawnPoint: allMonsterSpawn) {
|
|
if(spawnPoint.getMobTime() == -1) { //just those allowed to be spawned only once
|
|
spawnMonster(spawnPoint.getMonster());
|
|
}
|
|
}
|
|
}
|
|
|
|
public void instanceMapRespawn() {
|
|
if(!allowSummons) return;
|
|
|
|
final int numShouldSpawn = (short) ((monsterSpawn.size() - spawnedMonstersOnMap.get()));//Fking lol'd
|
|
if (numShouldSpawn > 0) {
|
|
List<SpawnPoint> randomSpawn = new ArrayList<>(monsterSpawn);
|
|
Collections.shuffle(randomSpawn);
|
|
int spawned = 0;
|
|
for (SpawnPoint spawnPoint : randomSpawn) {
|
|
if(spawnPoint.shouldSpawn()) {
|
|
spawnMonster(spawnPoint.getMonster());
|
|
spawned++;
|
|
if (spawned >= numShouldSpawn) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void restoreMapSpawnPoints() {
|
|
for (SpawnPoint spawnPoint : monsterSpawn) {
|
|
spawnPoint.setDenySpawn(false);
|
|
}
|
|
}
|
|
|
|
public void setAllowSpawnPointInBox(boolean allow, Rectangle box) {
|
|
for(SpawnPoint sp: monsterSpawn) {
|
|
if(box.contains(sp.getPosition())) {
|
|
sp.setDenySpawn(!allow);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void setAllowSpawnPointInRange(boolean allow, Point from, double rangeSq) {
|
|
for(SpawnPoint sp: monsterSpawn) {
|
|
if(from.distanceSq(sp.getPosition()) <= rangeSq) {
|
|
sp.setDenySpawn(!allow);
|
|
}
|
|
}
|
|
}
|
|
|
|
public SpawnPoint findClosestSpawnpoint(Point from) {
|
|
SpawnPoint closest = null;
|
|
double shortestDistance = Double.POSITIVE_INFINITY;
|
|
for (SpawnPoint sp : monsterSpawn) {
|
|
double distance = sp.getPosition().distanceSq(from);
|
|
if (distance < shortestDistance) {
|
|
closest = sp;
|
|
shortestDistance = distance;
|
|
}
|
|
}
|
|
return closest;
|
|
}
|
|
|
|
public void respawn() {
|
|
if(!allowSummons) return;
|
|
|
|
chrRLock.lock();
|
|
try {
|
|
if(characters.isEmpty()) {
|
|
return;
|
|
}
|
|
}
|
|
finally {
|
|
chrRLock.unlock();
|
|
}
|
|
|
|
/*
|
|
System.out.println("----------------------------------");
|
|
for (SpawnPoint spawnPoint : monsterSpawn) {
|
|
System.out.println("sp " + spawnPoint.getPosition().getX() + ", " + spawnPoint.getPosition().getY() + ": " + spawnPoint.getDenySpawn());
|
|
}
|
|
System.out.println("try " + monsterSpawn.size() + " - " + spawnedMonstersOnMap.get());
|
|
System.out.println("----------------------------------");
|
|
*/
|
|
|
|
short numShouldSpawn = (short) ((monsterSpawn.size() - spawnedMonstersOnMap.get()));//Fking lol'd
|
|
if(numShouldSpawn > 0) {
|
|
List<SpawnPoint> randomSpawn = new ArrayList<>(monsterSpawn);
|
|
Collections.shuffle(randomSpawn);
|
|
short spawned = 0;
|
|
for(SpawnPoint spawnPoint : randomSpawn) {
|
|
if(spawnPoint.shouldSpawn()) {
|
|
spawnMonster(spawnPoint.getMonster());
|
|
spawned++;
|
|
|
|
if(spawned >= numShouldSpawn) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public final int getNumPlayersInArea(final int index) {
|
|
return getNumPlayersInRect(getArea(index));
|
|
}
|
|
|
|
public final int getNumPlayersInRect(final Rectangle rect) {
|
|
int ret = 0;
|
|
|
|
chrRLock.lock();
|
|
try {
|
|
final Iterator<MapleCharacter> ltr = characters.iterator();
|
|
while (ltr.hasNext()) {
|
|
if (rect.contains(ltr.next().getPosition())) {
|
|
ret++;
|
|
}
|
|
}
|
|
} finally {
|
|
chrRLock.unlock();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
public final int getNumPlayersItemsInArea(final int index) {
|
|
return getNumPlayersItemsInRect(getArea(index));
|
|
}
|
|
|
|
public final int getNumPlayersItemsInRect(final Rectangle rect) {
|
|
int retP = getNumPlayersInRect(rect);
|
|
int retI = getMapObjectsInBox(rect, Arrays.asList(MapleMapObjectType.ITEM)).size();
|
|
|
|
return retP + retI;
|
|
}
|
|
|
|
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 !boat ? 0 : (docked ? 1 : 2);
|
|
}
|
|
|
|
public void setBoat(boolean hasBoat) {
|
|
this.boat = hasBoat;
|
|
}
|
|
|
|
public void setDocked(boolean isDocked) {
|
|
this.docked = isDocked;
|
|
}
|
|
|
|
public boolean getDocked() {
|
|
return this.docked;
|
|
}
|
|
|
|
public void broadcastGMMessage(MapleCharacter source, final byte[] packet, boolean repeatToSource) {
|
|
broadcastGMMessage(repeatToSource ? null : source, packet, Double.POSITIVE_INFINITY, source.getPosition());
|
|
}
|
|
|
|
private void broadcastGMMessage(MapleCharacter source, final byte[] 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, final byte[] 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);
|
|
this.broadcastMessage(MaplePacketCreator.removeItemFromMap(i.getObjectId(), 0, 0));
|
|
}
|
|
}
|
|
|
|
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.riceCakes = 0;
|
|
}
|
|
|
|
public void allowSummonState(boolean b) {
|
|
MapleMap.this.allowSummons = b;
|
|
}
|
|
|
|
public boolean getSummonState() {
|
|
return MapleMap.this.allowSummons;
|
|
}
|
|
|
|
public void warpEveryone(int to) {
|
|
List<MapleCharacter> players = new ArrayList<>(getCharacters());
|
|
|
|
for (MapleCharacter chr : players) {
|
|
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) {
|
|
List<MapleCharacter> chars = new ArrayList<>(getCharacters());
|
|
for (MapleCharacter chr : chars) {
|
|
if (chr != null) {
|
|
if (chr.getTeam() == team) {
|
|
chr.changeMap(mapid);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void startEvent(final MapleCharacter chr) {
|
|
if (this.mapid == 109080000 && getCoconut() == null) {
|
|
setCoconut(new MapleCoconut(this));
|
|
coconut.startEvent();
|
|
} else if (this.mapid == 109040000) {
|
|
chr.setFitness(new MapleFitness(chr));
|
|
chr.getFitness().startFitness();
|
|
} else if (this.mapid == 109030101 || this.mapid == 109030201 || this.mapid == 109030301 || this.mapid == 109030401) {
|
|
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 Pair<>(id, msg);
|
|
}
|
|
|
|
public Pair<Integer, String> getTimeMob() {
|
|
return timeMob;
|
|
}
|
|
|
|
public void toggleHiddenNPC(int id) {
|
|
objectRLock.lock();
|
|
try {
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
objectRLock.unlock();
|
|
}
|
|
}
|
|
|
|
public void setMobInterval(short interval) {
|
|
this.mobInterval = interval;
|
|
}
|
|
|
|
public short getMobInterval() {
|
|
return mobInterval;
|
|
}
|
|
|
|
public void clearMapObjects() {
|
|
clearDrops();
|
|
killAllMonsters();
|
|
resetReactors();
|
|
}
|
|
|
|
public void resetMapObjects() {
|
|
resetMapObjects(1, false);
|
|
}
|
|
|
|
public final void resetFully() {
|
|
resetMapObjects();
|
|
}
|
|
|
|
public void resetPQ() {
|
|
resetPQ(1);
|
|
}
|
|
|
|
public void resetPQ(int difficulty) {
|
|
resetMapObjects(difficulty, true);
|
|
}
|
|
|
|
public void resetMapObjects(int difficulty, boolean isPq) {
|
|
clearMapObjects();
|
|
|
|
restoreMapSpawnPoints();
|
|
instanceMapFirstSpawn(difficulty, isPq);
|
|
}
|
|
|
|
public void broadcastShip(final boolean state) {
|
|
broadcastMessage(MaplePacketCreator.boatPacket(state));
|
|
this.setDocked(state);
|
|
}
|
|
|
|
public void broadcastEnemyShip(final boolean state) {
|
|
broadcastMessage(MaplePacketCreator.crogBoatPacket(state));
|
|
this.setDocked(state);
|
|
}
|
|
|
|
public boolean isDojoMap() {
|
|
return mapid >= 925020000 && mapid < 925040000;
|
|
}
|
|
|
|
public boolean isHorntailDefeated() { // all parts of dead horntail can be found here?
|
|
for(int i = 8810010; i <= 8810017; i++) {
|
|
if(getMonsterById(i) == null) return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public void spawnHorntailOnGroundBelow(final Point targetPoint) { // ayy lmao
|
|
spawnMonsterOnGroundBelow(MapleLifeFactory.getMonster(8810026), targetPoint);
|
|
TimerManager.getInstance().schedule(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
for (int x = 8810002; x <= 8810009; x++) {
|
|
spawnMonsterOnGroundBelow(MapleLifeFactory.getMonster(x), targetPoint);
|
|
}
|
|
|
|
spawnMonsterOnGroundBelow(MapleLifeFactory.getMonster(8810018), targetPoint);
|
|
|
|
final MapleMonster ht = getMonsterById(8810018);
|
|
for(int mobId = 8810002; mobId <= 8810009; mobId++) {
|
|
getMonsterById(mobId).addListener(new MonsterListener() {
|
|
@Override
|
|
public void monsterKilled(int aniTime) {}
|
|
|
|
@Override
|
|
public void monsterDamaged(MapleCharacter from, int trueDmg) {
|
|
ht.damage(from, trueDmg);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
}, 5 * 1000);
|
|
}
|
|
}
|