Quantcast
Channel: RaGEZONE - MMO Development Forums
Viewing all 24059 articles
Browse latest View live

Buy Room in Navigator

$
0
0
I want that the player's need to purchase room's but i dont know how to implement it into the Emulator im Currently using Phoenix Emulator (So that players cant create infinity room's) :):

Help

$
0
0
Good, I would like to know why I get this problem, I do not know if I need to update a driver or I do not know, I hope they help me with this error.

https://ibb.co/RNf5cpB

char giving negative exp

$
0
0
char giving negative exp, Can somebody help me?

Help me plz

[HELP]tyranny war

$
0
0
Can someone help regarding the game play inside of TW?

In my client in order to win the TW you have to capture the 3 TOWERS.

How can I change to 2 towers only to be able to win?

need some files..

$
0
0
hello :)
im looking for server+client+db files for ep1
if some 1 has a full working pack please help me !
i have some but cant get them to work. most of the servers work but clients not.. and so on.
thank you

[S6E3][x25] agrOpol [start 09/08/20]

$
0
0


OFFICIAL LAST SERVER INFO UPDATE: 02/08/2020

PLEASE NOTE THAT THE INITIAL CONFIGURATION CAN CHANGE.

Every day small and larger modifications are introduced - please do not be discouraged by mistakes.
At the moment in the shop in Nora you can find BOK's for easier foreplay till official start. After the tests all extras will be removed.
GOLD MOBS ON ARENA ll BE REMOVED AS WELL > TEST PURPOSE ONLY.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



Discord: discord.gg/mC5Qzu7
Web: agropol.club
Forum: agropol.xyz
VIP: NO
X-shop: NO
WEBSHOP: NO



Exp/ Drop: 25/40
*WEEKEND BONUS +5%*
ML EXP: 1
Minimum ML Monster LVL:75
Max Player: 1000
Max IP: 2
Monster HP: 100%
Monster Health Bar: Yes
Player Health Bar: NO.
COMBO SKILL FOR ALL CLASS: NO.
CS SKILLS ALL MAPS: NO.
All character: From 1st LVL
Ancient+Exc: NO.
Socket+Exc: NO. EXC OPTION ONLY
Max Stat: 32K
LVL Up Points: 5/6/7
Master LVL Max: 400
Marry: Yes
PARTY
Party Reconnect: Yes.
Regular Party Bonus:
2 Players: 1%
3 Players: 2%
4 Players: 3%
5 Players: 5%
Special Party Bonus: 10%
Party PK - NO
GUILD
DL MAX PLAYERS: 25
OTHER MAX PLAYERS: 20
Alliance Minimum User: 1
Maximum Alliance Count: 2
Gulid Warehouse: YES.
HELPER
Active Level: 40
Price: Free
Elf Buffer Max: 220
Chaos Machine/ Jewel
+10: 55%
+11: 50%
+12: 45%
+13: 40%
+14/+15: RESET REWARD ONLY
BC/ DS MIX = 80%Chaos item: 70%Bless 100%
Soul: 55%
JOL: 65%
Luck: 25%



RESET/POINTS
Reward: 1x GOLD COIN
Reset Price: Reset x 10kk
Reset Freeze Time: +/- 24hrs (need attention)
Reset LVL: 400
LVL after reset: 1
Clear Stats: YES
Clear Magic/Spells: OFF
Clear Inventory: OFF
Clear Class: OFF
Points for reset:
SM, ELF, BK, SUM- 600 points x reset.
MG, DL, RF- 750 points x reset.
RESET REWARD(1 reset= 1 GOLD COIN):
6 GOLD COINS:
Jewel of Option- Add +28AD to 1 item.
Jewel of Skill- Add Skill to 1 item.
Jewel of Luck- Add Luck to 1 item.
Jewel of Excelent- Add 1x RANDOM excelent option to 1item.
16 COINS:
Jewel of Level- Increase item level from +13 to +15.





"/move" <Map> - Move to a map
"/post" <Msg> - Post a message in global chat
"/s" <points> - Add points in Str
"/a" <points> - Add points in Agi
"/v" <points> - Add points in Vit
"/e" <points> - Add points in Ene
"/c" <points> - Add points in Com
"/pk" - Clear PK
"/war" <guild> - Challenge another guild
"/soccer" <guild> - Challenge another guild to battle soccer
"/re" on/off - Resquest system
"/marry" <player> - Marry to a player
"/quest" - Quest Command
"/resp" <msg> - Quiz event answer
"/pick" <type> - Auto get itens
"/lock" <pass> - Block Items
"/unlock"<pass> - Unlock tems
"/offstore" - Open Custom Store Offline
"/attack"<skill> <buff> - Start Custom Attack
"/offattack" - Start Custom Attack Offline
"/pack" <Type> <Qtd> - Command for pack jewels
"/unpack" <Type> <Qtd> - Command for unpack jewels



Events(LONDON TIME) (NEED ATTENTION/ TIME MOD )
CS: Sunday 19:00
Chaos Castle 01:00, 11:00, 17:00, 22:00
CW: DoW: 4 & 7 / 20:30
Item Drop Event (Lorencia Bar) 11:00, 16:00, 21:00
Moss Merchant: NO
Bonus exp: WEEKEND +5%
Castle Deep: 16:00
Devil Square: 01:00, 03:00, 05:00, 07:00, 09:00, 11:00, 13:00, 15:00, 17:00, 19:00, 21:00, 23:00
Blood Castle: 00:00, 02:00, 04:00, 06:00, 08:00, 10:00, 12:00, 14:00, 16:00, 18:00, 20:00, 22:00
Illusion Temple: 10:30, 21:30
Skeleton King: 00:00, 08:00, 20:00
Red Dragon: 01:00, 09:00, 17:00, 22:00
Gold Invasion: 02:00, 06:00, 10:00, 14:00, 18:00, 22:00
White Wizard: 03:00, 07:00, 11:00, 15:00, 19:00, 23:00
EVENT REWARDS EXAMPLE:Box of Heaven - chaos, creation, soul, bless, life, imp, angel
Heart/ Gold/ Silver Medal - up to Dragon
Box of Kundun + 1 - +5 - Standard up to GD
Medusa - 2x item 380 exc
Selupan - 3x K5 item
Kundun - Weapon EXC 380lvl + ACC (3 items in total)
Nightmare - Random EXC 380lvl
Maya Hands - Random EXC Tier K4
Erohim - Random EXC+ ACC (2 items in total)
Dark Elf - Random EXC 380/400lvl
BC: 1-5 Random Jewel x1
BC: 6 Random Jewel x2
BC: 7- Random Jewel x10
BC8: Random Jewel x10 / TOCA (LOW RATE)
CC: 1-6 random jewel x3
CC7:- 25% ACC RATE
New Year Event, RABITS, SUMMER, SANTA, MEDUSA, BALROG SELUP, KONDUN, ERO - NEEDS TIME FIXFULL EVENT INFO:
https://agropol.xyz/index.php?/forum/8-event-faq/



SPOTS:
7x Monster Per spot.
Random monster resp on map excluding Arena.
More than 400 Spots.
Over 7000 monsters ON.
New Locations:
Arkania
Debenter
Urk Montain
Ferea
Nars
Nixie Lake
Kubera Mine
Advenced Stat INFO3D CameraHD DETAILSEXTRA VISUAL MODSOFFLVL: 4H/ 24h
Max item level+15+28
SOCKET AND SEED REMOVED
Custom Shop
3x Warehouse;
/ware 0, /ware 1, /ware 2
And Many more..../////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////























I'm not the owner of the server. I just posted the presentation here, because i think this server is looking rly good and i didn't see a presentation on this forum so many people would miss this out.

Error Fix

$
0
0
How to Fix this Error

'03:51:43' questdailytable.ext Invalid QuestID[1364][ItemID:237]

OldSchool Server, Old School is Back! (OFF Platform/ Interlude)

$
0
0
Are you looking for a server with the old settings? Then AllControl Interlude is your server. :thumbup1: :thumbup:

We are the only serious project at the moment with these configurations, we have an OFF platform with our own code. Besides an excellent physical Firewall and a Server with excellent features, configured to support connections from anywhere in the world.

What do we offer?

Exp and Sp x3
Adena and Drop x3
Spoil x3
Quest Reward x1
Quest Drop x1
Safe Enchant +3
Max Enchant +12
Raid Boss drop x1
Raid Boss Epic x1
Full Craft Old School

---------------------------------------------------

Maximum accounts per PC 2
Learning skills through Books and Sp
Auto Pick Up
Change of classes by Quest
Subclass and Noble by Quest
Duration of retail buf 20 min
Slots de buf Retail (20+4)
Olympics every 15 days
Enabling Sieges in a Progressive Way

---------------------------------------------------

Voting system
Weekly and Monthly Events
AntiBot and AntiDDos System
Gm Police 24/7

We want to be the best Gamer community, soon we will have more games and more surprises, help us to share to reach more people!

AllControlGames The World of Video Games is Our

Home Page
AllControlGames – El Mundo de los VideoJuegos es Nuestro

Page Lineage 2
https://allcontrolgames.com/lineage2

Our Forum
https://allcontrolgames.com/community/foros/

Discord Channel
https://discord.gg/fSVxWEQ




Follow us on Facebook and Instagram
https://www.facebook.com/AllControlGames
https://www.instagram.com/allcontrolgames/

[RELEASE] Laghaim KR full source stable 2016

Missing Details please help

$
0
0
Hi i have a item and cant find the Location on World Map.
Can someone giveme the info?

Laghaim Game Section

$
0
0
Hello

I am here to ask a Laghaim Section.

Its a good game and i have alot of content to share.

MuWarning.net | S6 | 250x | 40% | PvP Balanced | NO ITEMS F.O | Play2Win !

$
0
0
MuWarning.net

(Donwload)
(Register)

=============================================

MuWarningS6


=============================================


INFORMATION
Opening: 14.08.2019
Version: Season 6 Episode 3
Exp Base: 250x
Master Exp: 20x
Drop: 40%
Reset: On
Reset: Acumulative
Level: 400 Level Basic and 400 Level Master
Spots: 5 mobs (Hot 6 Mobs)
Elf Buff: 200 (Max Level)
Items Custom: Off
Host: OVH (Canadá)
- HOT
Game Guard: PS GAME GUARD PREMIUM - HOT
All Class Create LV 1
PC Limit ip: Unlimited
Offattack: On


EXTRA
➠ Helper Season 6
➠ Resolutons 640x480~1920x1080
3D Cam + Zoom + Rotation [F10]
➠ Trilinear Textures + Tron Glow + Fogs
➠ Reduces CPU/GPU to play more fluid
➠ Disconnect Friends (Login)
➠ AutoParty (/re auto)
➠ AutoAdd (/add Status)
➠ PvP Rings Lorencia + FreePK
➠ Item Protect + AntiDelete (/pin)
➠ Mapa + Spots + NPC + Gates [TAB]
➠ HP/SD Bar + Info (PvP, Party, Monstruos)
➠ Minimizer + Tray Icon + Reduce CPU/GPU/RAM [F12]
➠ Reconnect + AutoHelper + AntiDisconnect
➠ Launcher + AutoUpdate
➠ Server Hour/Local Hour (Interface)
➠ Party bonus
Extended Master Skill Tree
Balanced Characters Pvp

Please Help me with this Error

[HELP] Need ALL PW Tools :(

$
0
0
Someone have New Link and Tool For PW V1.5.5+++
I Want Make Private PW, but never found best Tool For
  • Element.data
  • Gshop.data
  • task.data
  • npsgen.data
  • Change Model Weapon/NPC
  • Model Viewer
  • Pck ekstrator



If u Guys Have Tools Free ... can u give me :(
I Found but All Tool always have Dead Link ... :dancemental:

[Request] Eternal Sword M Mobile MMORPG

$
0
0
Hi,

Can I request for this game? This game is great, i want to setup server this game.

Thanks RAGEZONE.

Gpack 4.4 how???

$
0
0
i have version 4.2 and i got a version 4.4 i just don't know how to replace it

so for me travian_Travian_4.2_BigBang
you have this version

i want to change it to version 4.4
how can i replace it without error? :grr:

Java Need Help adding Mob Spawner.

$
0
0
/duplicate thread./

Delete please.

Java Need Help adding Mob Spawner. v83 (HeavenMS)

$
0
0
Hello guys, it has been a while since I've coded or helped code for a PServer - somewhere around ~6 years ago.
I've been helping a friend get his new server together and I've been running into tons of issues in the

NPCConversationManager.

Recently one issue I found is there is no function for
Quote:

summonMob
I've tried implementing this myself and compiled without any errors, but it doesn't seem to be spawning to monsters at all.
Also the
Quote:

killAllMonsters
and the
Quote:

reloadDrops
functions aren't working either.

The source we are using is HeavenMS, which i think is OdinMS Based.

Here is the code for the NPC being used: (I took the meso deduction out for testing purposes)

Spoiler:
var status = -1;

function start() {
status = -1;
action(1, 0, 0);
}

function action(mode, type, selection) {
if (mode == -1) {
cm.sendOk("Are you sure? Scaredy cat :<");
cm.dispose();
} else {
if (mode == 1) {
status++;
} else {
status--;
}

if (status == 0) {
cm.sendSimple("Hello #h # ! I am the boss summoner NPC of Server! Each boss monster that i summon would cost you 3mil mesos because they have more Exp and lesser HP! Would you like to pay me some mesos to spawn some #e Special Boss Monsters #n for you?\r\n\r\n#rCurrently not working#k \r\n\r\n Please choose #b\r\n#L1#Papu clock#l\r\n#L2#Pianus#l\r\n#L3#BlackCrow#l\r\n#L4#Anego#l\r\n#L5#BodyGuard A#l\r\n#L6#Bodyguard B#l\r\n#L7#TheBoss!#l#k");
} else {
if (selection == 1) {
if (cm.getMeso() < 3000000) {
cm.sendOk("You do not have enough mesos.");
cm.dispose();
return;
} else {
cm.gainMeso(-0);
cm.summonMob(85000010, 24000000, 3000000, 110, 1500000, 1, 0, 1, 827, 238);
cm.dispose();
}
} else if (selection == 2) {
if (cm.getMeso() < 3000000) {
cm.sendOk("You do not have enough mesos.");
cm.dispose();
return;
} else {
cm.gainMeso(-0);
cm.summonMob(8510000, 24000000, 3000000, 110, 1500000, 1, 0, 1, 827, 238);
cm.dispose();
}
} else if (selection == 3) {
if (cm.getMeso() < 3000000) {
cm.sendOk("You do not have enough mesos.");
cm.dispose();
return;
} else {
cm.gainMeso(-0);
cm.summonMob(9400014, 30000000, 10000000, 115, 2000000, 1, 0, 1, 430, 238);
cm.dispose();
}
} else if (selection == 4) {
if (cm.getMeso() < 3000000) {
cm.sendOk("You do not have enough mesos.");
cm.dispose();
return;
} else {
cm.gainMeso(-0);
cm.summonMob(9400121, 70000000, 10000, 130, 4500000, 1, 0, 1, 430, 238);
cm.dispose();
}
} else if (selection == 5) {
if (cm.getMeso() < 3000000) {
cm.sendOk("You do not have enough mesos.");
cm.dispose();
return;
} else {
cm.gainMeso(-0);
cm.summonMob(9400112, 350000000, 5000, 152, 15000000, 1, 0, 1, 430, 238);
cm.dispose();
}
} else if (selection == 6) {
if (cm.getMeso() < 3000000) {
cm.sendOk("You do not have enough mesos.");
cm.dispose();
return;
} else {
cm.gainMeso(-0);
cm.summonMob(9400113, 450000000, 50000, 160, 15000000, 1, 0, 1, 430, 238);
cm.dispose();
}
} else if (selection == 7) {
if (cm.getMeso() < 3000000) {
cm.sendOk("You do not have enough mesos.");
cm.dispose();
return;
} else {
cm.gainMeso(-0);
cm.summonMob(9400300, 123000000, 10000, 175, 20000000, 1, 0, 1, 430, 238);
cm.dispose();
}
} else {
cm.dispose();
}
}
}
}


Here is my NpcConversationManager.java
Spoiler:
/*
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 scripting.npc;

import java.io.File;
import java.sql.SQLException;

import config.YamlConfig;
import net.server.Server;
import net.server.guild.MapleAlliance;
import net.server.guild.MapleGuild;
import net.server.world.MapleParty;
import net.server.world.MaplePartyCharacter;
import provider.MapleData;
import provider.MapleDataProviderFactory;
import scripting.AbstractPlayerInteraction;
import server.MapleItemInformationProvider;
import server.MapleStatEffect;
import server.MapleShop;
import server.MapleShopFactory;
import server.events.gm.MapleEvent;
import server.gachapon.MapleGachapon;
import server.gachapon.MapleGachapon.MapleGachaponItem;
import server.life.MaplePlayerNPC;
import server.maps.MapleMap;
import server.maps.MapleMapManager;
import server.partyquest.Pyramid;
import server.partyquest.Pyramid.PyramidMode;
import tools.LogHelper;
import tools.MaplePacketCreator;
import client.MapleCharacter;
import client.MapleClient;
import client.MapleJob;
import client.MapleSkinColor;
import client.MapleStat;
import client.Skill;
import client.SkillFactory;
import client.inventory.Item;
import client.inventory.ItemFactory;
import client.inventory.MaplePet;
import constants.game.GameConstants;
import constants.inventory.ItemConstants;
import constants.string.LanguageConstants;
import net.server.channel.Channel;
import net.server.coordinator.matchchecker.MatchCheckerListenerFactory.MatchCheckerType;
import server.MapleMarriage;
import server.MapleSkillbookInformationProvider;
import server.MapleSkillbookInformationProvider.SkillBookEntry;
import server.TimerManager;
import server.life.MapleLifeFactory;
import server.maps.MapleMapObject;
import server.maps.MapleMapObjectType;
import server.expeditions.MapleExpedition;
import server.expeditions.MapleExpeditionType;
import server.partyquest.AriantColiseum;
import server.partyquest.MonsterCarnival;
import tools.packets.Wedding;

import java.awt.Point;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import server.life.MapleMonster;
import server.life.MapleMonsterStats;
import tools.FilePrinter;

/**
*
* @author Matze
*/
public class NPCConversationManager extends AbstractPlayerInteraction {

private int npc;
private int npcOid;
private String scriptName;
private String getText;
private boolean itemScript;
private List<MaplePartyCharacter> otherParty;

private Map<Integer, String> npcDefaultTalks = new HashMap<>();

private String getDefaultTalk(int npcid) {
String talk = npcDefaultTalks.get(npcid);
if (talk == null) {
talk = MapleLifeFactory.getNPCDefaultTalk(npcid);
npcDefaultTalks.put(npcid, talk);
}

return talk;
}

public NPCConversationManager(MapleClient c, int npc, String scriptName) {
this(c, npc, -1, scriptName, false);
}

public NPCConversationManager(MapleClient c, int npc, List<MaplePartyCharacter> otherParty, boolean test) {
super(c);
this.c = c;
this.npc = npc;
this.otherParty = otherParty;
}

public NPCConversationManager(MapleClient c, int npc, int oid, String scriptName, boolean itemScript) {
super(c);
this.npc = npc;
this.npcOid = oid;
this.scriptName = scriptName;
this.itemScript = itemScript;
}

public int getNpc() {
return npc;
}

public int getNpcObjectId() {
return npcOid;
}

public String getScriptName() {
return scriptName;
}

public boolean isItemScript() {
return itemScript;
}

public void resetItemScript() {
this.itemScript = false;
}
public void summonMob(int mobid, int customHP, int customEXP, int amount) {
MapleMonsterStats newStats = new MapleMonsterStats();
if (customHP > 0) {
newStats.setHp(customHP);
}
if (customEXP >= 0) {
newStats.setExp(customEXP);
}
if (amount <= 1) {
MapleMonster npcmob = MapleLifeFactory.getMonster(mobid);
npcmob.setOverrideStats(newStats);
npcmob.setHp(npcmob.getMaxHp());
getPlayer().getMap().spawnMonsterOnGroudBelow(npcmob, getPlayer().getPosition());
} else {
for (int i = 0; i < amount; i++) {
MapleMonster npcmob = MapleLifeFactory.getMonster(mobid);
npcmob.setOverrideStats(newStats);
npcmob.setHp(npcmob.getMaxHp());
getPlayer().getMap().spawnMonsterOnGroudBelow(npcmob, getPlayer().getPosition());
}
}
}
public void dispose() {
NPCScriptManager.getInstance().dispose(this);
getClient().announce(MaplePacketCreator.enableActions());
}

public void sendNext(String text) {
getClient().announce(MaplePacketCreator.getNPCTalk(npc, (byte) 0, text, "00 01", (byte) 0));
}

public void sendPrev(String text) {
getClient().announce(MaplePacketCreator.getNPCTalk(npc, (byte) 0, text, "01 00", (byte) 0));
}

public void sendNextPrev(String text) {
getClient().announce(MaplePacketCreator.getNPCTalk(npc, (byte) 0, text, "01 01", (byte) 0));
}

public void sendOk(String text) {
getClient().announce(MaplePacketCreator.getNPCTalk(npc, (byte) 0, text, "00 00", (byte) 0));
}

public void sendDefault() {
sendOk(getDefaultTalk(npc));
}

public void sendYesNo(String text) {
getClient().announce(MaplePacketCreator.getNPCTalk(npc, (byte) 1, text, "", (byte) 0));
}

public void sendAcceptDecline(String text) {
getClient().announce(MaplePacketCreator.getNPCTalk(npc, (byte) 0x0C, text, "", (byte) 0));
}

public void sendSimple(String text) {
getClient().announce(MaplePacketCreator.getNPCTalk(npc, (byte) 4, text, "", (byte) 0));
}

public void sendNext(String text, byte speaker) {
getClient().announce(MaplePacketCreator.getNPCTalk(npc, (byte) 0, text, "00 01", speaker));
}

public void sendPrev(String text, byte speaker) {
getClient().announce(MaplePacketCreator.getNPCTalk(npc, (byte) 0, text, "01 00", speaker));
}

public void sendNextPrev(String text, byte speaker) {
getClient().announce(MaplePacketCreator.getNPCTalk(npc, (byte) 0, text, "01 01", speaker));
}

public void sendOk(String text, byte speaker) {
getClient().announce(MaplePacketCreator.getNPCTalk(npc, (byte) 0, text, "00 00", speaker));
}

public void sendYesNo(String text, byte speaker) {
getClient().announce(MaplePacketCreator.getNPCTalk(npc, (byte) 1, text, "", speaker));
}

public void sendAcceptDecline(String text, byte speaker) {
getClient().announce(MaplePacketCreator.getNPCTalk(npc, (byte) 0x0C, text, "", speaker));
}

public void sendSimple(String text, byte speaker) {
getClient().announce(MaplePacketCreator.getNPCTalk(npc, (byte) 4, text, "", speaker));
}

public void sendStyle(String text, int styles[]) {
if (styles.length > 0) {
getClient().announce(MaplePacketCreator.getNPCTalkStyle(npc, text, styles));
} else { // thanks Conrad for noticing empty styles crashing players
sendOk("Sorry, there are no options of cosmetics available for you here at the moment.");
dispose();
}
}

public void sendGetNumber(String text, int def, int min, int max) {
getClient().announce(MaplePacketCreator.getNPCTalkNum(npc, text, def, min, max));
}

public void sendGetText(String text) {
getClient().announce(MaplePacketCreator.getNPCTalkText(npc, text, ""));
}

/*
* 0 = ariant colliseum
* 1 = Dojo
* 2 = Carnival 1
* 3 = Carnival 2
* 4 = Ghost Ship PQ?
* 5 = Pyramid PQ
* 6 = Kerning Subway
*/
public void sendDimensionalMirror(String text) {
getClient().announce(MaplePacketCreator.getDimensionalMirror(text));
}

public void setGetText(String text) {
this.getText = text;
}

public String getText() {
return this.getText;
}

@Override
public boolean forceStartQuest(int id) {
return forceStartQuest(id, npc);
}

@Override
public boolean forceCompleteQuest(int id) {
return forceCompleteQuest(id, npc);
}

@Override
public boolean startQuest(short id) {
return startQuest((int) id);
}

@Override
public boolean completeQuest(short id) {
return completeQuest((int) id);
}

@Override
public boolean startQuest(int id) {
return startQuest(id, npc);
}

@Override
public boolean completeQuest(int id) {
return completeQuest(id, npc);
}

public int getMeso() {
return getPlayer().getMeso();
}

public void gainMeso(int gain) {
getPlayer().gainMeso(gain);
}

public void gainExp(int gain) {
getPlayer().gainExp(gain, true, true);
}

@Override
public void showEffect(String effect) {
getPlayer().getMap().broadcastMessage(MaplePacketCreator.environmentChange(effect, 3));
}

public void setHair(int hair) {
getPlayer().setHair(hair);
getPlayer().updateSingleStat(MapleStat.HAIR, hair);
getPlayer().equipChanged();
}

public void setFace(int face) {
getPlayer().setFace(face);
getPlayer().updateSingleStat(MapleStat.FACE, face);
getPlayer().equipChanged();
}

public void setSkin(int color) {
getPlayer().setSkinColor(MapleSkinColor.getById(color));
getPlayer().updateSingleStat(MapleStat.SKIN, color);
getPlayer().equipChanged();
}

public int itemQuantity(int itemid) {
return getPlayer().getInventory(ItemConstants.getInventoryType(itemid)).countById(itemid);
}

public void displayGuildRanks() {
MapleGuild.displayGuildRanks(getClient(), npc);
}

public boolean canSpawnPlayerNpc(int mapid) {
MapleCharacter chr = getPlayer();
return !YamlConfig.config.server.PLAYERNPC_AUTODEPLOY && chr.getLevel() >= chr.getMaxClassLevel() && !chr.isGM() && MaplePlayerNPC.canSpawnPlayerNpc(chr.getName(), mapid);
}

public MaplePlayerNPC getPlayerNPCByScriptid(int scriptId) {
for(MapleMapObject pnpcObj : getPlayer().getMap().getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.PLAYER_NPC))) {
MaplePlayerNPC pn = (MaplePlayerNPC) pnpcObj;

if(pn.getScriptId() == scriptId) {
return pn;
}
}

return null;
}

@Override
public MapleParty getParty() {
return getPlayer().getParty();
}

@Override
public void resetMap(int mapid) {
getClient().getChannelServer().getMapFactory().getMap(mapid).resetReactors();
}

public void gainCloseness(int closeness) {
for (MaplePet pet : getPlayer().getPets()) {
if(pet != null) {
pet.gainClosenessFullness(getPlayer(), closeness, 0, 0);
}
}
}

public String getName() {
return getPlayer().getName();
}

public int getGender() {
return getPlayer().getGender();
}

public void changeJobById(int a) {
getPlayer().changeJob(MapleJob.getById(a));
}

public void changeJob(MapleJob job) {
getPlayer().changeJob(job);
}

public String getJobName(int id) {
return GameConstants.getJobName(id);
}

public MapleStatEffect getItemEffect(int itemId) {
return MapleItemInformationProvider.getInstance().getItemEffect(itemId);
}

public void resetStats() {
getPlayer().resetStats();
}

public void openShopNPC(int id) {
MapleShop shop = MapleShopFactory.getInstance().getShop(id);

if (shop != null) {
shop.sendShop(c);
} else { // check for missing shopids thanks to resinate
FilePrinter.printError(FilePrinter.NPC_UNCODED, "Shop ID: " + id + " is missing from database.");
MapleShopFactory.getInstance().getShop(11000).sendShop(c);
}
}

public void maxMastery() {
for (MapleData skill_ : MapleDataProviderFactory.getDataProvider(new File(System.getProperty("wzpath") + "/" + "String.wz")).getData("Skill.img").getChildren()) {
try {
Skill skill = SkillFactory.getSkill(Integer.parseInt(skill_.getName()));
getPlayer().changeSkillLevel(skill, (byte) 0, skill.getMaxLevel(), -1);
} catch (NumberFormatException nfe) {
nfe.printStackTrace();
break;
} catch (NullPointerException npe) {
npe.printStackTrace();
continue;
}
}
}

public void doGachapon() {
int[] maps = {100000000, 101000000, 102000000, 103000000, 105040300, 800000000, 809000101, 809000201, 600000000, 120000000};

MapleGachaponItem item = MapleGachapon.getInstance().process(npc);

Item itemGained = gainItem(item.getId(), (short) (item.getId() / 10000 == 200 ? 100 : 1), true, true); // For normal potions, make it give 100.

sendNext("You have obtained a #b#t" + item.getId() + "##k.");

String map = c.getChannelServer().getMapFactory().getMap(maps[(getNpc() != 9100117 && getNpc() != 9100109) ? (getNpc() - 9100100) : getNpc() == 9100109 ? 8 : 9]).getMapName();

LogHelper.logGacha(getPlayer(), item.getId(), map);

if (item.getTier() > 0){ //Uncommon and Rare
Server.getInstance().broadcastMessage(c.getWorld(), MaplePacketCreator.gachaponMessage(itemGained, map, getPlayer()));
}
}

public void upgradeAlliance() {
MapleAlliance alliance = Server.getInstance().getAlliance(c.getPlayer().getGuild().getAllianceId());
alliance.increaseCapacity(1);

Server.getInstance().allianceMessage(alliance.getId(), MaplePacketCreator.getGuildAlliances(alliance, c.getWorld()), -1, -1);
Server.getInstance().allianceMessage(alliance.getId(), MaplePacketCreator.allianceNotice(alliance.getId(), alliance.getNotice()), -1, -1);

c.announce(MaplePacketCreator.updateAllianceInfo(alliance, c.getWorld())); // thanks Vcoc for finding an alliance update to leader issue
}

public void disbandAlliance(MapleClient c, int allianceId) {
MapleAlliance.disbandAlliance(allianceId);
}

public boolean canBeUsedAllianceName(String name) {
return MapleAlliance.canBeUsedAllianceName(name);
}

public MapleAlliance createAlliance(String name) {
return MapleAlliance.createAlliance(getParty(), name);
}

public int getAllianceCapacity() {
return Server.getInstance().getAlliance(getPlayer().getGuild().getAllianceId()).getCapacity();
}

public boolean hasMerchant() {
return getPlayer().hasMerchant();
}

public boolean hasMerchantItems() {
try {
if (!ItemFactory.MERCHANT.loadItems(getPlayer().getId(), false).isEmpty()) {
return true;
}
} catch (SQLException e) {
e.printStackTrace();
return false;
}
if (getPlayer().getMerchantMeso() == 0) {
return false;
} else {
return true;
}
}

public void showFredrick() {
c.announce(MaplePacketCreator.getFredrick(getPlayer()));
}

public int partyMembersInMap() {
int inMap = 0;
for (MapleCharacter char2 : getPlayer().getMap().getCharacters()) {
if (char2.getParty() == getPlayer().getParty()) {
inMap++;
}
}
return inMap;
}

public MapleEvent getEvent() {
return c.getChannelServer().getEvent();
}

public void divideTeams() {
if (getEvent() != null) {
getPlayer().setTeam(getEvent().getLimit() % 2); //muhaha :D
}
}

public MapleCharacter getMapleCharacter(String player) {
MapleCharacter target = Server.getInstance().getWorld(c.getWorld()).getChannel(c.getChannel()).getPlayerStorage().getCharacterByName(player);
return target;
}

public void logLeaf(String prize) {
LogHelper.logLeaf(getPlayer(), true, prize);
}

public boolean createPyramid(String mode, boolean party) {//lol
PyramidMode mod = PyramidMode.valueOf(mode);

MapleParty partyz = getPlayer().getParty();
MapleMapManager mapManager = c.getChannelServer().getMapFactory();

MapleMap map = null;
int mapid = 926010100;
if (party) {
mapid += 10000;
}
mapid += (mod.getMode() * 1000);

for (byte b = 0; b < 5; b++) {//They cannot warp to the next map before the timer ends (:
map = mapManager.getMap(mapid + b);
if (map.getCharacters().size() > 0) {
continue;
} else {
break;
}
}

if (map == null) {
return false;
}

if (!party) {
partyz = new MapleParty(-1, new MaplePartyCharacter(getPlayer()));
}
Pyramid py = new Pyramid(partyz, mod, map.getId());
getPlayer().setPartyQuest(py);
py.warp(mapid);
dispose();
return true;
}

public boolean itemExists(int itemid) {
return MapleItemInformationProvider.getInstance().getName(itemid) != null;
}

public int getCosmeticItem(int itemid) {
if (itemExists(itemid)) {
return itemid;
}

int baseid;
if (itemid < 30000) {
baseid = (itemid / 1000) * 1000 + (itemid % 100);
} else {
baseid = (itemid / 10) * 10;
}

return itemid != baseid && itemExists(baseid) ? baseid : -1;
}

private int getEquippedCosmeticid(int itemid) {
if (itemid < 30000) {
return getPlayer().getFace();
} else {
return getPlayer().getHair();
}
}

public boolean isCosmeticEquipped(int itemid) {
return getEquippedCosmeticid(itemid) == itemid;
}

public boolean isUsingOldPqNpcStyle() {
return YamlConfig.config.server.USE_OLD_GMS_STYLED_PQ_NPCS && this.getPlayer().getParty() != null;
}

public Object[] getAvailableMasteryBooks() {
return MapleItemInformationProvider.getInstance().usableMasteryBooks(this.getPlayer()).toArray();
}

public Object[] getAvailableSkillBooks() {
List<Integer> ret = MapleItemInformationProvider.getInstance().usableSkillBooks(this.getPlayer());
ret.addAll(MapleSkillbookInformationProvider.getInstance().getTeachableSkills(this.getPlayer()));

return ret.toArray();
}

public Object[] getNamesWhoDropsItem(Integer itemId) {
return MapleItemInformationProvider.getInstance().getWhoDrops(itemId).toArray();
}

public String getSkillBookInfo(int itemid) {
SkillBookEntry sbe = MapleSkillbookInformationProvider.getInstance().getSkillbookAvailability(itemid);
switch (sbe) {
case UNAVAILABLE:
return "";

case REACTOR:
return " Obtainable through #rexploring#k (loot boxes).";

case SCRIPT:
return " Obtainable through #rexploring#k (field interaction).";

case QUEST_BOOK:
return " Obtainable through #rquestline#k (collecting book).";

case QUEST_REWARD:
return " Obtainable through #rquestline#k (quest reward).";

default:
return " Obtainable through #rquestline#k.";
}
}

// (CPQ + WED wishlist) by -- Drago (Dragohe4rt)
public int cpqCalcAvgLvl(int map) {
int num = 0;
int avg = 0;
for (MapleMapObject mmo : c.getChannelServer().getMapFactory().getMap(map).getAllPlayer()) {
avg += ((MapleCharacter) mmo).getLevel();
num++;
}
avg /= num;
return avg;
}

public boolean sendCPQMapLists() {
String msg = LanguageConstants.getMessage(getPlayer(), LanguageConstants.CPQPickRoom);
int msgLen = msg.length();
for (int i = 0; i < 6; i++) {
if (fieldTaken(i)) {
if (fieldLobbied(i)) {
msg += "#b#L" + i + "#Carnival Field " + (i + 1) + " (Level: " // "Carnival field" GMS-like improvement thanks to Jayd (jaydenseah)
+ cpqCalcAvgLvl(980000100 + i * 100) + " / "
+ getPlayerCount(980000100 + i * 100) + "x"
+ getPlayerCount(980000100 + i * 100) + ") #l\r\n";
}
} else {
if (i >= 0 && i <= 3) {
msg += "#b#L" + i + "#Carnival Field " + (i + 1) + " (2x2) #l\r\n";
} else {
msg += "#b#L" + i + "#Carnival Field " + (i + 1) + " (3x3) #l\r\n";
}
}
}

if (msg.length() > msgLen) {
sendSimple(msg);
return true;
} else {
return false;
}
}

public boolean fieldTaken(int field) {
if (!c.getChannelServer().canInitMonsterCarnival(true, field)) {
return true;
}
if (!c.getChannelServer().getMapFactory().getMap(980000100 + field * 100).getAllPlayer().isEmpty()) {
return true;
}
if (!c.getChannelServer().getMapFactory().getMap(980000101 + field * 100).getAllPlayer().isEmpty()) {
return true;
}
if (!c.getChannelServer().getMapFactory().getMap(980000102 + field * 100).getAllPlayer().isEmpty()) {
return true;
}
return false;
}

public boolean fieldLobbied(int field) {
if (!c.getChannelServer().getMapFactory().getMap(980000100 + field * 100).getAllPlayer().isEmpty()) {
return true;
}
return false;
}

public void cpqLobby(int field) {
try {
final MapleMap map, mapExit;
Channel cs = c.getChannelServer();

map = cs.getMapFactory().getMap(980000100 + 100 * field);
mapExit = cs.getMapFactory().getMap(980000000);
for (MaplePartyCharacter mpc : c.getPlayer().getParty().getMembers()) {
final MapleCharacter mc = mpc.getPlayer();
if (mc != null) {
mc.setChallenged(false);
mc.changeMap(map, map.getPortal(0));
mc.announce(MaplePacketCreator.serverNotice(6, LanguageConstants.getMessage(mc, LanguageConstants.CPQEntryLobby)));
TimerManager tMan = TimerManager.getInstance();
tMan.schedule(new Runnable() {
@Override
public void run() {
mapClock(3 * 60);
}
}, 1500);

mc.setCpqTimer(TimerManager.getInstance().schedule(new Runnable() {
@Override
public void run() {
mc.changeMap(mapExit, mapExit.getPortal(0));
}
}, 3 * 60 * 1000));
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}

public MapleCharacter getChrById(int id) {
return c.getChannelServer().getPlayerStorage().getCharacterById(id);
}

public void cancelCPQLobby() {
for (MaplePartyCharacter mpc : c.getPlayer().getParty().getMembers()) {
MapleCharacter mc = mpc.getPlayer();
if (mc != null) {
mc.clearCpqTimer();
}
}
}

private void warpoutCPQLobby(MapleMap lobbyMap) {
MapleMap out = lobbyMap.getChannelServer().getMapFactory().getMap((lobbyMap.getId() < 980030000) ? 980000000 : 980030000);
for (MapleCharacter mc : lobbyMap.getAllPlayers()) {
mc.resetCP();
mc.setTeam(-1);
mc.setMonsterCarnival(null);
mc.changeMap(out, out.getPortal(0));
}
}

private int isCPQParty(MapleMap lobby, MapleParty party) {
int cpqMinLvl, cpqMaxLvl;

if (lobby.isCPQLobby()) {
cpqMinLvl = 30;
cpqMaxLvl = 50;
} else {
cpqMinLvl = 51;
cpqMaxLvl = 70;
}

List<MaplePartyCharacter> partyMembers = party.getPartyMembers();
for (MaplePartyCharacter pchr : partyMembers) {
if (pchr.getLevel() >= cpqMinLvl && pchr.getLevel() <= cpqMaxLvl) {
if (lobby.getCharacterById(pchr.getId()) == null) {
return 1; // party member detected out of area
}
} else {
return 2; // party member doesn't fit requirements
}
}

return 0;
}

private int canStartCPQ(MapleMap lobby, MapleParty party, MapleParty challenger) {
int ret = isCPQParty(lobby, party);
if (ret != 0) {
return ret;
}

ret = isCPQParty(lobby, challenger);
if (ret != 0) {
return -ret;
}

return 0;
}

public void startCPQ(final MapleCharacter challenger, final int field) {
try {
cancelCPQLobby();

final MapleMap lobbyMap = getPlayer().getMap();
if (challenger != null) {
if (challenger.getParty() == null) {
throw new RuntimeException("No opponent found!");
}

for (MaplePartyCharacter mpc : challenger.getParty().getMembers()) {
MapleCharacter mc = mpc.getPlayer();
if (mc != null) {
mc.changeMap(lobbyMap, lobbyMap.getPortal(0));
TimerManager tMan = TimerManager.getInstance();
tMan.schedule(new Runnable() {
@Override
public void run() {
mapClock(10);
}
}, 1500);
}
}
for (MaplePartyCharacter mpc : getPlayer().getParty().getMembers()) {
MapleCharacter mc = mpc.getPlayer();
if (mc != null) {
TimerManager tMan = TimerManager.getInstance();
tMan.schedule(new Runnable() {
@Override
public void run() {
mapClock(10);
}
}, 1500);
}
}
}
final int mapid = c.getPlayer().getMapId() + 1;
TimerManager tMan = TimerManager.getInstance();
tMan.schedule(new Runnable() {
@Override
public void run() {
try {
for (MaplePartyCharacter mpc : getPlayer().getParty().getMembers()) {
MapleCharacter mc = mpc.getPlayer();
if (mc != null) {
mc.setMonsterCarnival(null);
}
}
for (MaplePartyCharacter mpc : challenger.getParty().getMembers()) {
MapleCharacter mc = mpc.getPlayer();
if (mc != null) {
mc.setMonsterCarnival(null);
}
}
} catch (NullPointerException npe) {
warpoutCPQLobby(lobbyMap);
return;
}

MapleParty lobbyParty = getPlayer().getParty(), challengerParty = challenger.getParty();
int status = canStartCPQ(lobbyMap, lobbyParty, challengerParty);
if (status == 0) {
new MonsterCarnival(lobbyParty, challengerParty, mapid, true, (field / 100) % 10);
} else {
warpoutCPQLobby(lobbyMap);
}
}
}, 11000);
} catch (Exception e) {
e.printStackTrace();
}
}

public void startCPQ2(final MapleCharacter challenger, final int field) {
try {
cancelCPQLobby();

final MapleMap lobbyMap = getPlayer().getMap();
if (challenger != null) {
if (challenger.getParty() == null) {
throw new RuntimeException("No opponent found!");
}

for (MaplePartyCharacter mpc : challenger.getParty().getMembers()) {
MapleCharacter mc = mpc.getPlayer();
if (mc != null) {
mc.changeMap(lobbyMap, lobbyMap.getPortal(0));
mapClock(10);
}
}
}
final int mapid = c.getPlayer().getMapId() + 100;
TimerManager tMan = TimerManager.getInstance();
tMan.schedule(new Runnable() {
@Override
public void run() {
try {
for (MaplePartyCharacter mpc : getPlayer().getParty().getMembers()) {
MapleCharacter mc = mpc.getPlayer();
if (mc != null) {
mc.setMonsterCarnival(null);
}
}
for (MaplePartyCharacter mpc : challenger.getParty().getMembers()) {
MapleCharacter mc = mpc.getPlayer();
if (mc != null) {
mc.setMonsterCarnival(null);
}
}
} catch (NullPointerException npe) {
warpoutCPQLobby(lobbyMap);
return;
}

MapleParty lobbyParty = getPlayer().getParty(), challengerParty = challenger.getParty();
int status = canStartCPQ(lobbyMap, lobbyParty, challengerParty);
if (status == 0) {
new MonsterCarnival(lobbyParty, challengerParty, mapid, false, (field / 1000) % 10);
} else {
warpoutCPQLobby(lobbyMap);
}
}
}, 10000);
} catch (Exception e) {
e.printStackTrace();
}
}

public boolean sendCPQMapLists2() {
String msg = LanguageConstants.getMessage(getPlayer(), LanguageConstants.CPQPickRoom);
int msgLen = msg.length();
for (int i = 0; i < 3; i++) {
if (fieldTaken2(i)) {
if (fieldLobbied2(i)) {
msg += "#b#L" + i + "#Carnival Field " + (i + 1) + " (Level: " // "Carnival field" GMS-like improvement thanks to Jayd
+ cpqCalcAvgLvl(980031000 + i * 1000) + " / "
+ getPlayerCount(980031000 + i * 1000) + "x"
+ getPlayerCount(980031000 + i * 1000) + ") #l\r\n";
}
} else {
if (i == 0 || i == 1) {
msg += "#b#L" + i + "#Carnival Field " + (i + 1) + " (2x2) #l\r\n";
} else {
msg += "#b#L" + i + "#Carnival Field " + (i + 1) + " (3x3) #l\r\n";
}
}
}

if (msg.length() > msgLen) {
sendSimple(msg);
return true;
} else {
return false;
}
}

public boolean fieldTaken2(int field) {
if (!c.getChannelServer().canInitMonsterCarnival(false, field)) {
return true;
}
if (!c.getChannelServer().getMapFactory().getMap(980031000 + field * 1000).getAllPlayer().isEmpty()) {
return true;
}
if (!c.getChannelServer().getMapFactory().getMap(980031100 + field * 1000).getAllPlayer().isEmpty()) {
return true;
}
if (!c.getChannelServer().getMapFactory().getMap(980031200 + field * 1000).getAllPlayer().isEmpty()) {
return true;
}
return false;
}

public boolean fieldLobbied2(int field) {
if (!c.getChannelServer().getMapFactory().getMap(980031000 + field * 1000).getAllPlayer().isEmpty()) {
return true;
}
return false;
}

public void cpqLobby2(int field) {
try {
final MapleMap map, mapExit;
Channel cs = c.getChannelServer();

mapExit = cs.getMapFactory().getMap(980030000);
map = cs.getMapFactory().getMap(980031000 + 1000 * field);
for (MaplePartyCharacter mpc : c.getPlayer().getParty().getMembers()) {
final MapleCharacter mc = mpc.getPlayer();
if (mc != null) {
mc.setChallenged(false);
mc.changeMap(map, map.getPortal(0));
mc.announce(MaplePacketCreator.serverNotice(6, LanguageConstants.getMessage(mc, LanguageConstants.CPQEntryLobby)));
TimerManager tMan = TimerManager.getInstance();
tMan.schedule(new Runnable() {
@Override
public void run() {
mapClock(3 * 60);
}
}, 1500);

mc.setCpqTimer(TimerManager.getInstance().schedule(new Runnable() {
@Override
public void run() {
mc.changeMap(mapExit, mapExit.getPortal(0));
}
}, 3 * 60 * 1000));
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}

public void mapClock(int time) {
getPlayer().getMap().broadcastMessage(MaplePacketCreator.getClock(time));
}

private boolean sendCPQChallenge(String cpqType, int leaderid) {
Set<Integer> cpqLeaders = new HashSet<>();
cpqLeaders.add(leaderid);
cpqLeaders.add(getPlayer().getId());

return c.getWorldServer().getMatchCheckerCoordinator().createMatchConfirmation(MatchCheckerType.CPQ_CHALLENGE, c.getWorld(), getPlayer().getId(), cpqLeaders, cpqType);
}

public void answerCPQChallenge(boolean accept) {
c.getWorldServer().getMatchCheckerCoordinator().answerMatchConfirmation(getPlayer().getId(), accept);
}

public void challengeParty2(int field) {
MapleCharacter leader = null;
MapleMap map = c.getChannelServer().getMapFactory().getMap(980031000 + 1000 * field);
for (MapleMapObject mmo : map.getAllPlayer()) {
MapleCharacter mc = (MapleCharacter) mmo;
if (mc.getParty() == null) {
sendOk(LanguageConstants.getMessage(mc, LanguageConstants.CPQFindError));
return;
}
if (mc.getParty().getLeader().getId() == mc.getId()) {
leader = mc;
break;
}
}
if (leader != null) {
if (!leader.isChallenged()) {
if (!sendCPQChallenge("cpq2", leader.getId())) {
sendOk(LanguageConstants.getMessage(leader, LanguageConstants.CPQChallengeRoomAnswer));
}
} else {
sendOk(LanguageConstants.getMessage(leader, LanguageConstants.CPQChallengeRoomAnswer));
}
} else {
sendOk(LanguageConstants.getMessage(leader, LanguageConstants.CPQLeaderNotFound));
}
}

public void challengeParty(int field) {
MapleCharacter leader = null;
MapleMap map = c.getChannelServer().getMapFactory().getMap(980000100 + 100 * field);
if (map.getAllPlayer().size() != getPlayer().getParty().getMembers().size()) {
sendOk("An unexpected error regarding the other party has occurred.");
return;
}
for (MapleMapObject mmo : map.getAllPlayer()) {
MapleCharacter mc = (MapleCharacter) mmo;
if (mc.getParty() == null) {
sendOk(LanguageConstants.getMessage(mc, LanguageConstants.CPQFindError));
return;
}
if (mc.getParty().getLeader().getId() == mc.getId()) {
leader = mc;
break;
}
}
if (leader != null) {
if (!leader.isChallenged()) {
if (!sendCPQChallenge("cpq1", leader.getId())) {
sendOk(LanguageConstants.getMessage(leader, LanguageConstants.CPQChallengeRoomAnswer));
}
} else {
sendOk(LanguageConstants.getMessage(leader, LanguageConstants.CPQChallengeRoomAnswer));
}
} else {
sendOk(LanguageConstants.getMessage(leader, LanguageConstants.CPQLeaderNotFound));
}
}

private synchronized boolean setupAriantBattle(MapleExpedition exped, int mapid) {
MapleMap arenaMap = this.getMap().getChannelServer().getMapFactory().getMap(mapid + 1);
if (!arenaMap.getAllPlayers().isEmpty()) {
return false;
}

new AriantColiseum(arenaMap, exped);
return true;
}

public String startAriantBattle(MapleExpeditionType expedType, int mapid) {
if (!GameConstants.isAriantColiseumLobby(mapid)) {
return "You cannot start an Ariant tournament from outside the Battle Arena Entrance.";
}

MapleExpedition exped = this.getMap().getChannelServer().getExpedition(expedType);
if (exped == null) {
return "Please register on an expedition before attempting to start an Ariant tournament.";
}

List<MapleCharacter> players = exped.getActiveMembers();

int playersSize = players.size();
if (!(playersSize >= exped.getMinSize() && playersSize <= exped.getMaxSize())) {
return "Make sure there are between #r" + exped.getMinSize() + " ~ " + exped.getMaxSize() + " players#k in this room to start the battle.";
}

MapleMap leaderMap = this.getMap();
for (MapleCharacter mc : players) {
if (mc.getMap() != leaderMap) {
return "All competing players should be on this area to start the battle.";
}

if (mc.getParty() != null) {
return "All competing players must not be on a party to start the battle.";
}

int level = mc.getLevel();
if (!(level >= expedType.getMinLevel() && level <= expedType.getMaxLevel())) {
return "There are competing players outside of the acceptable level range in this room. All players must be on #blevel between 20~30#k to start the battle.";
}
}

if (setupAriantBattle(exped, mapid)) {
return "";
} else {
return "Other players are already competing on the Ariant tournament in this room. Please wait a while until the arena becomes available again.";
}
}

public void sendMarriageWishlist(boolean groom) {
MapleCharacter player = this.getPlayer();
MapleMarriage marriage = player.getMarriageInstance();
if(marriage != null) {
int cid = marriage.getIntProperty(groom ? "groomId" : "brideId");
MapleCharacter chr = marriage.getPlayerById(cid);
if (chr != null) {
if (chr.getId() == player.getId()) {
player.announce(Wedding.OnWeddingGiftResult((byte) 0xA, marriage.getWishlistItems(groom), marriage.getGiftItems(player.getClient(), groom)));
} else {
marriage.setIntProperty("wishlistSelection", groom ? 0 : 1);
player.announce(Wedding.OnWeddingGiftResult((byte) 0x09, marriage.getWishlistItems(groom), marriage.getGiftItems(player.getClient(), groom)));
}
}
}
}

public void sendMarriageGifts(List<Item> gifts) {
this.getPlayer().announce(Wedding.OnWeddingGiftResult((byte) 0xA, Collections.singletonList(""), gifts));
}

public boolean createMarriageWishlist() {
MapleMarriage marriage = this.getPlayer().getMarriageInstance();
if (marriage != null) {
Boolean groom = marriage.isMarriageGroom(this.getPlayer());
if (groom != null) {
String wlKey;
if (groom) {
wlKey = "groomWishlist";
} else {
wlKey = "brideWishlist";
}

if (marriage.getProperty(wlKey).contentEquals("")) {
getClient().announce(Wedding.sendWishList());
return true;
}
}
}

return false;
}
}


Here is my MapleMonster.java
Spoiler:
/*
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.life;

import client.MapleBuffStat;
import client.MapleCharacter;
import client.MapleClient;
import client.MapleFamilyEntry;
import client.MapleJob;
import client.Skill;
import client.SkillFactory;
import client.status.MonsterStatus;
import client.status.MonsterStatusEffect;
import config.YamlConfig;
import constants.skills.Crusader;
import constants.skills.FPMage;
import constants.skills.Hermit;
import constants.skills.ILMage;
import constants.skills.NightLord;
import constants.skills.NightWalker;
import constants.skills.Priest;
import constants.skills.Shadower;
import constants.skills.WhiteKnight;
import java.awt.Point;
import java.lang.ref.WeakReference;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import net.server.audit.locks.MonitoredReentrantLock;
import net.server.channel.Channel;
import net.server.world.MapleParty;
import net.server.world.MaplePartyCharacter;
import scripting.event.EventInstanceManager;
import server.TimerManager;
import server.life.MapleLifeFactory.BanishInfo;
import server.maps.MapleMap;
import server.maps.MapleMapObjectType;
import tools.IntervalBuilder;
import tools.MaplePacketCreator;
import tools.Pair;
import tools.Randomizer;
import net.server.audit.LockCollector;
import net.server.audit.locks.MonitoredLockType;
import net.server.audit.locks.factory.MonitoredReentrantLockFactory;
import net.server.services.type.ChannelServices;
import net.server.services.task.channel.MobAnimationService;
import net.server.services.task.channel.MobClearSkillService;
import net.server.services.task.channel.MobStatusService;
import net.server.services.task.channel.OverallService;
import net.server.coordinator.world.MapleMonsterAggroCoordinator;
import server.MapleStatEffect;
import server.loot.MapleLootManager;
import server.maps.MapleSummon;
import tools.DatabaseConnection;

public class MapleMonster extends AbstractLoadedMapleLife {

private ChangeableStats ostats = null; //unused, v83 WZs offers no support for changeable stats.
private MapleMonsterStats stats;
private MapleMonsterStats overrideStats;
private AtomicInteger hp = new AtomicInteger(1);
private AtomicLong maxHpPlusHeal = new AtomicLong(1);
private int mp;
private WeakReference<MapleCharacter> controller = new WeakReference<>(null);
private boolean controllerHasAggro, controllerKnowsAboutAggro, controllerHasPuppet;
private Collection<MonsterListener> listeners = new LinkedList<>();
private EnumMap<MonsterStatus, MonsterStatusEffect> stati = new EnumMap<>(MonsterStatus.class);
private ArrayList<MonsterStatus> alreadyBuffed = new ArrayList<>();
private MapleMap map;
private int VenomMultiplier = 0;
private boolean fake = false;
private boolean dropsDisabled = false;
private List<Pair<Integer, Integer>> usedSkills = new ArrayList<>();
private Map<Pair<Integer, Integer>, Integer> skillsUsed = new HashMap<>();
private Set<Integer> usedAttacks = new HashSet<>();
private Set<Integer> calledMobOids = null;
private WeakReference<MapleMonster> callerMob = new WeakReference<>(null);
private List<Integer> stolenItems = new ArrayList<>(5);
private int team;
private int parentMobOid = 0;
private int spawnEffect = 0;
private final HashMap<Integer, AtomicLong> takenDamage = new HashMap<>();
private ScheduledFuture<?> monsterItemDrop = null;
private Runnable removeAfterAction = null;
private boolean availablePuppetUpdate = true;

private MonitoredReentrantLock externalLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.MOB_EXT);
private MonitoredReentrantLock monsterLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.MOB, true);
private MonitoredReentrantLock statiLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.MOB_STATI);
private MonitoredReentrantLock animationLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.MOB_ANI);
private MonitoredReentrantLock aggroUpdateLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.MOB_AGGRO);

public MapleMonster(int id, MapleMonsterStats stats) {
super(id);
initWithStats(stats);
}

public MapleMonster(MapleMonster monster) {
super(monster);
initWithStats(monster.stats);
}

public void lockMonster() {
externalLock.lock();
}

public void unlockMonster() {
externalLock.unlock();
}

private void initWithStats(MapleMonsterStats baseStats) {
setStance(5);
this.stats = baseStats.copy();
hp.set(stats.getHp());
mp = stats.getMp();

maxHpPlusHeal.set(hp.get());
}

public void setSpawnEffect(int effect) {
spawnEffect = effect;
}

public int getSpawnEffect() {
return spawnEffect;
}

public void disableDrops() {
this.dropsDisabled = true;
}

public void enableDrops() {
this.dropsDisabled = false;
}

public boolean dropsDisabled() {
return dropsDisabled;
}

public void setMap(MapleMap map) {
this.map = map;
}

public int getParentMobOid() {
return parentMobOid;
}

public void setParentMobOid(int parentMobId) {
this.parentMobOid = parentMobId;
}

public int countAvailableMobSummons(int summonsSize, int skillLimit) { // limit prop for summons has another conotation, found thanks to MedicOP
int summonsCount;

Set<Integer> calledOids = this.calledMobOids;
if(calledOids != null) {
summonsCount = calledOids.size();
} else {
summonsCount = 0;
}

return Math.min(summonsSize, skillLimit - summonsCount);
}

public void addSummonedMob(MapleMonster mob) {
Set<Integer> calledOids = this.calledMobOids;
if (calledOids == null) {
calledOids = Collections.synchronizedSet(new HashSet<Integer>());
this.calledMobOids = calledOids;
}

calledOids.add(mob.getObjectId());
mob.setSummonerMob(this);
}

private void removeSummonedMob(int mobOid) {
Set<Integer> calledOids = this.calledMobOids;
if (calledOids != null) {
calledOids.remove(mobOid);
}
}

private void setSummonerMob(MapleMonster mob) {
this.callerMob = new WeakReference<>(mob);
}

private void dispatchClearSummons() {
MapleMonster caller = this.callerMob.get();
if (caller != null) {
caller.removeSummonedMob(this.getObjectId());
}

this.calledMobOids = null;
}

public void pushRemoveAfterAction(Runnable run) {
this.removeAfterAction = run;
}

public Runnable popRemoveAfterAction() {
Runnable r = this.removeAfterAction;
this.removeAfterAction = null;

return r;
}

public int getHp() {
return hp.get();
}
public void setHp(int hp) {
this.hp.set(hp);
}

public int getMaxHp() {
if (overrideStats != null) {
return overrideStats.getHp();
}
return stats.getHp();
}

public synchronized void addHp(int hp) {
if (this.hp.get() <= 0) {
return;
}
this.hp.addAndGet(hp);
}

public synchronized void setStartingHp(int hp) {
stats.setHp(hp); // refactored mob stats after non-static HP pool suggestion thanks to twigs
this.hp.set(hp);
}

public int getMp() {
return mp;
}

public void setMp(int mp) {
if (mp < 0) {
mp = 0;
}
this.mp = mp;
}

public int getMaxMp() {
if (overrideStats != null) {
return overrideStats.getMp();
}
return stats.getMp();
}

public int getExp() {
if (overrideStats != null) {
return overrideStats.getExp();
}
return stats.getExp();
}

public int getLevel() {
return stats.getLevel();
}

public int getCP() {
return stats.getCP();
}

public int getTeam() {
return team;
}

public void setTeam(int team) {
this.team = team;
}

public int getVenomMulti() {
return this.VenomMultiplier;
}

public void setVenomMulti(int multiplier) {
this.VenomMultiplier = multiplier;
}

public MapleMonsterStats getStats() {
return stats;
}

public boolean isBoss() {
return stats.isBoss();
}
public int getBossLog(String bossid) throws SQLException {
Connection con1 = DatabaseConnection.getConnection();
try {
int ret_count = 0;
PreparedStatement ps;
ps = con1.prepareStatement("select count(*) from bosslog where characterid = ? and bossid = ? and lastattempt >= subtime(current_timestamp, '1 0:0:0.0')");
ps.setInt(1, id);
ps.setString(2, bossid);
ResultSet rs = ps.executeQuery();
if (rs.next())
ret_count = rs.getInt(1);
else
ret_count = -1;
rs.close();
ps.close();
return ret_count;
} catch (Exception Ex) {
return -1;
}
}

public int getGiftLog(String bossid) throws SQLException {
Connection con1 = DatabaseConnection.getConnection();
try {
int ret_count = 0;
int accountid = 0;
PreparedStatement ps;
ps = con1.prepareStatement("select count(*) from bosslog where accountid = ? and bossid = ? and lastattempt >= subtime(current_timestamp, '1 0:0:0.0')");
ps.setInt(1, accountid);
ps.setString(2, bossid);
ResultSet rs = ps.executeQuery();
if (rs.next())
ret_count = rs.getInt(1);
else
ret_count = -1;
rs.close();
ps.close();
return ret_count;
} catch (Exception Ex) {
return -1;
}
}

//setBossLog module
public void setBossLog(String bossid) throws SQLException {
Connection con1 = DatabaseConnection.getConnection();
try {
PreparedStatement ps;
ps = con1.prepareStatement("insert into bosslog (accountid, characterid, bossid) values (?,?,?)");
int accountid = 0;
ps.setInt(1, accountid);
ps.setInt(2, id);
ps.setString(3, bossid);
ps.executeUpdate();
ps.close();
} catch (Exception Ex) {
}
}
public int getAnimationTime(String name) {
return stats.getAnimationTime(name);
}

private List<Integer> getRevives() {
return stats.getRevives();
}

private byte getTagColor() {
return stats.getTagColor();
}

private byte getTagBgColor() {
return stats.getTagBgColor();
}

public void setHpZero() { // force HP = 0
applyAndGetHpDamage(Integer.MAX_VALUE, false);
}

private boolean applyAnimationIfRoaming(int attackPos, MobSkill skill) { // roam: not casting attack or skill animations
if (!animationLock.tryLock()) {
return false;
}

try {
long animationTime;

if(skill == null) {
animationTime = MapleMonsterInformationProvider.getInstance().getMobAttackAnimationTime(this.getId(), attackPos);
} else {
animationTime = MapleMonsterInformationProvider.getInstance().getMobSkillAnimationTime(skill);
}

if(animationTime > 0) {
MobAnimationService service = (MobAnimationService) map.getChannelServer().getServiceAccess(ChannelServices.MOB_ANIMATION);
return service.registerMobOnAnimationEffect(map.getId(), this.hashCode(), animationTime);
} else {
return true;
}
} finally {
animationLock.unlock();
}
}

public synchronized Integer applyAndGetHpDamage(int delta, boolean stayAlive) {
int curHp = hp.get();
if (curHp <= 0) { // this monster is already dead
return null;
}

if(delta >= 0) {
if (stayAlive) {
curHp--;
}
int trueDamage = Math.min(curHp, delta);

hp.addAndGet(-trueDamage);
return trueDamage;
} else {
int trueHeal = -delta;
int hp2Heal = curHp + trueHeal;
int maxHp = getMaxHp();

if (hp2Heal > maxHp) {
trueHeal -= (hp2Heal - maxHp);
}

hp.addAndGet(trueHeal);
return trueHeal;
}
}

public synchronized void disposeMapObject() { // mob is no longer associated with the map it was in
hp.set(-1);
}

public void broadcastMobHpBar(MapleCharacter from) {
if (hasBossHPBar()) {
from.setPlayerAggro(this.hashCode());
from.getMap().broadcastBossHpMessage(this, this.hashCode(), makeBossHPBarPacket(), getPosition());
} else if (!isBoss()) {
int remainingHP = (int) Math.max(1, hp.get() * 100f / getMaxHp());
byte[] packet = MaplePacketCreator.showMonsterHP(getObjectId(), remainingHP);
if (from.getParty() != null) {
for (MaplePartyCharacter mpc : from.getParty().getMembers()) {
MapleCharacter member = from.getMap().getCharacterById(mpc.getId()); // god bless
if (member != null) {
member.announce(packet.clone()); // clone it just in case of crypto
}
}
} else {
from.announce(packet);
}
}
}

public boolean damage(MapleCharacter attacker, int damage, boolean stayAlive) {
boolean lastHit = false;

this.lockMonster();
try {
if (!this.isAlive()) {
return false;
}

/* pyramid not implemented
Pair<Integer, Integer> cool = this.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) {
this.applyDamage(attacker, damage, stayAlive, false);
if (!this.isAlive()) { // monster just died
lastHit = true;
}
}
} finally {
this.unlockMonster();
}

return lastHit;
}

/**
*
* @param from the player that dealt the damage
* @param damage
* @param stayAlive
*/
private void applyDamage(MapleCharacter from, int damage, boolean stayAlive, boolean fake) {
Integer trueDamage = applyAndGetHpDamage(damage, stayAlive);
if (trueDamage == null) {
return;
}

if (YamlConfig.config.server.USE_DEBUG) {
from.dropMessage(5, "Hitted MOB " + this.getId() + ", OID " + this.getObjectId());
}

if (!fake) {
dispatchMonsterDamaged(from, trueDamage);
}

if (!takenDamage.containsKey(from.getId())) {
takenDamage.put(from.getId(), new AtomicLong(trueDamage));
} else {
takenDamage.get(from.getId()).addAndGet(trueDamage);
}

broadcastMobHpBar(from);
}

public void applyFakeDamage(MapleCharacter from, int damage, boolean stayAlive) {
applyDamage(from, damage, stayAlive, true);
}

public void heal(int hp, int mp) {
Integer hpHealed = applyAndGetHpDamage(-hp, false);
if (hpHealed == null) {
return;
}

int mp2Heal = getMp() + mp;
int maxMp = getMaxMp();
if (mp2Heal >= maxMp) {
mp2Heal = maxMp;
}
setMp(mp2Heal);

if (hp > 0) {
getMap().broadcastMessage(MaplePacketCreator.healMonster(getObjectId(), hp, getHp(), getMaxHp()));
}

maxHpPlusHeal.addAndGet(hpHealed);
dispatchMonsterHealed(hpHealed);
}

public boolean isAttackedBy(MapleCharacter chr) {
return takenDamage.containsKey(chr.getId());
}

private static boolean isWhiteExpGain(MapleCharacter chr, Map<Integer, Float> personalRatio, double sdevRatio) {
Float pr = personalRatio.get(chr.getId());
if (pr == null) {
return false;
}

return pr >= sdevRatio;
}

private static double calcExperienceStandDevThreshold(List<Float> entryExpRatio, int totalEntries) {
float avgExpReward = 0.0f;
for (Float exp : entryExpRatio) {
avgExpReward += exp;
}

// thanks Simon (HarborMS) for finding an issue with solo party player gaining yellow EXP when soloing mobs
avgExpReward /= totalEntries;

float varExpReward = 0.0f;
for (Float exp : entryExpRatio) {
varExpReward += Math.pow(exp - avgExpReward, 2);
}
varExpReward /= entryExpRatio.size();

return avgExpReward + Math.sqrt(varExpReward);
}

private void distributePlayerExperience(MapleCharacter chr, float exp, float partyBonusMod, int totalPartyLevel, boolean highestPartyDamager, boolean whiteExpGain, boolean hasPartySharers) {
float playerExp = (YamlConfig.config.server.EXP_SPLIT_COMMON_MOD * chr.getLevel()) / totalPartyLevel;
if (highestPartyDamager) playerExp += YamlConfig.config.server.EXP_SPLIT_MVP_MOD;

playerExp *= exp;
float bonusExp = partyBonusMod * playerExp;

this.giveExpToCharacter(chr, playerExp, bonusExp, whiteExpGain, hasPartySharers);
giveFamilyRep(chr.getFamilyEntry());
}

private void distributePartyExperience(Map<MapleCharacter, Long> partyParticipation, float expPerDmg, Set<MapleCharacter> underleveled, Map<Integer, Float> personalRatio, double sdevRatio) {
IntervalBuilder leechInterval = new IntervalBuilder();
leechInterval.addInterval(this.getLevel() - YamlConfig.config.server.EXP_SPLIT_LEVEL_INTERVAL, this.getLevel() + YamlConfig.config.server.EXP_SPLIT_LEVEL_INTERVAL);

long maxDamage = 0, partyDamage = 0;
MapleCharacter participationMvp = null;
for (Entry<MapleCharacter, Long> e : partyParticipation.entrySet()) {
long entryDamage = e.getValue();
partyDamage += entryDamage;

if (maxDamage < entryDamage) {
maxDamage = entryDamage;
participationMvp = e.getKey();
}

// thanks Thora for pointing out leech level limitation
int chrLevel = e.getKey().getLevel();
leechInterval.addInterval(chrLevel - YamlConfig.config.server.EXP_SPLIT_LEECH_INTERVAL, chrLevel + YamlConfig.config.server.EXP_SPLIT_LEECH_INTERVAL);
}

List<MapleCharacter> expMembers = new LinkedList<>();
int totalPartyLevel = 0;

// thanks G h o s t, Alfred, Vcoc, BHB for poiting out a bug in detecting party members after membership transactions in a party took place
if (YamlConfig.config.server.USE_ENFORCE_MOB_LEVEL_RANGE) {
for (MapleCharacter member : partyParticipation.keySet().iterator().next().getPartyMembersOnSameMap()) {
if (!leechInterval.inInterval(member.getLevel())) {
underleveled.add(member);
continue;
}

totalPartyLevel += member.getLevel();
expMembers.add(member);
}
} else { // thanks Ari for noticing unused server flag after EXP system overhaul
for (MapleCharacter member : partyParticipation.keySet().iterator().next().getPartyMembersOnSameMap()) {
totalPartyLevel += member.getLevel();
expMembers.add(member);
}
}

int membersSize = expMembers.size();
float participationExp = partyDamage * expPerDmg;

// thanks Crypter for reporting an insufficiency on party exp bonuses
boolean hasPartySharers = membersSize > 1;
float partyBonusMod = hasPartySharers ? 0.05f * membersSize : 0.0f;

for (MapleCharacter mc : expMembers) {
distributePlayerExperience(mc, participationExp, partyBonusMod, totalPartyLevel, mc == participationMvp, isWhiteExpGain(mc, personalRatio, sdevRatio), hasPartySharers);
giveFamilyRep(mc.getFamilyEntry());
}
}

private void distributeExperience(int killerId) {
if (isAlive()) {
return;
}

Map<MapleParty, Map<MapleCharacter, Long>> partyExpDist = new HashMap<>();
Map<MapleCharacter, Long> soloExpDist = new HashMap<>();

Map<Integer, MapleCharacter> mapPlayers = map.getMapAllPlayers();

int totalEntries = 0; // counts "participant parties", players who no longer are available in the map is an "independent party"
for (Entry<Integer, AtomicLong> e : takenDamage.entrySet()) {
MapleCharacter chr = mapPlayers.get(e.getKey());
if (chr != null) {
long damage = e.getValue().longValue();

MapleParty p = chr.getParty();
if (p != null) {
Map<MapleCharacter, Long> partyParticipation = partyExpDist.get(p);
if (partyParticipation == null) {
partyParticipation = new HashMap<>(6);
partyExpDist.put(p, partyParticipation);

totalEntries += 1;
}

partyParticipation.put(chr, damage);
} else {
soloExpDist.put(chr, damage);
totalEntries += 1;
}
} else {
totalEntries += 1;
}
}

long totalDamage = maxHpPlusHeal.get();
int mobExp = getExp();
float expPerDmg = ((float) mobExp) / totalDamage;

Map<Integer, Float> personalRatio = new HashMap<>();
List<Float> entryExpRatio = new LinkedList<>();
for (Entry<MapleCharacter, Long> e : soloExpDist.entrySet()) {
float ratio = ((float) e.getValue()) / totalDamage;

personalRatio.put(e.getKey().getId(), ratio);
entryExpRatio.add(ratio);
}

for (Map<MapleCharacter, Long> m : partyExpDist.values()) {
float ratio = 0.0f;
for (Entry<MapleCharacter, Long> e : m.entrySet()) {
float chrRatio = ((float) e.getValue()) / totalDamage;

personalRatio.put(e.getKey().getId(), chrRatio);
ratio += chrRatio;
}

entryExpRatio.add(ratio);
}

double sdevRatio = calcExperienceStandDevThreshold(entryExpRatio, totalEntries);

// GMS-like player and party split calculations found thanks to Russt, KaidaTan, Dusk, AyumiLove - src: https://ayumilovemaple.wordpress.com...lator_formula/
Set<MapleCharacter> underleveled = new HashSet<>();
for (Entry<MapleCharacter, Long> chrParticipation : soloExpDist.entrySet()) {
float exp = chrParticipation.getValue() * expPerDmg;
MapleCharacter chr = chrParticipation.getKey();

distributePlayerExperience(chr, exp, 0.0f, chr.getLevel(), true, isWhiteExpGain(chr, personalRatio, sdevRatio), false);
}

for (Map<MapleCharacter, Long> partyParticipation : partyExpDist.values()) {
distributePartyExperience(partyParticipation, expPerDmg, underleveled, personalRatio, sdevRatio);
}

EventInstanceManager eim = getMap().getEventInstance();
if (eim != null) {
MapleCharacter chr = mapPlayers.get(killerId);
if (chr != null) {
eim.monsterKilled(chr, this);
}
}

for(MapleCharacter mc : underleveled) {
mc.showUnderleveledInfo(this);
}

}

private float getStatusExpMultiplier(MapleCharacter attacker, boolean hasPartySharers) {
float multiplier = 1.0f;

// thanks Prophecy & Aika for finding out Holy Symbol not being applied on party bonuses
Integer holySymbol = attacker.getBuffedValue(MapleBuffStat.HOLY_SYMBOL);
if (holySymbol != null) {
if (YamlConfig.config.server.USE_FULL_HOLY_SYMBOL) { // thanks Mordred, xinyifly, AyumiLove, andy33 for noticing HS hands out 20% of its potential on less than 3 players
multiplier *= (1.0 + (holySymbol.doubleValue() / 100.0));
} else {
multiplier *= (1.0 + (holySymbol.doubleValue() / (hasPartySharers ? 100.0 : 500.0)));
}
}

statiLock.lock();
try {
MonsterStatusEffect mse = stati.get(MonsterStatus.SHOWDOWN);
if (mse != null) {
multiplier *= (1.0 + (mse.getStati().get(MonsterStatus.SHOWDOWN).doubleValue() / 100.0));
}
} finally {
statiLock.unlock();
}

return multiplier;
}

private static int expValueToInteger(double exp) {
if (exp > Integer.MAX_VALUE) {
exp = Integer.MAX_VALUE;
} else if (exp < Integer.MIN_VALUE) {
exp = Integer.MIN_VALUE;
}

return (int) Math.round(exp); // operations on float point are not point-precise... thanks IxianMace for noticing -1 EXP gains
}

private void giveExpToCharacter(MapleCharacter attacker, Float personalExp, Float partyExp, boolean white, boolean hasPartySharers) {
if (attacker.isAlive()) {
if (personalExp != null) {
personalExp *= getStatusExpMultiplier(attacker, hasPartySharers);
personalExp *= attacker.getExpRate();
} else {
personalExp = 0.0f;
}

Integer expBonus = attacker.getBuffedValue(MapleBuffStat.EXP_INCREASE);
if (expBonus != null) { // exp increase player buff found thanks to HighKey21
personalExp += expBonus;
}

int _personalExp = expValueToInteger(personalExp); // assuming no negative xp here

if (partyExp != null) {
partyExp *= getStatusExpMultiplier(attacker, hasPartySharers);
partyExp *= attacker.getExpRate();
partyExp *= YamlConfig.config.server.PARTY_BONUS_EXP_RATE;
} else {
partyExp = 0.0f;
}

int _partyExp = expValueToInteger(partyExp);

attacker.gainExp(_personalExp, _partyExp, true, false, white);
attacker.increaseEquipExp(_personalExp);
attacker.raiseQuestMobCount(getId());
}
}

public List<MonsterDropEntry> retrieveRelevantDrops() {
if (this.getStats().isFriendly()) { // thanks Conrad for noticing friendly mobs not spawning loots after a recent update
return MapleMonsterInformationProvider.getInstance().retrieveEffectiveDrop(this.getId());
}

Map<Integer, MapleCharacter> pchars = map.getMapAllPlayers();

List<MapleCharacter> lootChars = new LinkedList<>();
for (Integer cid : takenDamage.keySet()) {
MapleCharacter chr = pchars.get(cid);
if (chr != null && chr.isLoggedinWorld()) {
lootChars.add(chr);
}
}

return MapleLootManager.retrieveRelevantDrops(this.getId(), lootChars);
}

public MapleCharacter killBy(final MapleCharacter killer) {
distributeExperience(killer != null ? killer.getId() : 0);

final Pair<MapleCharacter, Boolean> lastController = aggroRemoveController();
final List<Integer> toSpawn = this.getRevives();
if (toSpawn != null) {
final MapleMap reviveMap = map;
if (toSpawn.contains(9300216) && reviveMap.getId() > 925000000 && reviveMap.getId() < 926000000) {
reviveMap.broadcastMessage(MaplePacketCreator.playSound("Dojang/clear"));
reviveMap.broadcastMessage(MaplePacketCreator.showEffect("dojang/end/clear"));
}
Pair<Integer, String> timeMob = reviveMap.getTimeMob();
if (timeMob != null) {
if (toSpawn.contains(timeMob.getLeft())) {
reviveMap.broadcastMessage(MaplePacketCreator.serverNotice(6, timeMob.getRight()));
}
}

if(toSpawn.size() > 0) {
final EventInstanceManager eim = this.getMap().getEventInstance();

TimerManager.getInstance().schedule(new Runnable() {
@Override
public void run() {
MapleCharacter controller = lastController.getLeft();
boolean aggro = lastController.getRight();

for (Integer mid : toSpawn) {
final MapleMonster mob = MapleLifeFactory.getMonster(mid);
mob.setPosition(getPosition());
mob.setFh(getFh());
mob.setParentMobOid(getObjectId());

if (dropsDisabled()) {
mob.disableDrops();
}
reviveMap.spawnMonster(mob);

if (mob.getId() >= 8810010 && mob.getId() <= 8810017 && reviveMap.isHorntailDefeated()) {
boolean htKilled = false;
MapleMonster ht = reviveMap.getMonsterById(8810018);

if(ht != null) {
ht.lockMonster();
try {
htKilled = ht.isAlive();
ht.setHpZero();
} finally {
ht.unlockMonster();
}

if(htKilled) {
reviveMap.killMonster(ht, killer, true);
}
}

for(int i = 8810017; i >= 8810010; i--) {
reviveMap.killMonster(reviveMap.getMonsterById(i), killer, true);
}
} else if (controller != null) {
mob.aggroSwitchController(controller, aggro);
}

if(eim != null) {
eim.reviveMonster(mob);
}
}
}
}, getAnimationTime("die1"));
}
} else { // is this even necessary?
System.out.println("[CRITICAL LOSS] toSpawn is null for " + this.getName());
}

MapleCharacter looter = map.getCharacterById(getHighestDamagerId());
return looter != null ? looter : killer;
}

public void dropFromFriendlyMonster(long delay) {
final MapleMonster m = this;
monsterItemDrop = TimerManager.getInstance().register(new Runnable() {
@Override
public void run() {
if (!m.isAlive()) {
if (monsterItemDrop != null) {
monsterItemDrop.cancel(false);
}

return;
}

MapleMap map = m.getMap();
List<MapleCharacter> chrList = map.getAllPlayers();
if (!chrList.isEmpty()) {
MapleCharacter chr = (MapleCharacter) chrList.get(0);

EventInstanceManager eim = map.getEventInstance();
if (eim != null) {
eim.friendlyItemDrop(m);
}

map.dropFromFriendlyMonster(chr, m);
}
}
}, delay, delay);
}

private void dispatchRaiseQuestMobCount() {
Set<Integer> attackerChrids = takenDamage.keySet();
if(!attackerChrids.isEmpty()) {
Map<Integer, MapleCharacter> mapChars = map.getMapPlayers();
if(!mapChars.isEmpty()) {
int mobid = getId();

for (Integer chrid : attackerChrids) {
MapleCharacter chr = mapChars.get(chrid);

if(chr != null && chr.isLoggedinWorld()) {
chr.raiseQuestMobCount(mobid);
}
}
}
}
}

public void dispatchMonsterKilled(boolean hasKiller) {
processMonsterKilled(hasKiller);

EventInstanceManager eim = getMap().getEventInstance();
if (eim != null) {
if (!this.getStats().isFriendly()) {
eim.monsterKilled(this, hasKiller);
} else {
eim.friendlyKilled(this, hasKiller);
}
}
}

private synchronized void processMonsterKilled(boolean hasKiller) {
if(!hasKiller) { // players won't gain EXP from a mob that has no killer, but a quest count they should
dispatchRaiseQuestMobCount();
}

this.aggroClearDamages();
this.dispatchClearSummons();

MonsterListener[] listenersList;
statiLock.lock();
try {
listenersList = listeners.toArray(new MonsterListener[listeners.size()]);
} finally {
statiLock.unlock();
}

for (MonsterListener listener : listenersList) {
listener.monsterKilled(getAnimationTime("die1"));
}

statiLock.lock();
try {
stati.clear();
alreadyBuffed.clear();
listeners.clear();
} finally {
statiLock.unlock();
}
}

private void dispatchMonsterDamaged(MapleCharacter from, int trueDmg) {
MonsterListener[] listenersList;
statiLock.lock();
try {
listenersList = listeners.toArray(new MonsterListener[listeners.size()]);
} finally {
statiLock.unlock();
}

for (MonsterListener listener : listenersList) {
listener.monsterDamaged(from, trueDmg);
}
}

private void dispatchMonsterHealed(int trueHeal) {
MonsterListener[] listenersList;
statiLock.lock();
try {
listenersList = listeners.toArray(new MonsterListener[listeners.size()]);
} finally {
statiLock.unlock();
}

for (MonsterListener listener : listenersList) {
listener.monsterHealed(trueHeal);
}
}

private void giveFamilyRep(MapleFamilyEntry entry) {
if(entry != null) {
int repGain = isBoss() ? YamlConfig.config.server.FAMILY_REP_PER_BOSS_KILL : YamlConfig.config.server.FAMILY_REP_PER_KILL;
if(getMaxHp() <= 1) repGain = 0; //don't count trash mobs
entry.giveReputationToSenior(repGain, true);
}
}

public int getHighestDamagerId() {
int curId = 0;
long curDmg = 0;

for (Entry<Integer, AtomicLong> damage : takenDamage.entrySet()) {
curId = damage.getValue().get() >= curDmg ? damage.getKey() : curId;
curDmg = damage.getKey() == curId ? damage.getValue().get() : curDmg;
}

return curId;
}

public boolean isAlive() {
return this.hp.get() > 0;
}

public void addListener(MonsterListener listener) {
statiLock.lock();
try {
listeners.add(listener);
} finally {
statiLock.unlock();
}
}

public MapleCharacter getController() {
return controller.get();
}

private void setController(MapleCharacter controller) {
this.controller = new WeakReference<>(controller);
}

public boolean isControllerHasAggro() {
return fake ? false : controllerHasAggro;
}

private void setControllerHasAggro(boolean controllerHasAggro) {
if (!fake) {
this.controllerHasAggro = controllerHasAggro;
}
}

public boolean isControllerKnowsAboutAggro() {
return fake ? false : controllerKnowsAboutAggro;
}

private void setControllerKnowsAboutAggro(boolean controllerKnowsAboutAggro) {
if (!fake) {
this.controllerKnowsAboutAggro = controllerKnowsAboutAggro;
}
}

private void setControllerHasPuppet(boolean controllerHasPuppet) {
this.controllerHasPuppet = controllerHasPuppet;
}

public byte[] makeBossHPBarPacket() {
return MaplePacketCreator.showBossHP(getId(), getHp(), getMaxHp(), getTagColor(), getTagBgColor());
}

public boolean hasBossHPBar() {
return isBoss() && getTagColor() > 0;
}

@Override
public void sendSpawnData(MapleClient client) {
if (hp.get() <= 0) { // mustn't monsterLock this function
return;
}
if (fake) {
client.announce(MaplePacketCreator.spawnFakeMonster(this, 0));
} else {
client.announce(MaplePacketCreator.spawnMonster(this, false));
}

if (hasBossHPBar()) {
client.announceBossHpBar(this, this.hashCode(), makeBossHPBarPacket());
}
}

@Override
public void sendDestroyData(MapleClient client) {
client.announce(MaplePacketCreator.killMonster(getObjectId(), false));
client.announce(MaplePacketCreator.killMonster(getObjectId(), true));
}

@Override
public MapleMapObjectType getType() {
return MapleMapObjectType.MONSTER;
}

public boolean isMobile() {
return stats.isMobile();
}

@Override
public boolean isFacingLeft() {
int fixedStance = stats.getFixedStance(); // thanks DimDiDima for noticing inconsistency on some AOE mobskills
if (fixedStance != 0) {
return Math.abs(fixedStance) % 2 == 1;
}

return super.isFacingLeft();
}

public ElementalEffectiveness getElementalEffectiveness(Element e) {
statiLock.lock();
try {
if (stati.get(MonsterStatus.DOOM) != null) {
return ElementalEffectiveness.NORMAL; // like blue snails
}
} finally {
statiLock.unlock();
}

return getMonsterEffectiveness(e);
}

private ElementalEffectiveness getMonsterEffectiveness(Element e) {
monsterLock.lock();
try {
return stats.getEffectiveness(e);
} finally {
monsterLock.unlock();
}
}

private MapleCharacter getActiveController() {
MapleCharacter chr = getController();

if (chr != null && chr.isLoggedinWorld() && chr.getMap() == this.getMap()) {
return chr;
} else {
return null;
}
}

private void broadcastMonsterStatusMessage(byte[] packet) {
map.broadcastMessage(packet, getPosition());

MapleCharacter chrController = getActiveController();
if (chrController != null && !chrController.isMapObjectVisible(MapleMonster.this)) {
chrController.announce(packet);
}
}

private int broadcastStatusEffect(final MonsterStatusEffect status) {
int animationTime = status.getSkill().getAnimationTime();
byte[] packet = MaplePacketCreator.applyMonsterStatus(getObjectId(), status, null);
broadcastMonsterStatusMessage(packet);

return animationTime;
}

public boolean applyStatus(MapleCharacter from, final MonsterStatusEffect status, boolean poison, long duration) {
return applyStatus(from, status, poison, duration, false);
}

public boolean applyStatus(MapleCharacter from, final MonsterStatusEffect status, boolean poison, long duration, boolean venom) {
switch (getMonsterEffectiveness(status.getSkill().getElement())) {
case IMMUNE:
case STRONG:
case NEUTRAL:
return false;
case NORMAL:
case WEAK:
break;
default: {
System.out.println("Unknown elemental effectiveness: " + getMonsterEffectiveness(status.getSkill().getElement()));
return false;
}
}

if (status.getSkill().getId() == FPMage.ELEMENT_COMPOSITION) { // fp compo
ElementalEffectiveness effectiveness = getMonsterEffectiveness(Element.POISON);
if (effectiveness == ElementalEffectiveness.IMMUNE || effectiveness == ElementalEffectiveness.STRONG) {
return false;
}
} else if (status.getSkill().getId() == ILMage.ELEMENT_COMPOSITION) { // il compo
ElementalEffectiveness effectiveness = getMonsterEffectiveness(Element.ICE);
if (effectiveness == ElementalEffectiveness.IMMUNE || effectiveness == ElementalEffectiveness.STRONG) {
return false;
}
} else if (status.getSkill().getId() == NightLord.VENOMOUS_STAR || status.getSkill().getId() == Shadower.VENOMOUS_STAB || status.getSkill().getId() == NightWalker.VENOM) {// venom
if (getMonsterEffectiveness(Element.POISON) == ElementalEffectiveness.WEAK) {
return false;
}
}
if (poison && hp.get() <= 1) {
return false;
}

final Map<MonsterStatus, Integer> statis = status.getStati();
if (stats.isBoss()) {
if (!(statis.containsKey(MonsterStatus.SPEED)
&& statis.containsKey(MonsterStatus.NINJA_AMBUSH)
&& statis.containsKey(MonsterStatus.WATK))) {
return false;
}
}

final Channel ch = map.getChannelServer();
final int mapid = map.getId();
if(statis.size() > 0) {
statiLock.lock();
try {
for (MonsterStatus stat : statis.keySet()) {
final MonsterStatusEffect oldEffect = stati.get(stat);
if (oldEffect != null) {
oldEffect.removeActiveStatus(stat);
if (oldEffect.getStati().isEmpty()) {
MobStatusService service = (MobStatusService) map.getChannelServer().getServiceAccess(ChannelServices.MOB_STATUS);
service.interruptMobStatus(mapid, oldEffect);
}
}
}
} finally {
statiLock.unlock();
}
}

final Runnable cancelTask = new Runnable() {

@Override
public void run() {
if (isAlive()) {
byte[] packet = MaplePacketCreator.cancelMonsterStatus(getObjectId(), status.getStati());
broadcastMonsterStatusMessage(packet);
}

statiLock.lock();
try {
for (MonsterStatus stat : status.getStati().keySet()) {
stati.remove(stat);
}
} finally {
statiLock.unlock();
}

setVenomMulti(0);
}
};

Runnable overtimeAction = null;
int overtimeDelay = -1;

int animationTime;
if (poison) {
int poisonLevel = from.getSkillLevel(status.getSkill());
int poisonDamage = Math.min(Short.MAX_VALUE, (int) (getMaxHp() / (70.0 - poisonLevel) + 0.999));
status.setValue(MonsterStatus.POISON, Integer.valueOf(poisonDamage));
animationTime = broadcastStatusEffect(status);

overtimeAction = new DamageTask(poisonDamage, from, status, 0);
overtimeDelay = 1000;
} else if (venom) {
if (from.getJob() == MapleJob.NIGHTLORD || from.getJob() == MapleJob.SHADOWER || from.getJob().isA(MapleJob.NIGHTWALKER3)) {
int poisonLevel, matk, jobid = from.getJob().getId();
int skillid = (jobid == 412 ? NightLord.VENOMOUS_STAR : (jobid == 422 ? Shadower.VENOMOUS_STAB : NightWalker.VENOM));
poisonLevel = from.getSkillLevel(SkillFactory.getSkill(skillid));
if (poisonLevel <= 0) {
return false;
}
matk = SkillFactory.getSkill(skillid).getEffect(poisonLevel).getMatk();
int luk = from.getLuk();
int maxDmg = (int) Math.ceil(Math.min(Short.MAX_VALUE, 0.2 * luk * matk));
int minDmg = (int) Math.ceil(Math.min(Short.MAX_VALUE, 0.1 * luk * matk));
int gap = maxDmg - minDmg;
if (gap == 0) {
gap = 1;
}
int poisonDamage = 0;
for (int i = 0; i < getVenomMulti(); i++) {
poisonDamage += (Randomizer.nextInt(gap) + minDmg);
}
poisonDamage = Math.min(Short.MAX_VALUE, poisonDamage);
status.setValue(MonsterStatus.VENOMOUS_WEAPON, Integer.valueOf(poisonDamage));
status.setValue(MonsterStatus.POISON, Integer.valueOf(poisonDamage));
animationTime = broadcastStatusEffect(status);

overtimeAction = new DamageTask(poisonDamage, from, status, 0);
overtimeDelay = 1000;
} else {
return false;
}
/*
} else if (status.getSkill().getId() == Hermit.SHADOW_WEB || status.getSkill().getId() == NightWalker.SHADOW_WEB) { //Shadow Web
int webDamage = (int) (getMaxHp() / 50.0 + 0.999);
status.setValue(MonsterStatus.SHADOW_WEB, Integer.valueOf(webDamage));
animationTime = broadcastStatusEffect(status);

overtimeAction = new DamageTask(webDamage, from, status, 1);
overtimeDelay = 3500;
*/
} else if (status.getSkill().getId() == 4121004 || status.getSkill().getId() == 4221004) { // Ninja Ambush
final Skill skill = SkillFactory.getSkill(status.getSkill().getId());
final byte level = from.getSkillLevel(skill);
final int damage = (int) ((from.getStr() + from.getLuk()) * ((3.7 * skill.getEffect(level).getDamage()) / 100));

status.setValue(MonsterStatus.NINJA_AMBUSH, Integer.valueOf(damage));
animationTime = broadcastStatusEffect(status);

overtimeAction = new DamageTask(damage, from, status, 2);
overtimeDelay = 1000;
} else {
animationTime = broadcastStatusEffect(status);
}

statiLock.lock();
try {
for (MonsterStatus stat : status.getStati().keySet()) {
stati.put(stat, status);
alreadyBuffed.add(stat);
}
} finally {
statiLock.unlock();
}

MobStatusService service = (MobStatusService) map.getChannelServer().getServiceAccess(ChannelServices.MOB_STATUS);
service.registerMobStatus(mapid, status, cancelTask, duration + animationTime - 100, overtimeAction, overtimeDelay);
return true;
}

public final void dispelSkill(final MobSkill skillId) {
List<MonsterStatus> toCancel = new ArrayList<MonsterStatus>();
for (Entry<MonsterStatus, MonsterStatusEffect> effects : stati.entrySet()) {
MonsterStatusEffect mse = effects.getValue();
if (mse.getMobSkill() != null && mse.getMobSkill().getSkillId() == skillId.getSkillId()) { //not checking for level.
toCancel.add(effects.getKey());
}
}
for (MonsterStatus stat : toCancel) {
debuffMobStat(stat);
}
}

public void applyMonsterBuff(final Map<MonsterStatus, Integer> stats, final int x, int skillId, long duration, MobSkill skill, final List<Integer> reflection) {
final Runnable cancelTask = new Runnable() {

@Override
public void run() {
if (isAlive()) {
byte[] packet = MaplePacketCreator.cancelMonsterStatus(getObjectId(), stats);
broadcastMonsterStatusMessage(packet);

statiLock.lock();
try {
for (final MonsterStatus stat : stats.keySet()) {
stati.remove(stat);
}
} finally {
statiLock.unlock();
}
}
}
};
final MonsterStatusEffect effect = new MonsterStatusEffect(stats, null, skill, true);
byte[] packet = MaplePacketCreator.applyMonsterStatus(getObjectId(), effect, reflection);
broadcastMonsterStatusMessage(packet);

statiLock.lock();
try {
for (MonsterStatus stat : stats.keySet()) {
stati.put(stat, effect);
alreadyBuffed.add(stat);
}
} finally {
statiLock.unlock();
}

MobStatusService service = (MobStatusService) map.getChannelServer().getServiceAccess(ChannelServices.MOB_STATUS);
service.registerMobStatus(map.getId(), effect, cancelTask, duration);
}

public void refreshMobPosition() {
resetMobPosition(getPosition());
}

public void resetMobPosition(Point newPoint) {
aggroRemoveController();

setPosition(newPoint);
map.broadcastMessage(MaplePacketCreator.moveMonster(this.getObjectId(), false, -1, 0, 0, 0, this.getPosition(), this.getIdleMovement(), getIdleMovementDataLength()));
map.moveMonster(this, this.getPosition());

aggroUpdateController();
}

private void debuffMobStat(MonsterStatus stat) {
MonsterStatusEffect oldEffect;
statiLock.lock();
try {
oldEffect = stati.remove(stat);
} finally {
statiLock.unlock();
}

if (oldEffect != null) {
byte[] packet = MaplePacketCreator.cancelMonsterStatus(getObjectId(), oldEffect.getStati());
broadcastMonsterStatusMessage(packet);
}
}

public void debuffMob(int skillid) {
MonsterStatus[] statups = {MonsterStatus.WEAPON_ATTACK_UP, MonsterStatus.WEAPON_DEFENSE_UP, MonsterStatus.MAGIC_ATTACK_UP, MonsterStatus.MAGIC_DEFENSE_UP};
statiLock.lock();
try {
if(skillid == Hermit.SHADOW_MESO) {
debuffMobStat(statups[1]);
debuffMobStat(statups[3]);
} else if(skillid == Priest.DISPEL) {
for(MonsterStatus ms : statups) {
debuffMobStat(ms);
}
} else { // is a crash skill
int i = (skillid == Crusader.ARMOR_CRASH ? 1 : (skillid == WhiteKnight.MAGIC_CRASH ? 2 : 0));
debuffMobStat(statups[i]);

if(YamlConfig.config.server.USE_ANTI_IMMUNITY_CRASH) {
if (skillid == Crusader.ARMOR_CRASH) {
if(!isBuffed(MonsterStatus.WEAPON_REFLECT)) {
debuffMobStat(MonsterStatus.WEAPON_IMMUNITY);
}
if(!isBuffed(MonsterStatus.MAGIC_REFLECT)) {
debuffMobStat(MonsterStatus.MAGIC_IMMUNITY);
}
} else if (skillid == WhiteKnight.MAGIC_CRASH) {
if(!isBuffed(MonsterStatus.MAGIC_REFLECT)) {
debuffMobStat(MonsterStatus.MAGIC_IMMUNITY);
}
} else {
if(!isBuffed(MonsterStatus.WEAPON_REFLECT)) {
debuffMobStat(MonsterStatus.WEAPON_IMMUNITY);
}
}
}
}
} finally {
statiLock.unlock();
}
}

public boolean isBuffed(MonsterStatus status) {
statiLock.lock();
try {
return stati.containsKey(status);
} finally {
statiLock.unlock();
}
}

public void setFake(boolean fake) {
monsterLock.lock();
try {
this.fake = fake;
} finally {
monsterLock.unlock();
}
}

public boolean isFake() {
monsterLock.lock();
try {
return fake;
} finally {
monsterLock.unlock();
}
}

public MapleMap getMap() {
return map;
}

public MapleMonsterAggroCoordinator getMapAggroCoordinator() {
return map.getAggroCoordinator();
}

public List<Pair<Integer, Integer>> getSkills() {
return stats.getSkills();
}

public boolean hasSkill(int skillId, int level) {
return stats.hasSkill(skillId, level);
}

public int getSkillPos(int skillId, int level) {
int pos = 0;
for (Pair<Integer, Integer> ms : this.getSkills()) {
if (ms.getLeft() == skillId && ms.getRight() == level) {
return pos;
}

pos++;
}

return -1;
}

public boolean canUseSkill(MobSkill toUse, boolean apply) {
if (toUse == null) {
return false;
}

int useSkillid = toUse.getSkillId();
if (useSkillid >= 143 && useSkillid <= 145) {
if (this.isBuffed(MonsterStatus.WEAPON_REFLECT) || this.isBuffed(MonsterStatus.MAGIC_REFLECT)) {
return false;
}
}

monsterLock.lock();
try {
for (Pair<Integer, Integer> skill : usedSkills) { // thanks OishiiKawaiiDesu for noticing an issue with mobskill cooldown
if (skill.getLeft() == useSkillid && skill.getRight() == toUse.getSkillLevel()) {
return false;
}
}

int mpCon = toUse.getMpCon();
if (mp < mpCon) {
return false;
}

/*
if (!this.applyAnimationIfRoaming(-1, toUse)) {
return false;
}
*/

if (apply) {
this.usedSkill(toUse);
}
} finally {
monsterLock.unlock();
}

return true;
}

private void usedSkill(MobSkill skill) {
final int skillId = skill.getSkillId(), level = skill.getSkillLevel();
long cooltime = skill.getCoolTime();

monsterLock.lock();
try {
mp -= skill.getMpCon();

Pair<Integer, Integer> skillKey = new Pair<>(skillId, level);
this.usedSkills.add(skillKey);

Integer useCount = this.skillsUsed.remove(skillKey);
if (useCount != null) {
this.skillsUsed.put(skillKey, useCount + 1);
} else {
this.skillsUsed.put(skillKey, 1);
}
} finally {
monsterLock.unlock();
}

final MapleMonster mons = this;
MapleMap mmap = mons.getMap();
Runnable r = new Runnable() {
@Override
public void run() {
mons.clearSkill(skillId, level);
}
};

MobClearSkillService service = (MobClearSkillService) map.getChannelServer().getServiceAccess(ChannelServices.MOB_CLEAR_SKILL);
service.registerMobClearSkillAction(mmap.getId(), r, cooltime);
}

private void clearSkill(int skillId, int level) {
monsterLock.lock();
try {
int index = -1;
for (Pair<Integer, Integer> skill : usedSkills) {
if (skill.getLeft() == skillId && skill.getRight() == level) {
index = usedSkills.indexOf(skill);
break;
}
}
if (index != -1) {
usedSkills.remove(index);
}
} finally {
monsterLock.unlock();
}
}

public int canUseAttack(int attackPos, boolean isSkill) {
monsterLock.lock();
try {
/*
if (usedAttacks.contains(attackPos)) {
return -1;
}
*/

Pair<Integer, Integer> attackInfo = MapleMonsterInformationProvider.getInstance().getMobAttackInfo(this.getId(), attackPos);
if (attackInfo == null) {
return -1;
}

int mpCon = attackInfo.getLeft();
if (mp < mpCon) {
return -1;
}

/*
if (!this.applyAnimationIfRoaming(attackPos, null)) {
return -1;
}
*/

usedAttack(attackPos, mpCon, attackInfo.getRight());
return 1;
} finally {
monsterLock.unlock();
}
}

private void usedAttack(final int attackPos, int mpCon, int cooltime) {
monsterLock.lock();
try {
mp -= mpCon;
usedAttacks.add(attackPos);

final MapleMonster mons = this;
MapleMap mmap = mons.getMap();
Runnable r = new Runnable() {
@Override
public void run() {
mons.clearAttack(attackPos);
}
};

MobClearSkillService service = (MobClearSkillService) map.getChannelServer().getServiceAccess(ChannelServices.MOB_CLEAR_SKILL);
service.registerMobClearSkillAction(mmap.getId(), r, cooltime);
} finally {
monsterLock.unlock();
}
}

private void clearAttack(int attackPos) {
monsterLock.lock();
try {
usedAttacks.remove(attackPos);
} finally {
monsterLock.unlock();
}
}

public int getNoSkills() {
return this.stats.getNoSkills();
}

public boolean isFirstAttack() {
return this.stats.isFirstAttack();
}

public int getBuffToGive() {
return this.stats.getBuffToGive();
}

private final class DamageTask implements Runnable {

private final int dealDamage;
private final MapleCharacter chr;
private final MonsterStatusEffect status;
private final int type;
private final MapleMap map;

private DamageTask(int dealDamage, MapleCharacter chr, MonsterStatusEffect status, int type) {
this.dealDamage = dealDamage;
this.chr = chr;
this.status = status;
this.type = type;
this.map = chr.getMap();
}

@Override
public void run() {
int curHp = hp.get();
if(curHp <= 1) {
MobStatusService service = (MobStatusService) map.getChannelServer().getServiceAccess(ChannelServices.MOB_STATUS);
service.interruptMobStatus(map.getId(), status);
return;
}

int damage = dealDamage;
if (damage >= curHp) {
damage = curHp - 1;
if (type == 1 || type == 2) {
MobStatusService service = (MobStatusService) map.getChannelServer().getServiceAccess(ChannelServices.MOB_STATUS);
service.interruptMobStatus(map.getId(), status);
}
}
if (damage > 0) {
lockMonster();
try {
applyDamage(chr, damage, true, false);
} finally {
unlockMonster();
}

if (type == 1) {
map.broadcastMessage(MaplePacketCreator.damageMonster(getObjectId(), damage), getPosition());
} else if (type == 2) {
if(damage < dealDamage) { // ninja ambush (type 2) is already displaying DOT to the caster
map.broadcastMessage(MaplePacketCreator.damageMonster(getObjectId(), damage), getPosition());
}
}
}
}
}

public String getName() {
return stats.getName();
}

public void addStolen(int itemId) {
stolenItems.add(itemId);
}

public List<Integer> getStolen() {
return stolenItems;
}

public void setTempEffectiveness(Element e, ElementalEffectiveness ee, long milli) {
monsterLock.lock();
try {
final Element fE = e;
final ElementalEffectiveness fEE = stats.getEffectiveness(e);
if (!fEE.equals(ElementalEffectiveness.WEAK)) {
stats.setEffectiveness(e, ee);

MapleMap mmap = this.getMap();
Runnable r = new Runnable() {
@Override
public void run() {
monsterLock.lock();
try {
stats.removeEffectiveness(fE);
stats.setEffectiveness(fE, fEE);
} finally {
monsterLock.unlock();
}
}
};

MobClearSkillService service = (MobClearSkillService) mmap.getChannelServer().getServiceAccess(ChannelServices.MOB_CLEAR_SKILL);
service.registerMobClearSkillAction(mmap.getId(), r, milli);
}
} finally {
monsterLock.unlock();
}
}

public Collection<MonsterStatus> alreadyBuffedStats() {
statiLock.lock();
try {
return Collections.unmodifiableCollection(alreadyBuffed);
} finally {
statiLock.unlock();
}
}

public BanishInfo getBanish() {
return stats.getBanishInfo();
}

public void setBoss(boolean boss) {
this.stats.setBoss(boss);
}

public int getDropPeriodTime() {
return stats.getDropPeriod();
}

public int getPADamage() {
return stats.getPADamage();
}

public Map<MonsterStatus, MonsterStatusEffect> getStati() {
statiLock.lock();
try {
return new HashMap<>(stati);
} finally {
statiLock.unlock();
}
}

public MonsterStatusEffect getStati(MonsterStatus ms) {
statiLock.lock();
try {
return stati.get(ms);
} finally {
statiLock.unlock();
}
}

// ---- one can always have fun trying these pieces of codes below in-game rofl ----

public final ChangeableStats getChangedStats() {
return ostats;
}

public final int getMobMaxHp() {
if (ostats != null) {
return ostats.hp;
}
return stats.getHp();
}

public final void setOverrideStats(final OverrideMonsterStats ostats) {
this.ostats = new ChangeableStats(stats, ostats);
this.hp.set(ostats.getHp());
this.mp = ostats.getMp();
}

public void setOverrideStats(MapleMonsterStats override) {
this.overrideStats = override;
this.hp.set(ostats.getHp());
this.mp = ostats.getMp();// this.hp = override.getHp();
// this.mp = override.getMp();
}
public final void changeLevel(final int newLevel) {
changeLevel(newLevel, true);
}

public final void changeLevel(final int newLevel, boolean pqMob) {
if (!stats.isChangeable()) {
return;
}
this.ostats = new ChangeableStats(stats, newLevel, pqMob);
this.hp.set(ostats.getHp());
this.mp = ostats.getMp();
}

private float getDifficultyRate(final int difficulty) {
switch(difficulty) {
case 6:
return(7.7f);
case 5:
return(5.6f);
case 4:
return(3.2f);
case 3:
return(2.1f);
case 2:
return(1.4f);
}

return(1.0f);
}

private void changeLevelByDifficulty(final int difficulty, boolean pqMob) {
changeLevel((int)(this.getLevel() * getDifficultyRate(difficulty)), pqMob);
}

public final void changeDifficulty(final int difficulty, boolean pqMob) {
changeLevelByDifficulty(difficulty, pqMob);
}

// ---------------------------------------------------------------------------------

private boolean isPuppetInVicinity(MapleSummon summon) {
return summon.getPosition().distanceSq(this.getPosition()) < 177777;
}

public boolean isCharacterPuppetInVicinity(MapleCharacter chr) {
MapleStatEffect mse = chr.getBuffEffect(MapleBuffStat.PUPPET);
if (mse != null) {
MapleSummon summon = chr.getSummonByKey(mse.getSourceId());

// check whether mob is currently under a puppet's field of action or not
if (summon != null) {
if (isPuppetInVicinity(summon)) {
return true;
}
} else {
map.getAggroCoordinator().removePuppetAggro(chr.getId());
}
}

return false;
}

public boolean isLeadingPuppetInVicinity() {
MapleCharacter chrController = this.getActiveController();

if (chrController != null) {
if (this.isCharacterPuppetInVicinity(chrController)) {
return true;
}
}

return false;
}

private MapleCharacter getNextControllerCandidate() {
int mincontrolled = Integer.MAX_VALUE;
MapleCharacter newController = null;

int mincontrolleddead = Integer.MAX_VALUE;
MapleCharacter newControllerDead = null;

MapleCharacter newControllerWithPuppet = null;

for (MapleCharacter chr : getMap().getAllPlayers()) {
if (!chr.isHidden()) {
int ctrlMonsSize = chr.getNumControlledMonsters();

if (isCharacterPuppetInVicinity(chr)) {
newControllerWithPuppet = chr;
break;
} else if (chr.isAlive()) {
if (ctrlMonsSize < mincontrolled) {
mincontrolled = ctrlMonsSize;
newController = chr;
}
} else {
if (ctrlMonsSize < mincontrolleddead) {
mincontrolleddead = ctrlMonsSize;
newControllerDead = chr;
}
}
}
}

if (newControllerWithPuppet != null) {
return newControllerWithPuppet;
} else if (newController != null) {
return newController;
} else {
return newControllerDead;
}
}

/**
* Removes controllability status from the current controller of this mob.
*
*/
public Pair<MapleCharacter, Boolean> aggroRemoveController() {
MapleCharacter chrController;
boolean hadAggro;

aggroUpdateLock.lock();
try {
chrController = getActiveController();
hadAggro = isControllerHasAggro();

this.setController(null);
this.setControllerHasAggro(false);
this.setControllerKnowsAboutAggro(false);
} finally {
aggroUpdateLock.unlock();
}

if (chrController != null) { // this can/should only happen when a hidden gm attacks the monster
if (!this.isFake()) chrController.announce(MaplePacketCreator.stopControllingMonster(this.getObjectId()));
chrController.stopControllingMonster(this);
}

return new Pair<>(chrController, hadAggro);
}

/**
* Pass over the mob controllability and updates aggro status on the new
* player controller.
*
*/
public void aggroSwitchController(MapleCharacter newController, boolean immediateAggro) {
if (aggroUpdateLock.tryLock()) {
try {
MapleCharacter prevController = getController();
if (prevController == newController) {
return;
}

aggroRemoveController();
if (!(newController != null && newController.isLoggedinWorld() && newController.getMap() == this.getMap())) {
return;
}

this.setController(newController);
this.setControllerHasAggro(immediateAggro);
this.setControllerKnowsAboutAggro(false);
this.setControllerHasPuppet(false);
} finally {
aggroUpdateLock.unlock();
}

this.aggroUpdatePuppetVisibility();
aggroMonsterControl(newController.getClient(), this, immediateAggro);
newController.controlMonster(this);
}
}

public void aggroAddPuppet(MapleCharacter player) {
MapleMonsterAggroCoordinator mmac = map.getAggroCoordinator();
mmac.addPuppetAggro(player);

aggroUpdatePuppetController(player);

if (this.isControllerHasAggro()) {
this.aggroUpdatePuppetVisibility();
}
}

public void aggroRemovePuppet(MapleCharacter player) {
MapleMonsterAggroCoordinator mmac = map.getAggroCoordinator();
mmac.removePuppetAggro(player.getId());

aggroUpdatePuppetController(null);

if (this.isControllerHasAggro()) {
this.aggroUpdatePuppetVisibility();
}
}

/**
* Automagically finds a new controller for the given monster from the chars
* on the map it is from...
*
*/
public void aggroUpdateController() {
MapleCharacter chrController = this.getActiveController();
if (chrController != null && chrController.isAlive()) {
return;
}

MapleCharacter newController = getNextControllerCandidate();
if (newController == null) { // was a new controller found? (if not no one is on the map)
return;
}

this.aggroSwitchController(newController, false);
}

/**
* Finds a new controller for the given monster from the chars with deployed
* puppet nearby on the map it is from...
*
*/
private void aggroUpdatePuppetController(MapleCharacter newController) {
MapleCharacter chrController = this.getActiveController();
boolean updateController = false;

if (chrController != null && chrController.isAlive()) {
if (isCharacterPuppetInVicinity(chrController)) {
return;
}
} else {
updateController = true;
}

if (newController == null || !isCharacterPuppetInVicinity(newController)) {
MapleMonsterAggroCoordinator mmac = map.getAggroCoordinator();

List<Integer> puppetOwners = mmac.getPuppetAggroList();
List<Integer> toRemovePuppets = new LinkedList<>();

for (Integer cid : puppetOwners) {
MapleCharacter chr = map.getCharacterById(cid);

if (chr != null) {
if (isCharacterPuppetInVicinity(chr)) {
newController = chr;
break;
}
} else {
toRemovePuppets.add(cid);
}
}

for (Integer cid : toRemovePuppets) {
mmac.removePuppetAggro(cid);
}

if (newController == null) { // was a new controller found? (if not there's no puppet nearby)
if (updateController) {
aggroUpdateController();
}

return;
}
} else if (chrController == newController) {
this.aggroUpdatePuppetVisibility();
}

this.aggroSwitchController(newController, this.isControllerHasAggro());
}

/**
* Ensures controllability removal of the current player controller, and
* fetches for any player on the map to start controlling in place.
*
*/
public void aggroRedirectController() {
this.aggroRemoveController(); // don't care if new controller not found, at least remove current controller
this.aggroUpdateController();
}

/**
* Returns the current aggro status on the specified player, or null if the
* specified player is currently not this mob's controller.
*
*/
public Boolean aggroMoveLifeUpdate(MapleCharacter player) {
MapleCharacter chrController = getController();
if (chrController != null && player.getId() == chrController.getId()) {
boolean aggro = this.isControllerHasAggro();
if (aggro) {
this.setControllerKnowsAboutAggro(true);
}

return aggro;
} else {
return null;
}
}

/**
* Refreshes auto aggro for the player passed as parameter, does nothing if
* there is already an active controller for this mob.
*
*/
public void aggroAutoAggroUpdate(MapleCharacter player) {
MapleCharacter chrController = this.getActiveController();

if (chrController == null) {
this.aggroSwitchController(player, true);
} else if (chrController.getId() == player.getId()) {
this.setControllerHasAggro(true);
if (!YamlConfig.config.server.USE_AUTOAGGRO_NEARBY) { // thanks Lichtmager for noticing autoaggro not updating the player properly
aggroMonsterControl(player.getClient(), this, true);
}
}
}

/**
* Applied damage input for this mob, enough damage taken implies an aggro
* target update for the attacker shortly.
*
*/
public void aggroMonsterDamage(MapleCharacter attacker, int damage) {
MapleMonsterAggroCoordinator mmac = this.getMapAggroCoordinator();
mmac.addAggroDamage(this, attacker.getId(), damage);

MapleCharacter chrController = this.getController(); // aggro based on DPS rather than first-come-first-served, now live after suggestions thanks to MedicOP, Thora, Vcoc
if (chrController != attacker) {
if (this.getMapAggroCoordinator().isLeadingCharacterAggro(this, attacker)) {
this.aggroSwitchController(attacker, true);
} else {
this.setControllerHasAggro(true);
this.aggroUpdatePuppetVisibility();
}

/*
For some reason, some mobs loses aggro on controllers if other players also attacks them.
Maybe Nexon intended to interchange controllers at every attack...

else if (chrController != null) {
chrController.announce(MaplePacketCreator.stopControllingMonster(this.getObjectId()));
aggroMonsterControl(chrController.getClient(), this, true);
}
*/
} else {
this.setControllerHasAggro(true);
this.aggroUpdatePuppetVisibility();
}
}

private static void aggroMonsterControl(MapleClient c, MapleMonster mob, boolean immediateAggro) {
c.announce(MaplePacketCreator.controlMonster(mob, false, immediateAggro));
}

private void aggroRefreshPuppetVisibility(MapleCharacter chrController, MapleSummon puppet) {
// lame patch for client to redirect all aggro to the puppet

List<MapleMonster> puppetControlled = new LinkedList<>();
for (MapleMonster mob : chrController.getControlledMonsters()) {
if (mob.isPuppetInVicinity(puppet)) {
puppetControlled.add(mob);
}
}

for (MapleMonster mob : puppetControlled) {
chrController.announce(MaplePacketCreator.stopControllingMonster(mob.getObjectId()));
}
chrController.announce(MaplePacketCreator.removeSummon(puppet, false));

MapleClient c = chrController.getClient();
for (MapleMonster mob : puppetControlled) { // thanks BHB for noticing puppets disrupting mobstatuses for bowmans
aggroMonsterControl(c, mob, mob.isControllerKnowsAboutAggro());
}
chrController.announce(MaplePacketCreator.spawnSummon(puppet, false));
}

public void aggroUpdatePuppetVisibility() {
if (!availablePuppetUpdate) {
return;
}

availablePuppetUpdate = false;
Runnable r = new Runnable() {
@Override
public void run() {
try {
MapleCharacter chrController = MapleMonster.this.getActiveController();
if (chrController == null) {
return;
}

MapleStatEffect puppetEffect = chrController.getBuffEffect(MapleBuffStat.PUPPET);
if (puppetEffect != null) {
MapleSummon puppet = chrController.getSummonByKey(puppetEffect.getSourceId());

if (puppet != null && isPuppetInVicinity(puppet)) {
controllerHasPuppet = true;
aggroRefreshPuppetVisibility(chrController, puppet);
return;
}
}

if (controllerHasPuppet) {
controllerHasPuppet = false;

chrController.announce(MaplePacketCreator.stopControllingMonster(MapleMonster.this.getObjectId()));
aggroMonsterControl(chrController.getClient(), MapleMonster.this, MapleMonster.this.isControllerHasAggro());
}
} finally {
availablePuppetUpdate = true;
}
}
};

// had to schedule this since mob wouldn't stick to puppet aggro who knows why
OverallService service = (OverallService) this.getMap().getChannelServer().getServiceAccess(ChannelServices.OVERALL);
service.registerOverallAction(this.getMap().getId(), r, YamlConfig.config.server.UPDATE_INTERVAL);
}

/**
* Clears all applied damage input for this mob, doesn't refresh target
* aggro.
*
*/
public void aggroClearDamages() {
this.getMapAggroCoordinator().removeAggroEntries(this);
}

/**
* Clears this mob aggro on the current controller.
*
*/
public void aggroResetAggro() {
aggroUpdateLock.lock();
try {
this.setControllerHasAggro(false);
this.setControllerKnowsAboutAggro(false);
} finally {
aggroUpdateLock.unlock();
}
}

public final int getRemoveAfter() {
return stats.removeAfter();
}

public void dispose() {
if (monsterItemDrop != null) {
monsterItemDrop.cancel(false);
}

this.getMap().dismissRemoveAfter(this);
disposeLocks();
}

private void disposeLocks() {
LockCollector.getInstance().registerDisposeAction(new Runnable() {
@Override
public void run() {
emptyLocks();
}
});
}

private void emptyLocks() {
externalLock = externalLock.dispose();
monsterLock = monsterLock.dispose();
statiLock = statiLock.dispose();
animationLock = animationLock.dispose();
}
}


Here is my MapleMap.java
Spoiler:
/*
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 config.YamlConfig;
import constants.game.GameConstants;
import constants.inventory.ItemConstants;
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.HashSet;
import java.util.Set;
import java.util.LinkedList;
import java.util.List;
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.Lock;
import net.server.audit.locks.MonitoredLockType;
import net.server.audit.locks.MonitoredReadLock;
import net.server.audit.locks.MonitoredReentrantReadWriteLock;
import net.server.audit.locks.MonitoredWriteLock;
import net.server.audit.locks.factory.MonitoredReadLockFactory;
import net.server.audit.locks.factory.MonitoredReentrantLockFactory;
import net.server.audit.locks.factory.MonitoredWriteLockFactory;
import java.lang.ref.WeakReference;
import net.server.Server;
import net.server.coordinator.world.MapleMonsterAggroCoordinator;
import net.server.channel.Channel;
import net.server.services.type.ChannelServices;
import net.server.services.task.channel.FaceExpressionService;
import net.server.services.task.channel.MobMistService;
import net.server.services.task.channel.OverallService;
import net.server.world.MapleParty;
import net.server.world.World;
import scripting.map.MapScriptManager;
import server.MapleItemInformationProvider;
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 scripting.event.EventInstanceManager;
import server.life.MaplePlayerNPC;
import server.life.MonsterListener;
import server.partyquest.GuardianSpawnPoint;
import server.partyquest.MapleCarnivalFactory;
import server.partyquest.MapleCarnivalFactory.MCSkill;
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 static final Map<Integer, Pair<Integer, Integer>> dropBoundsCache = new HashMap<>(100);

private Map<Integer, MapleMapObject> mapobjects = new LinkedHashMap<>();
private Set<Integer> selfDestructives = new LinkedHashSet<>();
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 AtomicInteger droppedItemCount = new AtomicInteger(0);
private Collection<MapleCharacter> characters = new LinkedHashSet<>();
private Map<Integer, Set<Integer>> mapParty = new LinkedHashMap<>();
private Map<Integer, MaplePortal> portals = new HashMap<>();
private Map<Integer, Integer> backgroundTypes = new HashMap<>();
private Map<String, Integer> environment = new LinkedHashMap<>();
private Map<MapleMapItem, Long> droppedItems = new LinkedHashMap<>();
private LinkedList<WeakReference<MapleMapObject>> registeredDrops = new LinkedList<>();
private Map<MobLootEntry, Long> mobLootEntries = new HashMap(20);
private List<Runnable> statUpdateRunnables = new ArrayList(50);
private List<Rectangle> areas = new ArrayList<>();
private MapleFootholdTree footholds = null;
private Pair<Integer, Integer> xLimits; // caches the min and max x's with available footholds
private Rectangle mapArea = new Rectangle();
private int mapid;
private AtomicInteger runningOid = new AtomicInteger(1000000001);
private int returnMapId;
private int channel, world;
private int seats;
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 int timeLimit;
private long mapTimer;
private int decHP = 0;
private float recovery = 1.0f;
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 MapleMonsterAggroCoordinator aggroMonitor = null; // aggroMonitor activity in sync with itemMonitor
private ScheduledFuture<?> itemMonitor = null;
private ScheduledFuture<?> expireItemsTask = null;
private ScheduledFuture<?> mobSpawnLootTask = null;
private ScheduledFuture<?> characterStatUpdateTask = null;
private short itemMonitorTimeout;
private Pair<Integer, String> timeMob = null;
private short mobInterval = 5000;
private boolean allowSummons = true; // All maps should have this true at the beginning
private MapleCharacter mapOwner = null;
private long mapOwnerLastActivityTime = Long.MAX_VALUE;

// events
private boolean eventstarted = false, isMuted = false;
private MapleSnowball snowball0 = null;
private MapleSnowball snowball1 = null;
private MapleCoconut coconut;

//CPQ
private int maxMobs;
private int maxReactors;
private int deathCP;
private int timeDefault;
private int timeExpand;

//locks
private MonitoredReadLock chrRLock;
private MonitoredWriteLock chrWLock;
private MonitoredReadLock objectRLock;
private MonitoredWriteLock objectWLock;

private Lock lootLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.MAP_LOOT, true);

// due to the nature of loadMapFromWz (synchronized), sole function that calls 'generateMapDropRangeCache', this lock remains optional.
private static final Lock bndLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.MAP_BOUNDS, true);

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 MonitoredReentrantReadWriteLock chrLock = new MonitoredReentrantReadWriteLock(MonitoredLockType.MAP_CHRS, true);
chrRLock = MonitoredReadLockFactory.createLock(chrLock);
chrWLock = MonitoredWriteLockFactory.createLock(chrLock);

final MonitoredReentrantReadWriteLock objectLock = new MonitoredReentrantReadWriteLock(MonitoredLockType.MAP_OBJS, true);
objectRLock = MonitoredReadLockFactory.createLock(objectLock);
objectWLock = MonitoredWriteLockFactory.createLock(objectLock);

aggroMonitor = new MapleMonsterAggroCoordinator();
}

public void setEventInstance(EventInstanceManager eim) {
event = eim;
}

public EventInstanceManager getEventInstance() {
return event;
}

public Rectangle getMapArea() {
return mapArea;
}

public int getWorld() {
return world;
}

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 static double getRangedDistance() {
return(YamlConfig.config.server.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 Channel getChannelServer() {
return Server.getInstance().getWorld(world).getChannel(channel);
}

public World getWorldServer() {
return Server.getInstance().getWorld(world);
}

public MapleMap getReturnMap() {
if (returnMapId == 999999999) {
return this;
}
return getChannelServer().getMapFactory().getMap(returnMapId);
}

public int getReturnMapId() {
return returnMapId;
}

public MapleMap getForcedReturnMap() {
return getChannelServer().getMapFactory().getMap(forcedReturnMap);
}

public int getForcedReturnId() {
return forcedReturnMap;
}

public void setForcedReturnMap(int map) {
this.forcedReturnMap = map;
}

public int getTimeLimit() {
return timeLimit;
}

public void setTimeLimit(int timeLimit) {
this.timeLimit = timeLimit;
}

public int getTimeLeft() {
return (int) ((mapTimer - System.currentTimeMillis()) / 1000);
}

public void setReactorState() {
for (MapleMapObject o : getMapObjects()) {
if (o.getType() == MapleMapObjectType.REACTOR) {
if (((MapleReactor) o).getState() < 1) {
MapleReactor mr = (MapleReactor) o;
mr.lockReactor();
try {
mr.resetReactorActions(1);
broadcastMessage(MaplePacketCreator.triggerReactor((MapleReactor) o, 1));
} finally {
mr.unlockReactor();
}
}
}
}
}

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 getCurrentPartyId() {
for (MapleCharacter chr : this.getCharacters()) {
if (chr.getPartyId() != -1) {
return chr.getPartyId();
}
}
return -1;
}

public void addPlayerNPCMapObject(MaplePlayerNPC pnpcobject) {
objectWLock.lock();
try {
this.mapobjects.put(pnpcobject.getObjectId(), pnpcobject);
} finally {
objectWLock.unlock();
}
}

public void addMapObject(MapleMapObject mapobject) {
int curOID = getUsableOID();

objectWLock.lock();
try {
mapobject.setObjectId(curOID);
this.mapobjects.put(curOID, mapobject);
} finally {
objectWLock.unlock();
}
}

public void addSelfDestructive(MapleMonster mob) {
if (mob.getStats().selfDestruction() != null) {
this.selfDestructives.add(mob.getObjectId());
}
}

public boolean removeSelfDestructive(int mapobjectid) {
return this.selfDestructives.remove(mapobjectid);
}

private void spawnAndAddRangedMapObject(MapleMapObject mapobject, DelayedPacketCreation packetbakery) {
spawnAndAddRangedMapObject(mapobject, packetbakery, null);
}

private void spawnAndAddRangedMapObject(MapleMapObject mapobject, DelayedPacketCreation packetbakery, SpawnCondition condition) {
List<MapleCharacter> inRangeCharacters = new LinkedList<>();
int curOID = getUsableOID();

chrRLock.lock();
objectWLock.lock();
try {
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()) {
inRangeCharacters.add(chr);
chr.addVisibleMapObject(mapobject);
}
}
}
} finally {
objectWLock.unlock();
chrRLock.unlock();
}

for (MapleCharacter chr : inRangeCharacters) {
packetbakery.sendPackets(chr.getClient());
}
}

private void spawnRangedMapObject(MapleMapObject mapobject, DelayedPacketCreation packetbakery, SpawnCondition condition) {
List<MapleCharacter> inRangeCharacters = new LinkedList<>();

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()) {
inRangeCharacters.add(chr);
chr.addVisibleMapObject(mapobject);
}
}
}
} finally {
chrRLock.unlock();
}

for (MapleCharacter chr : inRangeCharacters) {
packetbakery.sendPackets(chr.getClient());
}
}

private int getUsableOID() {
objectRLock.lock();
try {
Integer curOid;

// clashes with playernpc on curOid >= 2147000000, developernpc uses >= 2147483000
do {
if ((curOid = runningOid.incrementAndGet()) >= 2147000000) {
runningOid.set(curOid = 1000000001);
}
} while (mapobjects.containsKey(curOid));

return curOid;
} finally {
objectRLock.unlock();
}
}

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 void generateMapDropRangeCache() {
bndLock.lock();
try {
Pair<Integer, Integer> bounds = dropBoundsCache.get(mapid);

if(bounds != null) {
xLimits = bounds;
} else {
// assuming MINIMAP always have an equal-greater picture representation of the map area (players won't walk beyond the area known by the minimap).
Point lp = new Point(mapArea.x, mapArea.y), rp = new Point(mapArea.x + mapArea.width, mapArea.y), fallback = new Point(mapArea.x + (mapArea.width / 2), mapArea.y);

lp = bsearchDropPos(lp, fallback); // approximated leftmost fh node position
rp = bsearchDropPos(rp, fallback); // approximated rightmost fh node position

xLimits = new Pair<>(lp.x + 14, rp.x - 14);
dropBoundsCache.put(mapid, xLimits);
}
} finally {
bndLock.unlock();
}
}

private Point bsearchDropPos(Point initial, Point fallback) {
Point res, dropPos = null;

int awayx = fallback.x;
int homex = initial.x;

int y = initial.y - 85;

do {
int distx = awayx - homex;
int dx = distx / 2;

int searchx = homex + dx;
if((res = calcPointBelow(new Point(searchx, y))) != null) {
awayx = searchx;
dropPos = res;
} else {
homex = searchx;
}
} while(Math.abs(homex - awayx) > 5);

return (dropPos != null) ? dropPos : fallback;
}

public Point calcDropPos(Point initial, Point fallback) {
if (initial.x < xLimits.left) {
initial.x = xLimits.left;
} else if(initial.x > xLimits.right) {
initial.x = xLimits.right;
}

Point ret = calcPointBelow(new Point(initial.x, initial.y - 85)); // actual drop ranges: default - 120, explosive - 360
if (ret == null) {
ret = bsearchDropPos(initial, fallback);
}

if(!mapArea.contains(ret)) { // found drop pos outside the map :O
return fallback;
}

return ret;
}

public boolean canDeployDoor(Point pos) {
Point toStep = calcPointBelow(pos);
return toStep != null && toStep.distance(pos) <= 42;
}

/**
* Fetches angle relative between spawn and door points where 3 O'Clock is 0
* and 12 O'Clock is 270 degrees
*
* @param spawnPoint
* @param doorPoint
* @return angle in degress from 0-360.
*/
private static double getAngle(Point doorPoint, Point spawnPoint) {
double dx = doorPoint.getX() - spawnPoint.getX();
// Minus to correct for coord re-mapping
double dy = -(doorPoint.getY() - spawnPoint.getY());

double inRads = Math.atan2(dy, dx);

// We need to map to coord system when 0 degree is at 3 O'clock, 270 at 12 O'clock
if (inRads < 0) {
inRads = Math.abs(inRads);
} else {
inRads = 2 * Math.PI - inRads;
}

return Math.toDegrees(inRads);
}

/**
* Converts angle in degrees to rounded cardinal coordinate.
*
* @param angle
* @return correspondent coordinate.
*/
public static String getRoundedCoordinate(double angle) {
String directions[] = {"E", "SE", "S", "SW", "W", "NW", "N", "NE", "E"};
return directions[ (int)Math.round(( ((double)angle % 360) / 45)) ];
}

public Pair<String, Integer> getDoorPositionStatus(Point pos) {
MaplePortal portal = findClosestPlayerSpawnpoint(pos);

double angle = getAngle(portal.getPosition(), pos);
double distn = pos.distanceSq(portal.getPosition());

if(distn <= 777777.7) {
return null;
}

distn = Math.sqrt(distn);
return new Pair<>(getRoundedCoordinate(angle), Integer.valueOf((int)distn));
}

private static void sortDropEntries(List<MonsterDropEntry> from, List<MonsterDropEntry> item, List<MonsterDropEntry> visibleQuest, List<MonsterDropEntry> otherQuest, MapleCharacter chr) {
MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();

for(MonsterDropEntry mde : from) {
if(!ii.isQuestItem(mde.itemId)) {
item.add(mde);
} else {
if(chr.needQuestItem(mde.questid, mde.itemId)) {
visibleQuest.add(mde);
} else {
otherQuest.add(mde);
}
}
}
}

private byte dropItemsFromMonsterOnMap(List<MonsterDropEntry> dropEntry, Point pos, byte d, int chRate, byte droptype, int mobpos, MapleCharacter chr, MapleMonster mob) {
if(dropEntry.isEmpty()) {
return d;
}

Collections.shuffle(dropEntry);

Item idrop;
MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();

for (final MonsterDropEntry de : dropEntry) {
float cardRate = chr.getCardRate(de.itemId);
int dropChance = (int) Math.min((float) de.chance * chRate * cardRate, Integer.MAX_VALUE);

if (Randomizer.nextInt(999999) < dropChance) {
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);
}
mesos = mesos * chr.getMesoRate();
if (mesos <= 0) {
mesos = Integer.MAX_VALUE;
}

spawnMesoDrop(mesos, 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++;
}
}

return d;
}

private byte dropGlobalItemsFromMonsterOnMap(List<MonsterGlobalDropEntry> globalEntry, Point pos, byte d, byte droptype, int mobpos, MapleCharacter chr, MapleMonster mob) {
Collections.shuffle(globalEntry);

Item idrop;
MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();

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++;
}
}
}

return d;
}

private void dropFromMonster(final MapleCharacter chr, final MapleMonster mob, final boolean useBaseRate) {
if (mob.dropsDisabled() || !dropsOn) {
return;
}

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 = !mob.isBoss() ? chr.getDropRate() : chr.getBossDropRate();
byte d = 1;
Point pos = new Point(0, mob.getPosition().y);

MonsterStatusEffect stati = mob.getStati(MonsterStatus.SHOWDOWN);
if (stati != null) {
chRate *= (stati.getStati().get(MonsterStatus.SHOWDOWN).doubleValue() / 100.0 + 1.0);
}

if (useBaseRate) {
chRate = 1;
}

final MapleMonsterInformationProvider mi = MapleMonsterInformationProvider.getInstance();
final List<MonsterGlobalDropEntry> globalEntry = mi.getRelevantGlobalDrops(this.getId());

final List<MonsterDropEntry> dropEntry = new ArrayList<>();
final List<MonsterDropEntry> visibleQuestEntry = new ArrayList<>();
final List<MonsterDropEntry> otherQuestEntry = new ArrayList<>();

List<MonsterDropEntry> lootEntry = YamlConfig.config.server.USE_SPAWN_RELEVANT_LOOT ? mob.retrieveRelevantDrops() : mi.retrieveEffectiveDrop(mob.getId());
sortDropEntries(lootEntry, dropEntry, visibleQuestEntry, otherQuestEntry, chr); // thanks Articuno, Limit, Rohenn for noticing quest loots not showing up in only-quest item drops scenario

if (lootEntry.isEmpty()) { // thanks resinate
return;
}

registerMobItemDrops(droptype, mobpos, chRate, pos, dropEntry, visibleQuestEntry, otherQuestEntry, globalEntry, chr, mob);
}

public void dropItemsFromMonster(List<MonsterDropEntry> list, final MapleCharacter chr, final MapleMonster mob) {
if (mob.dropsDisabled() || !dropsOn) {
return;
}

final byte droptype = (byte) (chr.getParty() != null ? 1 : 0);
final int mobpos = mob.getPosition().x;
int chRate = 1000000; // guaranteed item drop
byte d = 1;
Point pos = new Point(0, mob.getPosition().y);

dropItemsFromMonsterOnMap(list, pos, d, chRate, droptype, mobpos, chr, mob);
}

public void dropFromFriendlyMonster(final MapleCharacter chr, final MapleMonster mob) {
dropFromMonster(chr, mob, true);
}

public void dropFromReactor(final MapleCharacter chr, final MapleReactor reactor, Item drop, Point dropPos, short questid) {
spawnDrop(drop, this.calcDropPos(dropPos, reactor.getPosition()), reactor, chr, (byte)(chr.getParty() != null ? 1 : 0), questid);
}

private void stopItemMonitor() {
itemMonitor.cancel(false);
itemMonitor = null;

expireItemsTask.cancel(false);
expireItemsTask = null;

if(YamlConfig.config.server.USE_SPAWN_LOOT_ON_ANIMATION) {
mobSpawnLootTask.cancel(false);
mobSpawnLootTask = null;
}

characterStatUpdateTask.cancel(false);
characterStatUpdateTask = null;
}

private void cleanItemMonitor() {
objectWLock.lock();
try {
registeredDrops.removeAll(Collections.singleton(null));
} finally {
objectWLock.unlock();
}
}

private void startItemMonitor() {
chrWLock.lock();
try {
if (itemMonitor != null) {
return;
}

itemMonitor = TimerManager.getInstance().register(new Runnable() {
@Override
public void run() {
chrWLock.lock();
try {
if (characters.isEmpty()) {
if(itemMonitorTimeout == 0) {
if(itemMonitor != null) {
stopItemMonitor();
aggroMonitor.stopAggroCoordinator();
}

return;
} else {
itemMonitorTimeout--;
}
} else {
itemMonitorTimeout = 1;
}
} finally {
chrWLock.unlock();
}

boolean tryClean;
objectRLock.lock();
try {
tryClean = registeredDrops.size() > 70;
} finally {
objectRLock.unlock();
}

if (tryClean) {
cleanItemMonitor();
}
}
}, YamlConfig.config.server.ITEM_MONITOR_TIME, YamlConfig.config.server.ITEM_MONITOR_TIME);

expireItemsTask = TimerManager.getInstance().register(new Runnable() {
@Override
public void run() {
makeDisappearExpiredItemDrops();
}
}, YamlConfig.config.server.ITEM_EXPIRE_CHECK, YamlConfig.config.server.ITEM_EXPIRE_CHECK);

if(YamlConfig.config.server.USE_SPAWN_LOOT_ON_ANIMATION) {
lootLock.lock();
try {
mobLootEntries.clear();
} finally {
lootLock.unlock();
}

mobSpawnLootTask = TimerManager.getInstance().register(new Runnable() {
@Override
public void run() {
spawnMobItemDrops();
}
}, 200, 200);
}

characterStatUpdateTask = TimerManager.getInstance().register(new Runnable() {
@Override
public void run() {
runCharacterStatUpdate();
}
}, 200, 200);

itemMonitorTimeout = 1;
} finally {
chrWLock.unlock();
}
}

private boolean hasItemMonitor() {
chrRLock.lock();
try {
return itemMonitor != null;
} finally {
chrRLock.unlock();
}
}

public int getDroppedItemCount() {
return droppedItemCount.get();
}

private void instantiateItemDrop(MapleMapItem mdrop) {
if(droppedItemCount.get() >= YamlConfig.config.server.ITEM_LIMIT_ON_MAP) {
MapleMapObject mapobj;

do {
mapobj = null;

objectWLock.lock();
try {
while (mapobj == null) {
if (registeredDrops.isEmpty()) {
break;
}
mapobj = registeredDrops.remove(0).get();
}
} finally {
objectWLock.unlock();
}
} while (!makeDisappearItemFromMap(mapobj));
}

objectWLock.lock();
try {
registerItemDrop(mdrop);
registeredDrops.add(new WeakReference<>((MapleMapObject) mdrop));
} finally {
objectWLock.unlock();
}

droppedItemCount.incrementAndGet();
}

private void registerItemDrop(MapleMapItem mdrop) {
droppedItems.put(mdrop, !everlast ? Server.getInstance().getCurrentTime() + YamlConfig.config.server.ITEM_EXPIRE_TIME : Long.MAX_VALUE);
}

private void unregisterItemDrop(MapleMapItem mdrop) {
objectWLock.lock();
try {
droppedItems.remove(mdrop);
} finally {
objectWLock.unlock();
}
}

private void makeDisappearExpiredItemDrops() {
List<MapleMapItem> toDisappear = new LinkedList<>();

objectRLock.lock();
try {
long timeNow = Server.getInstance().getCurrentTime();

for(Entry<MapleMapItem, Long> it : droppedItems.entrySet()) {
if(it.getValue() < timeNow) {
toDisappear.add(it.getKey());
}
}
} finally {
objectRLock.unlock();
}

for(MapleMapItem mmi : toDisappear) {
makeDisappearItemFromMap(mmi);
}

objectWLock.lock();
try {
for(MapleMapItem mmi : toDisappear) {
droppedItems.remove(mmi);
}
} finally {
objectWLock.unlock();
}
}

private void registerMobItemDrops(byte droptype, int mobpos, int chRate, Point pos, List<MonsterDropEntry> dropEntry, List<MonsterDropEntry> visibleQuestEntry, List<MonsterDropEntry> otherQuestEntry, List<MonsterGlobalDropEntry> globalEntry, MapleCharacter chr, MapleMonster mob) {
MobLootEntry mle = new MobLootEntry(droptype, mobpos, chRate, pos, dropEntry, visibleQuestEntry, otherQuestEntry, globalEntry, chr, mob);

if(YamlConfig.config.server.USE_SPAWN_LOOT_ON_ANIMATION) {
int animationTime = mob.getAnimationTime("die1");

lootLock.lock();
try {
long timeNow = Server.getInstance().getCurrentTime();
mobLootEntries.put(mle, timeNow + ((long)(0.42 * animationTime)));
} finally {
lootLock.unlock();
}
} else {
mle.run();
}
}

private void spawnMobItemDrops() {
Set<Entry<MobLootEntry, Long>> mleList;

lootLock.lock();
try {
mleList = new HashSet<>(mobLootEntries.entrySet());
} finally {
lootLock.unlock();
}

long timeNow = Server.getInstance().getCurrentTime();
List<MobLootEntry> toRemove = new LinkedList<>();
for(Entry<MobLootEntry, Long> mlee : mleList) {
if(mlee.getValue() < timeNow) {
toRemove.add(mlee.getKey());
}
}

if(!toRemove.isEmpty()) {
List<MobLootEntry> toSpawnLoot = new LinkedList<>();

lootLock.lock();
try {
for(MobLootEntry mle : toRemove) {
Long mler = mobLootEntries.remove(mle);
if(mler != null) {
toSpawnLoot.add(mle);
}
}
} finally {
lootLock.unlock();
}

for(MobLootEntry mle : toSpawnLoot) {
mle.run();
}
}
}

private List<MapleMapItem> getDroppedItems() {
objectRLock.lock();
try {
return new LinkedList<>(droppedItems.keySet());
} finally {
objectRLock.unlock();
}
}

public int getDroppedItemsCountById(int itemid) {
int count = 0;
for (MapleMapItem mmi : getDroppedItems()) {
if (mmi.getItemId() == itemid) {
count++;
}
}

return count;
}

public void pickItemDrop(byte[] pickupPacket, MapleMapItem mdrop) { // mdrop must be already locked and not-pickedup checked at this point
broadcastMessage(pickupPacket, mdrop.getPosition());

droppedItemCount.decrementAndGet();
this.removeMapObject(mdrop);
mdrop.setPickedUp(true);
unregisterItemDrop(mdrop);
}

public List<MapleMapItem> updatePlayerItemDropsToParty(int partyid, int charid, List<MapleCharacter> partyMembers, MapleCharacter partyLeaver) {
List<MapleMapItem> partyDrops = new LinkedList<>();

for (MapleMapItem mdrop : getDroppedItems()) {
if (mdrop.getOwnerId() == charid) {
mdrop.lockItem();
try {
if (mdrop.isPickedUp()) {
continue;
}

mdrop.setPartyOwnerId(partyid);

byte[] removePacket = MaplePacketCreator.silentRemoveItemFromMap(mdrop.getObjectId());
byte[] updatePacket = MaplePacketCreator.updateMapItemObject(mdrop, partyLeaver == null);

for (MapleCharacter mc : partyMembers) {
if (this.equals(mc.getMap())) {
mc.announce(removePacket);

if (mc.needQuestItem(mdrop.getQuest(), mdrop.getItemId())) {
mc.announce(updatePacket);
}
}
}

if (partyLeaver != null) {
if (this.equals(partyLeaver.getMap())) {
partyLeaver.announce(removePacket);

if (partyLeaver.needQuestItem(mdrop.getQuest(), mdrop.getItemId())) {
partyLeaver.announce(MaplePacketCreator.updateMapItemObject(mdrop, true));
}
}
}
} finally {
mdrop.unlockItem();
}
} else if (partyid != -1 && mdrop.getPartyOwnerId() == partyid) {
partyDrops.add(mdrop);
}
}

return partyDrops;
}

public void updatePartyItemDropsToNewcomer(MapleCharacter newcomer, List<MapleMapItem> partyItems) {
for (MapleMapItem mdrop : partyItems) {
mdrop.lockItem();
try {
if (mdrop.isPickedUp()) {
continue;
}

byte[] removePacket = MaplePacketCreator.silentRemoveItemFromMap(mdrop.getObjectId());
byte[] updatePacket = MaplePacketCreator.updateMapItemObject(mdrop, true);

if (newcomer != null) {
if (this.equals(newcomer.getMap())) {
newcomer.announce(removePacket);

if (newcomer.needQuestItem(mdrop.getQuest(), mdrop.getItemId())) {
newcomer.announce(updatePacket);
}
}
}
} finally {
mdrop.unlockItem();
}
}
}

private void spawnDrop(final Item idrop, final Point dropPos, final MapleMapObject dropper, final MapleCharacter chr, final byte droptype, final short questid) {
final MapleMapItem mdrop = new MapleMapItem(idrop, dropPos, dropper, chr, chr.getClient(), droptype, false, questid);
mdrop.setDropTime(Server.getInstance().getCurrentTime());
spawnAndAddRangedMapObject(mdrop, new DelayedPacketCreation() {
@Override
public void sendPackets(MapleClient c) {
MapleCharacter chr = c.getPlayer();

if (chr.needQuestItem(questid, idrop.getItemId())) {
mdrop.lockItem();
try {
c.announce(MaplePacketCreator.dropItemFromMapObject(chr, mdrop, dropper.getPosition(), dropPos, (byte) 1));
} finally {
mdrop.unlockItem();
}
}
}
}, null);

instantiateItemDrop(mdrop);
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, owner.getClient(), droptype, playerDrop);
mdrop.setDropTime(Server.getInstance().getCurrentTime());

spawnAndAddRangedMapObject(mdrop, new DelayedPacketCreation() {
@Override
public void sendPackets(MapleClient c) {
mdrop.lockItem();
try {
c.announce(MaplePacketCreator.dropItemFromMapObject(c.getPlayer(), mdrop, dropper.getPosition(), droppos, (byte) 1));
} finally {
mdrop.unlockItem();
}
}
}, null);

instantiateItemDrop(mdrop);
}

public final void disappearingItemDrop(final MapleMapObject dropper, final MapleCharacter owner, final Item item, final Point pos) {
final Point droppos = calcDropPos(pos, pos);
final MapleMapItem mdrop = new MapleMapItem(item, droppos, dropper, owner, owner.getClient(), (byte) 1, false);

mdrop.lockItem();
try {
broadcastItemDropMessage(mdrop, dropper.getPosition(), droppos, (byte) 3, mdrop.getPosition());
} finally {
mdrop.unlockItem();
}
}

public final void disappearingMesoDrop(final int meso, final MapleMapObject dropper, final MapleCharacter owner, final Point pos) {
final Point droppos = calcDropPos(pos, pos);
final MapleMapItem mdrop = new MapleMapItem(meso, droppos, dropper, owner, owner.getClient(), (byte) 1, false);

mdrop.lockItem();
try {
broadcastItemDropMessage(mdrop, dropper.getPosition(), droppos, (byte) 3, mdrop.getPosition());
} finally {
mdrop.unlockItem();
}
}

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) {
return countMonster(id, id);
}

public int countMonster(int minid, int maxid) {
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() >= minid && mob.getId() <= maxid) {
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 final List<MapleMapObject> getMonsters() {
return getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.MONSTER));
}

public final List<MapleReactor> getAllReactors() {
List<MapleReactor> list = new LinkedList<>();
for (MapleMapObject mmo : getReactors()) {
list.add((MapleReactor) mmo);
}

return list;
}

public final List<MapleMonster> getAllMonsters() {
List<MapleMonster> list = new LinkedList<>();
for (MapleMapObject mmo : getMonsters()) {
list.add((MapleMonster) mmo);
}

return list;
}

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;
chrRLock.lock();
try {
character = new ArrayList<>(characters);
} finally {
chrRLock.unlock();
}

return character;
}

public Map<Integer, MapleCharacter> getMapAllPlayers() {
Map<Integer, MapleCharacter> pchars = new HashMap<>();
for (MapleCharacter chr : this.getAllPlayers()) {
pchars.put(chr.getId(), chr);
}

return pchars;
}

public List<MapleCharacter> getPlayersInRange(Rectangle box) {
List<MapleCharacter> character = new LinkedList<>();
chrRLock.lock();
try {
for (MapleCharacter chr : characters) {
if (box.contains(chr.getPosition())) {
character.add(chr);
}
}
} finally {
chrRLock.unlock();
}

return character;
}

public int countAlivePlayers() {
int count = 0;

for(MapleCharacter mc: getAllPlayers()) {
if (mc.isAlive()) {
count++;
}
}

return count;
}

public int countBosses() {
int count = 0;

for(MapleMonster mob: getAllMonsters()) {
if (mob.isBoss()) {
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 = monster.damage(chr, damage, false);

selfDestruction selfDestr = monster.getStats().selfDestruction();
if (selfDestr != null && selfDestr.getHp() > -1) {// should work ;p
if (monster.getHp() <= selfDestr.getHp()) {
killMonster(monster, chr, true, selfDestr.getAction());
return true;
}
}
if (killed) {
killMonster(monster, chr, true);
}
return true;
}
return false;
}

public void broadcastBalrogVictory(String leaderName) {
getWorldServer().dropMessage(6, "[Victory] " + leaderName + "'s party has successfully defeated the Balrog! Praise to them, they finished with " + countAlivePlayers() + " players alive.");
}

public void broadcastHorntailVictory() {
getWorldServer().dropMessage(6, "[Victory] To the crew that have finally conquered Horned Tail after numerous attempts, I salute thee! You are the true heroes of Leafre!!");
}

public void broadcastZakumVictory() {
getWorldServer().dropMessage(6, "[Victory] At last, the tree of evil that for so long overwhelmed Ossyria has fallen. To the crew that managed to finally conquer Zakum, after numerous attempts, victory! You are the true heroes of Ossyria!!");
}

public void broadcastPinkBeanVictory(int channel) {
getWorldServer().dropMessage(6, "[Victory] In a swift stroke of sorts, the crew that has attempted Pink Bean at channel " + channel + " has ultimately defeated it. The Temple of Time shines radiantly once again, the day finally coming back, as the crew that managed to finally conquer it returns victoriously from the battlefield!!");
}

private boolean removeKilledMonsterObject(MapleMonster monster) {
monster.lockMonster();
try {
if(monster.getHp() < 0) {
return false;
}

spawnedMonstersOnMap.decrementAndGet();
removeMapObject(monster);
monster.disposeMapObject();
if (monster.hasBossHPBar()) { // thanks resinate for noticing boss HPbar not clearing after mob defeat in certain scenarios
broadcastBossHpMessage(monster, monster.hashCode(), monster.makeBossHPBarPacket(), monster.getPosition());
}

return true;
} finally {
monster.unlockMonster();
}
}

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) {
if (removeKilledMonsterObject(monster)) {
monster.dispatchMonsterKilled(false);
broadcastMessage(MaplePacketCreator.killMonster(monster.getObjectId(), animation), monster.getPosition());
monster.aggroSwitchController(null, false);
}
} else {
if (removeKilledMonsterObject(monster)) {
try {
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()) {
}
}*/

if (monster.getCP() > 0 && chr.getMap().isCPQMap()) {
chr.gainCP(monster.getCP());
}

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() >= 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) {
MapleMap map = chr.getMap();

for (MapleMapObject object : objects) {
MapleMonster mons = map.getMonsterByOid(object.getObjectId());
if (mons != null) {
if (mons.getId() == 8800000) {
makeMonsterReal(mons);
break;
}
}
}
}
}

MapleCharacter dropOwner = monster.killBy(chr);
if (withDrops && !monster.dropsDisabled()) {
if (dropOwner == null) {
dropOwner = chr;
}
dropFromMonster(dropOwner, monster, false);
}

if (monster.hasBossHPBar()) {
for(MapleCharacter mc : this.getAllPlayers()) {
if(mc.getTargetHpBarHash() == monster.hashCode()) {
mc.resetPlayerAggro();
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally { // thanks resinate for pointing out a memory leak possibly from an exception thrown
monster.dispatchMonsterKilled(true);
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 mobId) {
MapleCharacter chr = (MapleCharacter) getPlayers().get(0);
List<MapleMonster> mobList = getAllMonsters();

for (MapleMonster mob : mobList) {
if (mob.getId() == mobId) {
this.killMonster(mob, chr, false);
}
}
}

public void killMonsterWithDrops(int mobId) {
Map<Integer, MapleCharacter> mapChars = this.getMapPlayers();

if(!mapChars.isEmpty()) {
MapleCharacter defaultChr = mapChars.entrySet().iterator().next().getValue();
List<MapleMonster> mobList = getAllMonsters();

for (MapleMonster mob : mobList) {
if (mob.getId() == mobId) {
MapleCharacter chr = mapChars.get(mob.getHighestDamagerId());
if (chr == null) {
chr = defaultChr;
}

this.killMonster(mob, chr, true);
}
}
}
}

public void softKillAllMonsters() {
closeMapSpawnPoints();

for (MapleMapObject monstermo : getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.MONSTER))) {
MapleMonster monster = (MapleMonster) monstermo;
if (monster.getStats().isFriendly()) {
continue;
}

if(removeKilledMonsterObject(monster)) {
monster.dispatchMonsterKilled(false);
}
}
}

public void killAllMonstersNotFriendly() {
closeMapSpawnPoints();

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() {
closeMapSpawnPoints();

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);

if (reactor != null) {
if (reactor.destroy()) {
removeMapObject(reactor);
}
}
}

public void resetReactors() {
List<MapleReactor> list = new ArrayList<>();

objectRLock.lock();
try {
for (MapleMapObject o : mapobjects.values()) {
if (o.getType() == MapleMapObjectType.REACTOR) {
final MapleReactor r = ((MapleReactor) o);
list.add(r);
}
}
} finally {
objectRLock.unlock();
}

resetReactors(list);
}

public final void resetReactors(List<MapleReactor> list) {
for (MapleReactor r : list) {
if (r.forceDelayedRespawn()) { // thanks Conrad for suggesting reactor with delay respawning immediately
continue;
}

r.lockReactor();
try {
r.resetReactorActions(0);
r.setAlive(true);
broadcastMessage(MaplePacketCreator.triggerReactor(r, 0));
} finally {
r.unlockReactor();
}
}
}

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> targets = new LinkedList<>();

objectRLock.lock();
try {
for (Object ob : list) {
if(ob instanceof MapleMapObject) {
MapleMapObject mmo = (MapleMapObject) ob;

if(mapobjects.containsValue(mmo) && mmo.getType() == MapleMapObjectType.REACTOR) {
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));
}
}

private Map<Integer, MapleMapObject> getCopyMapObjects() {
objectRLock.lock();
try {
return new HashMap<>(mapobjects);
} finally {
objectRLock.unlock();
}
}

public List<MapleMapObject> getMapObjects() {
objectRLock.lock();
try {
return new LinkedList(mapobjects.values());
} finally {
objectRLock.unlock();
}
}

public MapleNPC getNPCById(int id) {
for (MapleMapObject obj : getMapObjects()) {
if (obj.getType() == MapleMapObjectType.NPC) {
MapleNPC npc = (MapleNPC) obj;
if (npc.getId() == id) {
return npc;
}
}
}

return null;
}

public boolean containsNPC(int npcid) {
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 void destroyNPC(int npcid) { // assumption: there's at most one of the same NPC in a map.
List<MapleMapObject> npcs = getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.NPC));

chrRLock.lock();
objectWLock.lock();
try {
for (MapleMapObject obj : npcs) {
if (((MapleNPC) obj).getId() == npcid) {
broadcastMessage(MaplePacketCreator.removeNPCController(obj.getObjectId()));
broadcastMessage(MaplePacketCreator.removeNPC(obj.getObjectId()));

this.mapobjects.remove(Integer.valueOf(obj.getObjectId()));
}
}
} finally {
objectWLock.unlock();
chrRLock.unlock();
}
}

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 List<MapleReactor> getReactorsByIdRange(final int first, final int last) {
List<MapleReactor> list = new LinkedList<>();

objectRLock.lock();
try {
for (MapleMapObject obj : mapobjects.values()) {
if (obj.getType() == MapleMapObjectType.REACTOR) {
MapleReactor mr = (MapleReactor) obj;

if (mr.getId() >= first && mr.getId() <= last) {
list.add(mr);
}
}
}

return list;
} 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);
}

private void monsterItemDrop(final MapleMonster m, long delay) {
m.dropFromFriendlyMonster(delay);
}

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 - 14); // Using -14 fixes spawning pets causing a lot of issues.
spos = calcPointBelow(spos);
spos.y--;//shouldn't be null!
return spos;
}

public Point getPointBelow(Point pos) {
return calcPointBelow(pos);
}

public void spawnRevives(final MapleMonster monster) {
monster.setMap(this);
if (getEventInstance() != null) {
getEventInstance().registerMonster(monster);
}

spawnAndAddRangedMapObject(monster, new DelayedPacketCreation() {
@Override
public void sendPackets(MapleClient c) {
c.announce(MaplePacketCreator.spawnMonster(monster, false));
}
});

monster.aggroUpdateController();
updateBossSpawn(monster);

spawnedMonstersOnMap.incrementAndGet();
addSelfDestructive(monster);
applyRemoveAfter(monster);
}

private void applyRemoveAfter(final MapleMonster monster) {
final selfDestruction selfDestruction = monster.getStats().selfDestruction();
if (monster.getStats().removeAfter() > 0 || selfDestruction != null && selfDestruction.getHp() < 0) {
Runnable removeAfterAction;

if (selfDestruction == null) {
removeAfterAction = new Runnable() {
@Override
public void run() {
killMonster(monster, null, false);
}
};

registerMapSchedule(removeAfterAction, monster.getStats().removeAfter() * 1000);
} else {
removeAfterAction = new Runnable() {
@Override
public void run() {
killMonster(monster, null, false, selfDestruction.getAction());
}
};

registerMapSchedule(removeAfterAction, selfDestruction.removeAfter() * 1000);
}

monster.pushRemoveAfterAction(removeAfterAction);
}
}

public void dismissRemoveAfter(final MapleMonster monster) {
Runnable removeAfterAction = monster.popRemoveAfterAction();
if (removeAfterAction != null) {
OverallService service = (OverallService) this.getChannelServer().getServiceAccess(ChannelServices.OVERALL);
service.forceRunOverallAction(mapid, removeAfterAction);
}
}

private List<SpawnPoint> getMonsterSpawn() {
synchronized (monsterSpawn) {
return new ArrayList<>(monsterSpawn);
}
}

private List<SpawnPoint> getAllMonsterSpawn() {
synchronized (allMonsterSpawn) {
return new ArrayList<>(allMonsterSpawn);
}
}

public void spawnAllMonsterIdFromMapSpawnList(int id) {
spawnAllMonsterIdFromMapSpawnList(id, 1, false);
}

public void spawnAllMonsterIdFromMapSpawnList(int id, int difficulty, boolean isPq) {
for(SpawnPoint sp: getAllMonsterSpawn()) {
if(sp.getMonsterId() == id && sp.shouldForceSpawn()) {
spawnMonster(sp.getMonster(), difficulty, isPq);
}
}
}

public void spawnAllMonstersFromMapSpawnList() {
spawnAllMonstersFromMapSpawnList(1, false);
}

public void spawnAllMonstersFromMapSpawnList(int difficulty, boolean isPq) {
for(SpawnPoint sp: getAllMonsterSpawn()) {
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);

monster.aggroUpdateController();
updateBossSpawn(monster);

if ((monster.getTeam() == 1 || monster.getTeam() == 0) && (isCPQMap() || isCPQMap2())) {
List<MCSkill> teamS = null;
if (monster.getTeam() == 0) {
teamS = redTeamBuffs;
} else if (monster.getTeam() == 1) {
teamS = blueTeamBuffs;
}
if (teamS != null) {
for (MCSkill skil : teamS) {
if (skil != null) {
skil.getSkill().applyEffect(null, monster, false, null);
}
}
}
}

if (monster.getDropPeriodTime() > 0) { //9300102 - Watchhog, 9300061 - Moon Bunny (HPQ), 9300093 - Tylus
if (monster.getId() == 9300102) {
monsterItemDrop(monster, monster.getDropPeriodTime());
} else if (monster.getId() == 9300061) {
monsterItemDrop(monster, monster.getDropPeriodTime() / 3);
} else if (monster.getId() == 9300093) {
monsterItemDrop(monster, monster.getDropPeriodTime());
} else if (monster.getId() == 9400326 || monster.getId() == 9400331 || monster.getId() == 9400336) {
monsterItemDrop(monster, monster.getDropPeriodTime());
} else {
FilePrinter.printError(FilePrinter.UNHANDLED_EVENT, "UNCODED TIMED MOB DETECTED: " + monster.getId());
}
}

spawnedMonstersOnMap.incrementAndGet();
addSelfDestructive(monster);
applyRemoveAfter(monster); // thanks LightRyuzaki for pointing issues with spawned CWKPQ mobs not applying this
}

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;
}

if (getEventInstance() != null) {
getEventInstance().registerMonster(monster);
}

spos.y--;
monster.setPosition(spos);
monster.setSpawnEffect(effect);

spawnAndAddRangedMapObject(monster, new DelayedPacketCreation() {
@Override
public void sendPackets(MapleClient c) {
c.announce(MaplePacketCreator.spawnMonster(monster, true, effect));
}
});

monster.aggroUpdateController();
updateBossSpawn(monster);

spawnedMonstersOnMap.incrementAndGet();
addSelfDestructive(monster);
applyRemoveAfter(monster);
}

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();
addSelfDestructive(monster);
}

public void makeMonsterReal(final MapleMonster monster) {
monster.setFake(false);
broadcastMessage(MaplePacketCreator.makeMonsterReal(monster));
monster.aggroUpdateController();
updateBossSpawn(monster);
}

public void spawnReactor(final MapleReactor reactor) {
reactor.setMap(this);
spawnAndAddRangedMapObject(reactor, new DelayedPacketCreation() {
@Override
public void sendPackets(MapleClient c) {
c.announce(reactor.makeSpawnData());
}
});
}

public void spawnDoor(final MapleDoorObject door) {
spawnAndAddRangedMapObject(door, new DelayedPacketCreation() {
@Override
public void sendPackets(MapleClient c) {
MapleCharacter chr = c.getPlayer();
if (chr != null) {
door.sendSpawnData(c, false);
chr.addVisibleMapObject(door);
}
}
}, new SpawnCondition() {
@Override
public boolean canSpawn(MapleCharacter chr) {
return chr.getMapId() == door.getFrom().getId();
}
});
}

public MaplePortal getDoorPortal(int doorid) {
MaplePortal doorPortal = portals.get(0x80 + doorid);
if(doorPortal == null) {
FilePrinter.printError(FilePrinter.EXCEPTION, "[Door] " + mapName + "(" + mapid + ") does not contain door portalid " + doorid);
return portals.get(0x80);
}

return doorPortal;
}

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;
}

Runnable mistSchedule = new Runnable() {
@Override
public void run() {
removeMapObject(mist);
if (poisonSchedule != null) {
poisonSchedule.cancel(false);
}
broadcastMessage(mist.makeDestroyData());
}
};

MobMistService service = (MobMistService) this.getChannelServer().getServiceAccess(ChannelServices.MOB_MIST);
service.registerMobMistCancelAction(mapid, mistSchedule, duration);
}

public void spawnKite(final MapleKite kite) {
addMapObject(kite);
broadcastMessage(kite.makeSpawnData());

Runnable expireKite = new Runnable() {
@Override
public void run() {
removeMapObject(kite);
broadcastMessage(kite.makeDestroyData());
}
};

getWorldServer().registerTimedMapObject(expireKite, YamlConfig.config.server.KITE_EXPIRE_TIME);
}

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) {
if (FieldLimit.DROP_LIMIT.check(this.getFieldLimit())) { // thanks Conrad for noticing some maps shouldn't have loots available
this.disappearingItemDrop(dropper, owner, item, pos);
return;
}

final Point droppos = calcDropPos(pos, pos);
final MapleMapItem mdrop = new MapleMapItem(item, droppos, dropper, owner, owner.getClient(), dropType, playerDrop);
mdrop.setDropTime(Server.getInstance().getCurrentTime());

spawnAndAddRangedMapObject(mdrop, new DelayedPacketCreation() {
@Override
public void sendPackets(MapleClient c) {
mdrop.lockItem();
try {
c.announce(MaplePacketCreator.dropItemFromMapObject(c.getPlayer(), mdrop, dropper.getPosition(), droppos, (byte) 1));
} finally {
mdrop.unlockItem();
}
}
}, null);

mdrop.lockItem();
try {
broadcastItemDropMessage(mdrop, dropper.getPosition(), droppos, (byte) 0);
} finally {
mdrop.unlockItem();
}

instantiateItemDrop(mdrop);
activateItemReactors(mdrop, 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 (ItemConstants.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 registerMapSchedule(Runnable r, long delay) {
OverallService service = (OverallService) this.getChannelServer().getServiceAccess(ChannelServices.OVERALL);
service.registerOverallAction(mapid, r, delay);
}

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())) {
registerMapSchedule(new ActivateItemReactor(drop, react, c), 5000);
break;
}
}
}
}
}

public void searchItemReactors(final MapleReactor react) {
if (react.getReactorType() == 100) {
Pair<Integer, Integer> reactProp = react.getReactItem(react.getEventState());
int reactItem = reactProp.getLeft(), reactQty = reactProp.getRight();
Rectangle reactArea = react.getArea();

List<MapleMapItem> list;
objectRLock.lock();
try {
list = new ArrayList<>(droppedItems.keySet());
} finally {
objectRLock.unlock();
}

for(final MapleMapItem drop : list) {
drop.lockItem();
try {
if(!drop.isPickedUp()) {
final Item item = drop.getItem();

if (item != null && reactItem == item.getItemId() && reactQty == item.getQuantity()) {
if (reactArea.contains(drop.getPosition())) {
MapleClient owner = drop.getOwnerClient();
if(owner != null) {
registerMapSchedule(new ActivateItemReactor(drop, react, owner), 5000);
}
}
}
}
} finally {
drop.unlockItem();
}
}
}
}

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());

Runnable r = new Runnable() {
@Override
public void run() {
broadcastMessage(mapEffect.makeDestroyData());
mapEffect = null;
}
};

registerMapSchedule(r, time);
}

public MapleCharacter getAnyCharacterFromParty(int partyid) {
for (MapleCharacter chr : this.getAllPlayers()) {
if (chr.getPartyId() == partyid) {
return chr;
}
}

return null;
}

private void addPartyMemberInternal(MapleCharacter chr, int partyid) {
if (partyid == -1) {
return;
}

Set<Integer> partyEntry = mapParty.get(partyid);
if(partyEntry == null) {
partyEntry = new LinkedHashSet<>();
partyEntry.add(chr.getId());

mapParty.put(partyid, partyEntry);
} else {
partyEntry.add(chr.getId());
}
}

private void removePartyMemberInternal(MapleCharacter chr, int partyid) {
if (partyid == -1) {
return;
}

Set<Integer> partyEntry = mapParty.get(partyid);
if(partyEntry != null) {
if (partyEntry.size() > 1) {
partyEntry.remove(chr.getId());
} else {
mapParty.remove(partyid);
}
}
}

public void addPartyMember(MapleCharacter chr, int partyid) {
chrWLock.lock();
try {
addPartyMemberInternal(chr, partyid);
} finally {
chrWLock.unlock();
}
}

public void removePartyMember(MapleCharacter chr, int partyid) {
chrWLock.lock();
try {
removePartyMemberInternal(chr, partyid);
} finally {
chrWLock.unlock();
}
}

public void removeParty(int partyid) {
chrWLock.lock();
try {
mapParty.remove(partyid);
} finally {
chrWLock.unlock();
}
}

public void addPlayer(final MapleCharacter chr) {
int chrSize;
MapleParty party = chr.getParty();
chrWLock.lock();
try {
characters.add(chr);
chrSize = characters.size();

if (party != null && party.getMemberById(chr.getId()) != null) {
addPartyMemberInternal(chr, party.getId());
}
itemMonitorTimeout = 1;
} finally {
chrWLock.unlock();
}

chr.setMapId(mapid);
chr.updateActiveEffects();

MapScriptManager msm = MapScriptManager.getInstance();
if (chrSize == 1) {
if(!hasItemMonitor()) {
startItemMonitor();
aggroMonitor.startAggroCoordinator();
}

if (onFirstUserEnter.length() != 0) {
msm.runMapScript(chr.getClient(), "onFirstUserEnter/" + onFirstUserEnter, true);
}
}
if (onUserEnter.length() != 0) {
if (onUserEnter.equals("cygnusTest") && (mapid < 913040000 || mapid > 913040006)) {
chr.saveLocation("INTRO");
}

msm.runMapScript(chr.getClient(), "onUserEnter/" + 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 == 200090060) { // To Rien
int travelTime = getWorldServer().getTransportationTime(1 * 60 * 1000);
chr.announce(MaplePacketCreator.getClock(travelTime / 1000));
TimerManager.getInstance().schedule(new Runnable() {

@Override
public void run() {
if (chr.getMapId() == 200090060) {
chr.changeMap(140020300, 0);
}
}
}, travelTime);
} else if (mapid == 200090070) { // To Lith Harbor
int travelTime = getWorldServer().getTransportationTime(1 * 60 * 1000);
chr.announce(MaplePacketCreator.getClock(travelTime / 1000));
TimerManager.getInstance().schedule(new Runnable() {

@Override
public void run() {
if (chr.getMapId() == 200090070) {
chr.changeMap(104000000, 3);
}
}
}, travelTime);
} else if (mapid == 200090030) { // To Ereve (SkyFerry)
int travelTime = getWorldServer().getTransportationTime(2 * 60 * 1000);
chr.announce(MaplePacketCreator.getClock(travelTime / 1000));
TimerManager.getInstance().schedule(new Runnable() {

@Override
public void run() {
if (chr.getMapId() == 200090030) {
chr.changeMap(130000210, 0);
}
}
}, travelTime);
} else if (mapid == 200090031) { // To Victoria Island (SkyFerry)
int travelTime = getWorldServer().getTransportationTime(2 * 60 * 1000);
chr.announce(MaplePacketCreator.getClock(travelTime / 1000));
TimerManager.getInstance().schedule(new Runnable() {

@Override
public void run() {
if (chr.getMapId() == 200090031) {
chr.changeMap(101000400, 0);
}
}
}, travelTime);
} else if (mapid == 200090021) { // To Orbis (SkyFerry)
int travelTime = getWorldServer().getTransportationTime(8 * 60 * 1000);
chr.announce(MaplePacketCreator.getClock(travelTime / 1000));
TimerManager.getInstance().schedule(new Runnable() {

@Override
public void run() {
if (chr.getMapId() == 200090021) {
chr.changeMap(200000161, 0);
}
}
}, travelTime);
} else if (mapid == 200090020) { // To Ereve From Orbis (SkyFerry)
int travelTime = getWorldServer().getTransportationTime(8 * 60 * 1000);
chr.announce(MaplePacketCreator.getClock(travelTime / 1000));
TimerManager.getInstance().schedule(new Runnable() {

@Override
public void run() {
if (chr.getMapId() == 200090020) {
chr.changeMap(130000210, 0);
}
}
}, travelTime);
} else if (MapleMiniDungeonInfo.isDungeonMap(mapid)) {
MapleMiniDungeon mmd = chr.getClient().getChannelServer().getMiniDungeon(mapid);
if (mmd != null) {
mmd.registerPlayer(chr);
}
} else if (GameConstants.isAriantColiseumArena(mapid)) {
int pqTimer = (10 * 60 * 1000);
chr.announce(MaplePacketCreator.getClock(pqTimer / 1000));
}

MaplePet[] pets = chr.getPets();
for (int i = 0; i < pets.length; i++) {
if (pets[i] != null) {
pets[i].setPos(getGroundBelow(chr.getPosition()));
chr.announce(MaplePacketCreator.showPet(chr, pets[i], false, false));
} else {
break;
}
}
chr.commitExcludedItems(); // thanks OishiiKawaiiDesu for noticing pet item ignore registry erasing upon changing maps

if (chr.getMonsterCarnival() != null) {
chr.getClient().announce(MaplePacketCreator.getClock(chr.getMonsterCarnival().getTimeLeftSeconds()));
if (isCPQMap()) {
int team = -1;
int oposition = -1;
if (chr.getTeam() == 0) {
team = 0;
oposition = 1;
}
if (chr.getTeam() == 1) {
team = 1;
oposition = 0;
}
chr.getClient().announce(MaplePacketCreator.startMonsterCarnival(chr, team, oposition));
}
}

chr.removeSandboxItems();

if (chr.getChalkboard() != null) {
if (!GameConstants.isFreeMarketRoom(mapid)) {
chr.announce(MaplePacketCreator.useChalkboard(chr, false)); // update player's chalkboard when changing maps found thanks to Vcoc
} else {
chr.setChalkboard(null);
}
}

if (chr.isHidden()) {
broadcastGMSpawnPlayerMapObjectMessage(chr, chr, true);
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 {
broadcastSpawnPlayerMapObjectMessage(chr, chr, true);
}

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));
}

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();
announcePlayerDiseases(chr.getClient());
}

private static void announcePlayerDiseases(final MapleClient c) {
Server.getInstance().registerAnnouncePlayerDiseases(c);
}

public MaplePortal getRandomPlayerSpawnpoint() {
List<MaplePortal> spawnPoints = new ArrayList<>();
for (MaplePortal portal : portals.values()) {
if (portal.getType() >= 0 && portal.getType() <= 1 && portal.getTargetMapId() == 999999999) {
spawnPoints.add(portal);
}
}
MaplePortal portal = spawnPoints.get(new Random().nextInt(spawnPoints.size()));
return portal != null ? portal : getPortal(0);
}

public MaplePortal findClosestTeleportPortal(Point from) {
MaplePortal closest = null;
double shortestDistance = Double.POSITIVE_INFINITY;
for (MaplePortal portal : portals.values()) {
double distance = portal.getPosition().distanceSq(from);
if (portal.getType() == MaplePortal.TELEPORT_PORTAL && distance < shortestDistance && portal.getTargetMapId() != 999999999) {
closest = portal;
shortestDistance = distance;
}
}
return closest;
}

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 MaplePortal findMarketPortal() {
for (MaplePortal portal : portals.values()) {
String ptScript = portal.getScriptName();
if(ptScript != null && ptScript.contains("market")) {
return portal;
}
}
return null;
}

/*
public Collection<MaplePortal> getPortals() {
return Collections.unmodifiableCollection(portals.values());
}
*/

public void addPlayerPuppet(MapleCharacter player) {
for (MapleMonster mm : this.getAllMonsters()) {
mm.aggroAddPuppet(player);
}
}

public void removePlayerPuppet(MapleCharacter player) {
for (MapleMonster mm : this.getAllMonsters()) {
mm.aggroRemovePuppet(player);
}
}

public void removePlayer(MapleCharacter chr) {
Channel cserv = chr.getClient().getChannelServer();

FaceExpressionService service = (FaceExpressionService) this.getChannelServer().getServiceAccess(ChannelServices.FACE_EXPRESSION);
service.unregisterFaceExpression(mapid, chr);
chr.unregisterChairBuff();

MapleParty party = chr.getParty();
chrWLock.lock();
try {
if (party != null && party.getMemberById(chr.getId()) != null) {
removePartyMemberInternal(chr, party.getId());
}

characters.remove(chr);
} finally {
chrWLock.unlock();
}

if (MapleMiniDungeonInfo.isDungeonMap(mapid)) {
MapleMiniDungeon mmd = cserv.getMiniDungeon(mapid);
if(mmd != null) {
if(!mmd.unregisterPlayer(chr)) {
cserv.removeMiniDungeon(mapid);
}
}
}

removeMapObject(chr.getObjectId());
if (!chr.isHidden()) {
broadcastMessage(MaplePacketCreator.removePlayerFromMap(chr.getId()));
} else {
broadcastGMMessage(MaplePacketCreator.removePlayerFromMap(chr.getId()));
}

chr.leaveMap();

for (MapleSummon summon : new ArrayList<>(chr.getSummonsValues())) {
if (summon.isStationary()) {
chr.cancelEffectFromBuffStat(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 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 void updateBossSpawn(MapleMonster monster) {
if (monster.hasBossHPBar()) {
broadcastBossHpMessage(monster, monster.hashCode(), monster.makeBossHPBarPacket(), monster.getPosition());
}
if (monster.isBoss()) {
if (unclaimOwnership() != null) {
String mobName = MapleMonsterInformationProvider.getInstance().getMobNameFromId(monster.getId());
if (mobName != null) {
mobName = mobName.trim();
this.dropMessage(5, "This lawn has been taken siege by " + mobName + "'s forces and will be kept hold until their defeat.");
}
}
}
}

public void broadcastBossHpMessage(MapleMonster mm, int bossHash, final byte[] packet) {
broadcastBossHpMessage(mm, bossHash, null, packet, Double.POSITIVE_INFINITY, null);
}

public void broadcastBossHpMessage(MapleMonster mm, int bossHash, final byte[] packet, Point rangedFrom) {
broadcastBossHpMessage(mm, bossHash, null, packet, getRangedDistance(), rangedFrom);
}

private void broadcastBossHpMessage(MapleMonster mm, int bossHash, 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().announceBossHpBar(mm, bossHash, packet);
}
} else {
chr.getClient().announceBossHpBar(mm, bossHash, packet);
}
}
}
} finally {
chrRLock.unlock();
}
}

private void broadcastItemDropMessage(MapleMapItem mdrop, Point dropperPos, Point dropPos, byte mod, Point rangedFrom) {
broadcastItemDropMessage(mdrop, dropperPos, dropPos, mod, getRangedDistance(), rangedFrom);
}

private void broadcastItemDropMessage(MapleMapItem mdrop, Point dropperPos, Point dropPos, byte mod) {
broadcastItemDropMessage(mdrop, dropperPos, dropPos, mod, Double.POSITIVE_INFINITY, null);
}

private void broadcastItemDropMessage(MapleMapItem mdrop, Point dropperPos, Point dropPos, byte mod, double rangeSq, Point rangedFrom) {
chrRLock.lock();
try {
for (MapleCharacter chr : characters) {
final byte[] packet = MaplePacketCreator.dropItemFromMapObject(chr, mdrop, dropperPos, dropPos, mod);

if (rangeSq < Double.POSITIVE_INFINITY) {
if (rangedFrom.distanceSq(chr.getPosition()) <= rangeSq) {
chr.announce(packet);
}
} else {
chr.announce(packet);
}
}
} finally {
chrRLock.unlock();
}
}

public void broadcastSpawnPlayerMapObjectMessage(MapleCharacter source, MapleCharacter player, boolean enteringField) {
broadcastSpawnPlayerMapObjectMessage(source, player, enteringField, false);
}

public void broadcastGMSpawnPlayerMapObjectMessage(MapleCharacter source, MapleCharacter player, boolean enteringField) {
broadcastSpawnPlayerMapObjectMessage(source, player, enteringField, true);
}

private void broadcastSpawnPlayerMapObjectMessage(MapleCharacter source, MapleCharacter player, boolean enteringField, boolean gmBroadcast) {
chrRLock.lock();
try {
if (gmBroadcast) {
for (MapleCharacter chr : characters) {
if (chr.isGM()) {
if (chr != source) {
chr.announce(MaplePacketCreator.spawnPlayerMapObject(chr.getClient(), player, enteringField));
}
}
}
} else {
for (MapleCharacter chr : characters) {
if (chr != source) {
chr.announce(MaplePacketCreator.spawnPlayerMapObject(chr.getClient(), player, enteringField));
}
}
}
} finally {
chrRLock.unlock();
}
}

public void broadcastUpdateCharLookMessage(MapleCharacter source, MapleCharacter player) {
chrRLock.lock();
try {
for (MapleCharacter chr : characters) {
if (chr != source) {
chr.announce(MaplePacketCreator.updateCharLook(chr.getClient(), player));
}
}
} finally {
chrRLock.unlock();
}
}

public void dropMessage(int type, String message) {
broadcastStringMessage(type, message);
}

public void broadcastStringMessage(int type, String message) {
broadcastMessage(MaplePacketCreator.serverNotice(type, message));
}

private static boolean isNonRangedType(MapleMapObjectType type) {
switch (type) {
case NPC:
case PLAYER:
case HIRED_MERCHANT:
case PLAYER_NPC:
case DRAGON:
case MIST:
case KITE:
return true;
default:
return false;
}
}

private void sendObjectPlacement(MapleClient c) {
MapleCharacter chr = c.getPlayer();
Collection<MapleMapObject> objects;

objectRLock.lock();
try {
objects = new ArrayList<>(mapobjects.values());
} finally {
objectRLock.unlock();
}

for (MapleMapObject o : objects) {
if (isNonRangedType(o.getType())) {
o.sendSpawnData(c);
} else 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.getObjectId());
} finally {
objectWLock.unlock();
}

//continue;
}
}
}
}

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);

if (o.getType() == MapleMapObjectType.MONSTER) {
((MapleMonster) o).aggroUpdateController();
}
}
}
}
}

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;
}

public void setMapPointBoundings(int px, int py, int h, int w) {
mapArea.setBounds(px, py, w, h);
}

public void setMapLineBoundings(int vrTop, int vrBottom, int vrLeft, int vrRight) {
mapArea.setBounds(vrLeft, vrTop, vrRight - vrLeft, vrBottom - vrTop);
}

public MapleMonsterAggroCoordinator getAggroCoordinator() {
return aggroMonitor;
}

/**
* 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 removeMonsterSpawn(int mobId, int x, int y) {
// assumption: spawn points identifies by tuple (lifeid, x, y)

Point checkpos = calcPointBelow(new Point(x, y));
checkpos.y -= 1;

List<SpawnPoint> toRemove = new LinkedList<>();
for(SpawnPoint sp: getMonsterSpawn()) {
Point pos = sp.getPosition();
if (sp.getMonsterId() == mobId && checkpos.equals(pos)) {
toRemove.add(sp);
}
}

if (!toRemove.isEmpty()) {
synchronized (monsterSpawn) {
for (SpawnPoint sp : toRemove) {
monsterSpawn.remove(sp);
}
}
}
}

public void removeAllMonsterSpawn(int mobId, int x, int y) {
// assumption: spawn points identifies by tuple (lifeid, x, y)

Point checkpos = calcPointBelow(new Point(x, y));
checkpos.y -= 1;

List<SpawnPoint> toRemove = new LinkedList<>();
for(SpawnPoint sp: getAllMonsterSpawn()) {
Point pos = sp.getPosition();
if (sp.getMonsterId() == mobId && checkpos.equals(pos)) {
toRemove.add(sp);
}
}

if (!toRemove.isEmpty()) {
synchronized (allMonsterSpawn) {
for (SpawnPoint sp : toRemove) {
allMonsterSpawn.remove(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: getAllMonsterSpawn()) {
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 Map<Integer, MapleCharacter> getMapPlayers() {
chrRLock.lock();
try {
Map<Integer, MapleCharacter> mapChars = new HashMap<>(characters.size());

for(MapleCharacter chr : characters) {
mapChars.put(chr.getId(), chr);
}

return mapChars;
} finally {
chrRLock.unlock();
}
}

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 chr : this.characters) {
if (chr.getId() == id) {
return chr;
}
}
} finally {
chrRLock.unlock();
}
return null;
}

private static void updateMapObjectVisibility(MapleCharacter chr, MapleMapObject mo) {
if (!chr.isMapObjectVisible(mo)) { // object 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);
for (MapleCharacter chr : getAllPlayers()) {
updateMapObjectVisibility(chr, monster);
}
}

public void movePlayer(MapleCharacter player, Point newPosition) {
player.setPosition(newPosition);

try {
MapleMapObject[] visibleObjects = player.getVisibleMapObjects();

Map<Integer, MapleMapObject> mapObjects = getCopyMapObjects();
for (MapleMapObject mo : visibleObjects) {
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(), 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 chr : this.characters) {
sendNightEffect(chr);
}
} finally {
chrRLock.unlock();
}
}

public MapleCharacter getCharacterByName(String name) {
chrRLock.lock();
try {
for (MapleCharacter chr : this.characters) {
if (chr.getName().toLowerCase().equals(name.toLowerCase())) {
return chr;
}
}
} finally {
chrRLock.unlock();
}
return null;
}

public boolean makeDisappearItemFromMap(MapleMapObject mapobj) {
if(mapobj instanceof MapleMapItem) {
return makeDisappearItemFromMap((MapleMapItem) mapobj);
} else {
return mapobj == null; // no drop to make disappear...
}
}

public boolean makeDisappearItemFromMap(MapleMapItem mapitem) {
if (mapitem != null && mapitem == getMapObject(mapitem.getObjectId())) {
mapitem.lockItem();
try {
if (mapitem.isPickedUp()) {
return true;
}

MapleMap.this.pickItemDrop(MaplePacketCreator.removeItemFromMap(mapitem.getObjectId(), 0, 0), mapitem);
return true;
} finally {
mapitem.unlockItem();
}
}

return false;
}

public void spawnMonsterOnGroudBelow(MapleMonster monster, Point point) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}

public void killAllMonsters(boolean b) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}

private class MobLootEntry implements Runnable {

private byte droptype;
private int mobpos;
private int chRate;
private Point pos;
private List<MonsterDropEntry> dropEntry;
private List<MonsterDropEntry> visibleQuestEntry;
private List<MonsterDropEntry> otherQuestEntry;
private List<MonsterGlobalDropEntry> globalEntry;
private MapleCharacter chr;
private MapleMonster mob;

protected MobLootEntry(byte droptype, int mobpos, int chRate, Point pos, List<MonsterDropEntry> dropEntry, List<MonsterDropEntry> visibleQuestEntry, List<MonsterDropEntry> otherQuestEntry, List<MonsterGlobalDropEntry> globalEntry, MapleCharacter chr, MapleMonster mob) {
this.droptype = droptype;
this.mobpos = mobpos;
this.chRate = chRate;
this.pos = pos;
this.dropEntry = dropEntry;
this.visibleQuestEntry = visibleQuestEntry;
this.otherQuestEntry = otherQuestEntry;
this.globalEntry = globalEntry;
this.chr = chr;
this.mob = mob;
}

@Override
public void run() {
byte d = 1;

// Normal Drops
d = dropItemsFromMonsterOnMap(dropEntry, pos, d, chRate, droptype, mobpos, chr, mob);

// Global Drops
d = dropGlobalItemsFromMonsterOnMap(globalEntry, pos, d, droptype, mobpos, chr, mob);

// Quest Drops
d = dropItemsFromMonsterOnMap(visibleQuestEntry, pos, d, chRate, droptype, mobpos, chr, mob);
dropItemsFromMonsterOnMap(otherQuestEntry, pos, d, chRate, droptype, mobpos, chr, mob);
}
}

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.hitLockReactor();
try {
if(reactor.getReactorType() == 100) {
if (reactor.getShouldCollect() == true && mapitem != null && mapitem == getMapObject(mapitem.getObjectId())) {
mapitem.lockItem();
try {
if (mapitem.isPickedUp()) {
return;
}
mapitem.setPickedUp(true);
unregisterItemDrop(mapitem);

reactor.setShouldCollect(false);
MapleMap.this.broadcastMessage(MaplePacketCreator.removeItemFromMap(mapitem.getObjectId(), 0, 0), mapitem.getPosition());

droppedItemCount.decrementAndGet();
MapleMap.this.removeMapObject(mapitem);

reactor.hitReactor(c);

if (reactor.getDelay() > 0) {
MapleMap reactorMap = reactor.getMap();

OverallService service = (OverallService) reactorMap.getChannelServer().getServiceAccess(ChannelServices.OVERALL);
service.registerOverallAction(reactorMap.getId(), new Runnable() {
@Override
public void run() {
reactor.lockReactor();
try {
reactor.resetReactorActions(0);
reactor.setAlive(true);
broadcastMessage(MaplePacketCreator.triggerReactor(reactor, 0));
} finally {
reactor.unlockReactor();
}
}
}, reactor.getDelay());
}
} finally {
mapitem.unlockItem();
}
}
}
} finally {
reactor.hitUnlockReactor();
}
}
}

public void instanceMapFirstSpawn(int difficulty, boolean isPq) {
for(SpawnPoint spawnPoint: getAllMonsterSpawn()) {
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 = getMonsterSpawn();
Collections.shuffle(randomSpawn);
int spawned = 0;
for (SpawnPoint spawnPoint : randomSpawn) {
if(spawnPoint.shouldSpawn()) {
spawnMonster(spawnPoint.getMonster());
spawned++;
if (spawned >= numShouldSpawn) {
break;
}
}
}
}
}

public void instanceMapForceRespawn() {
if (!allowSummons) {
return;
}

final int numShouldSpawn = (short) ((monsterSpawn.size() - spawnedMonstersOnMap.get()));//Fking lol'd
if (numShouldSpawn > 0) {
List<SpawnPoint> randomSpawn = getMonsterSpawn();
Collections.shuffle(randomSpawn);
int spawned = 0;
for (SpawnPoint spawnPoint : randomSpawn) {
if(spawnPoint.shouldForceSpawn()) {
spawnMonster(spawnPoint.getMonster());
spawned++;
if (spawned >= numShouldSpawn) {
break;
}
}
}
}
}

public void closeMapSpawnPoints() {
for (SpawnPoint spawnPoint : getMonsterSpawn()) {
spawnPoint.setDenySpawn(true);
}
}

public void restoreMapSpawnPoints() {
for (SpawnPoint spawnPoint : getMonsterSpawn()) {
spawnPoint.setDenySpawn(false);
}
}

public void setAllowSpawnPointInBox(boolean allow, Rectangle box) {
for(SpawnPoint sp: getMonsterSpawn()) {
if(box.contains(sp.getPosition())) {
sp.setDenySpawn(!allow);
}
}
}

public void setAllowSpawnPointInRange(boolean allow, Point from, double rangeSq) {
for(SpawnPoint sp: getMonsterSpawn()) {
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 : getMonsterSpawn()) {
double distance = sp.getPosition().distanceSq(from);
if (distance < shortestDistance) {
closest = sp;
shortestDistance = distance;
}
}
return closest;
}

private static double getCurrentSpawnRate(int numPlayers) {
return 0.70 + (0.05 * Math.min(6, numPlayers));
}

private int getNumShouldSpawn(int numPlayers) {
/*
System.out.println("----------------------------------");
for (SpawnPoint spawnPoint : getMonsterSpawn()) {
System.out.println("sp " + spawnPoint.getPosition().getX() + ", " + spawnPoint.getPosition().getY() + ": " + spawnPoint.getDenySpawn());
}
System.out.println("try " + monsterSpawn.size() + " - " + spawnedMonstersOnMap.get());
System.out.println("----------------------------------");
*/

if(YamlConfig.config.server.USE_ENABLE_FULL_RESPAWN) {
return (monsterSpawn.size() - spawnedMonstersOnMap.get());
}

int maxNumShouldSpawn = (int) Math.ceil(getCurrentSpawnRate(numPlayers) * monsterSpawn.size());
return maxNumShouldSpawn - spawnedMonstersOnMap.get();
}

public void respawn() {
if (!allowSummons) {
return;
}

int numPlayers;
chrRLock.lock();
try {
numPlayers = characters.size();

if(numPlayers == 0) {
return;
}
} finally {
chrRLock.unlock();
}

int numShouldSpawn = getNumShouldSpawn(numPlayers);
if(numShouldSpawn > 0) {
List<SpawnPoint> randomSpawn = new ArrayList<>(getMonsterSpawn());
Collections.shuffle(randomSpawn);
short spawned = 0;
for(SpawnPoint spawnPoint : randomSpawn) {
if(spawnPoint.shouldSpawn()) {
spawnMonster(spawnPoint.getMonster());
spawned++;

if(spawned >= numShouldSpawn) {
break;
}
}
}
}
}

public void mobMpRecovery() {
for (MapleMonster mob : this.getAllMonsters()) {
if (mob.isAlive()) {
mob.heal(0, mob.getLevel());
}
}
}

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;
}

public float getRecovery() {
return recovery;
}

public void setRecovery(float recRate) {
recovery = recRate;
}

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 setSeats(int seats) {
this.seats = seats;
}

public int getSeats() {
return seats;
}

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) {
for (MapleMapObject i : getMapObjectsInRange(player.getPosition(), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.ITEM))) {
droppedItemCount.decrementAndGet();
removeMapObject(i);
this.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))) {
droppedItemCount.decrementAndGet();
removeMapObject(i);
this.broadcastMessage(MaplePacketCreator.removeItemFromMap(i.getObjectId(), 0, 0));
}
}

public void setFieldLimit(int fieldLimit) {
this.fieldLimit = fieldLimit;
}

public int getFieldLimit() {
return fieldLimit;
}

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);
}
}

public void warpEveryone(int to, int pto) {
List<MapleCharacter> players = new ArrayList<>(getCharacters());

for (MapleCharacter chr : players) {
chr.changeMap(to, pto);
}
}

// 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 setTimeMob(int id, String msg) {
timeMob = new Pair<>(id, msg);
}

public Pair<Integer, String> getTimeMob() {
return timeMob;
}

public void toggleHiddenNPC(int id) {
chrRLock.lock();
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();
chrRLock.unlock();
}
}

public void setMobInterval(short interval) {
this.mobInterval = interval;
}

public short getMobInterval() {
return mobInterval;
}

public void clearMapObjects() {
clearDrops();
killAllMonsters();
resetReactors();
}

public final void resetFully() {
resetMapObjects();
}

public void resetMapObjects() {
resetMapObjects(1, false);
}

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 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
MapleMonster htIntro = MapleLifeFactory.getMonster(8810026);
spawnMonsterOnGroundBelow(htIntro, targetPoint); // htintro spawn animation converting into horntail detected thanks to Arnah

final MapleMonster ht = MapleLifeFactory.getMonster(8810018);
ht.setParentMobOid(htIntro.getObjectId());
ht.addListener(new MonsterListener() {
@Override
public void monsterKilled(int aniTime) {}

@Override
public void monsterDamaged(MapleCharacter from, int trueDmg) {
ht.addHp(trueDmg);
}

@Override
public void monsterHealed(int trueHeal) {
ht.addHp(-trueHeal);
}
});
spawnMonsterOnGroundBelow(ht, targetPoint);

for (int x = 8810002; x <= 8810009; x++) {
MapleMonster m = MapleLifeFactory.getMonster(x);
m.setParentMobOid(htIntro.getObjectId());

m.addListener(new MonsterListener() {
@Override
public void monsterKilled(int aniTime) {}

@Override
public void monsterDamaged(MapleCharacter from, int trueDmg) {
// thanks Halcyon for noticing HT not dropping loots due to propagated damage not registering attacker
ht.applyFakeDamage(from, trueDmg, true);
}

@Override
public void monsterHealed(int trueHeal) {
ht.addHp(trueHeal);
}
});

spawnMonsterOnGroundBelow(m, targetPoint);
}
}

public boolean claimOwnership(MapleCharacter chr) {
if (mapOwner == null) {
this.mapOwner = chr;
chr.setOwnedMap(this);

mapOwnerLastActivityTime = Server.getInstance().getCurrentTime();

getChannelServer().registerOwnedMap(this);
return true;
} else {
return chr == mapOwner;
}
}

public MapleCharacter unclaimOwnership() {
MapleCharacter lastOwner = this.mapOwner;
return unclaimOwnership(lastOwner) ? lastOwner : null;
}

public boolean unclaimOwnership(MapleCharacter chr) {
if (chr != null && mapOwner == chr) {
this.mapOwner = null;
chr.setOwnedMap(null);

mapOwnerLastActivityTime = Long.MAX_VALUE;

getChannelServer().unregisterOwnedMap(this);
return true;
} else {
return false;
}
}

private void refreshOwnership() {
mapOwnerLastActivityTime = Server.getInstance().getCurrentTime();
}

public boolean isOwnershipRestricted(MapleCharacter chr) {
MapleCharacter owner = mapOwner;

if (owner != null) {
if (owner != chr && !owner.isPartyMember(chr)) { // thanks Vcoc & BHB for suggesting the map ownership feature
chr.showMapOwnershipInfo(owner);
return true;
} else {
this.refreshOwnership();
}
}

return false;
}

public void checkMapOwnerActivity() {
long timeNow = Server.getInstance().getCurrentTime();
if (timeNow - mapOwnerLastActivityTime > 60000) {
if (unclaimOwnership() != null) {
this.dropMessage(5, "This lawn is now free real estate.");
}
}
}

private final List<Point> takenSpawns = new LinkedList<>();
private final List<GuardianSpawnPoint> guardianSpawns = new LinkedList<>();
private final List<MCSkill> blueTeamBuffs = new ArrayList();
private final List<MCSkill> redTeamBuffs = new ArrayList();
private List<Integer> skillIds = new ArrayList();
private List<Pair<Integer, Integer>> mobsToSpawn = new ArrayList();

public List<MCSkill> getBlueTeamBuffs() {
return blueTeamBuffs;
}

public List<MCSkill> getRedTeamBuffs() {
return redTeamBuffs;
}

public void clearBuffList() {
redTeamBuffs.clear();
blueTeamBuffs.clear();
}

public List<MapleMapObject> getAllPlayer() {
return getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.PLAYER));
}

public boolean isCPQMap() {
switch (this.getId()) {
case 980000101:
case 980000201:
case 980000301:
case 980000401:
case 980000501:
case 980000601:
case 980031100:
case 980032100:
case 980033100:
return true;
}
return false;
}

public boolean isCPQMap2() {
switch (this.getId()) {
case 980031100:
case 980032100:
case 980033100:
return true;
}
return false;
}

public boolean isCPQLobby() {
switch (this.getId()) {
case 980000100:
case 980000200:
case 980000300:
case 980000400:
case 980000500:
case 980000600:
return true;
}
return false;
}

public boolean isBlueCPQMap() {
switch (this.getId()) {
case 980000501:
case 980000601:
case 980031200:
case 980032200:
case 980033200:
return true;
}
return false;
}

public boolean isPurpleCPQMap() {
switch (this.getId()) {
case 980000301:
case 980000401:
case 980031200:
case 980032200:
case 980033200:
return true;
}
return false;
}

public Point getRandomSP(int team) {
if (takenSpawns.size() > 0) {
for (SpawnPoint sp : monsterSpawn) {
for (Point pt : takenSpawns) {
if ((sp.getPosition().x == pt.x && sp.getPosition().y == pt.y) || (sp.getTeam() != team && !this.isBlueCPQMap())) {
continue;
} else {
takenSpawns.add(pt);
return sp.getPosition();
}
}
}
} else {
for (SpawnPoint sp : monsterSpawn) {
if (sp.getTeam() == team || this.isBlueCPQMap()) {
takenSpawns.add(sp.getPosition());
return sp.getPosition();
}
}
}
return null;
}

public GuardianSpawnPoint getRandomGuardianSpawn(int team) {
boolean alltaken = false;
for (GuardianSpawnPoint a : this.guardianSpawns) {
if (!a.isTaken()) {
alltaken = false;
break;
}
}
if (alltaken) {
return null;
}
if (this.guardianSpawns.size() > 0) {
while (true) {
for (GuardianSpawnPoint gsp : this.guardianSpawns) {
if (!gsp.isTaken() && Math.random() < 0.3 && (gsp.getTeam() == -1 || gsp.getTeam() == team)) {
return gsp;
}
}
}
}
return null;
}

public void addGuardianSpawnPoint(GuardianSpawnPoint a) {
this.guardianSpawns.add(a);
}

public int spawnGuardian(int team, int num) {
try {
if (team == 0 && redTeamBuffs.size() >= 4 || team == 1 && blueTeamBuffs.size() >= 4) {
return 2;
}
final MCSkill skill = MapleCarnivalFactory.getInstance().getGuardian(num);
if (team == 0 && redTeamBuffs.contains(skill)) {
return 0;
} else if (team == 1 && blueTeamBuffs.contains(skill)) {
return 0;
}
GuardianSpawnPoint pt = this.getRandomGuardianSpawn(team);
if (pt == null) {
return -1;
}
int reactorID = 9980000 + team;
MapleReactor reactor = new MapleReactor(MapleReactorFactory.getReactorS(reactorID), reactorID);
pt.setTaken(true);
reactor.setPosition(pt.getPosition());
reactor.setName(team + "" + num); //lol
reactor.resetReactorActions(0);
this.spawnReactor(reactor);
reactor.setGuardian(pt);
this.buffMonsters(team, skill);
getReactorByOid(reactor.getObjectId()).hitReactor(((MapleCharacter) this.getAllPlayer().get(0)).getClient());
} catch (Exception e) {
e.printStackTrace();
}
return 1;
}

public void buffMonsters(int team, MCSkill skill) {
if (skill == null) return;

if (team == 0) {
redTeamBuffs.add(skill);
} else if (team == 1) {
blueTeamBuffs.add(skill);
}
for (MapleMapObject mmo : this.mapobjects.values()) {
if (mmo.getType() == MapleMapObjectType.MONSTER) {
MapleMonster mob = (MapleMonster) mmo;
if (mob.getTeam() == team) {
skill.getSkill().applyEffect(null, mob, false, null);
}
}
}
}

public final List<Integer> getSkillIds() {
return skillIds;
}

public final void addSkillId(int z) {
this.skillIds.add(z);
}

public final void addMobSpawn(int mobId, int spendCP) {
this.mobsToSpawn.add(new Pair<Integer, Integer>(mobId, spendCP));
}

public final List<Pair<Integer, Integer>> getMobsToSpawn() {
return mobsToSpawn;
}

public boolean isCPQWinnerMap() {
switch (this.getId()) {
case 980000103:
case 980000203:
case 980000303:
case 980000403:
case 980000503:
case 980000603:
case 980031300:
case 980032300:
case 980033300:
return true;
}
return false;
}

public boolean isCPQLoserMap() {
switch (this.getId()) {
case 980000104:
case 980000204:
case 980000304:
case 980000404:
case 980000504:
case 980000604:
case 980031400:
case 980032400:
case 980033400:
return true;
}
return false;
}

public void runCharacterStatUpdate() {
if (!statUpdateRunnables.isEmpty()) {
List<Runnable> toRun = new ArrayList<>(statUpdateRunnables);
statUpdateRunnables.clear();

for (Runnable r : toRun) {
r.run();
}
}
}

public void registerCharacterStatUpdate(Runnable r) {
statUpdateRunnables.add(r);
}

public void dispose() {
for(MapleMonster mm : this.getAllMonsters()) {
mm.dispose();
}

clearMapObjects();

event = null;
footholds = null;
portals.clear();
mapEffect = null;

chrWLock.lock();
try {
aggroMonitor.dispose();
aggroMonitor = null;

if(itemMonitor != null) {
itemMonitor.cancel(false);
itemMonitor = null;
}

if(expireItemsTask != null) {
expireItemsTask.cancel(false);
expireItemsTask = null;
}

if(mobSpawnLootTask != null) {
mobSpawnLootTask.cancel(false);
mobSpawnLootTask = null;
}

if(characterStatUpdateTask != null) {
characterStatUpdateTask.cancel(false);
characterStatUpdateTask = null;
}
} finally {
chrWLock.unlock();
}
}

public int getMaxMobs() {
return maxMobs;
}

public void setMaxMobs(int maxMobs) {
this.maxMobs = maxMobs;
}

public int getMaxReactors() {
return maxReactors;
}

public void setMaxReactors(int maxReactors) {
this.maxReactors = maxReactors;
}

public int getDeathCP() {
return deathCP;
}

public void setDeathCP(int deathCP) {
this.deathCP = deathCP;
}

public int getTimeDefault() {
return timeDefault;
}

public void setTimeDefault(int timeDefault) {
this.timeDefault = timeDefault;
}

public int getTimeExpand() {
return timeExpand;
}

public void setTimeExpand(int timeExpand) {
this.timeExpand = timeExpand;
}

}



Don't know if anything else is needed but please advise if i need anything.
ANY help is much appreciated.

ran online classic client and bin anyone wants to help a newbie here

$
0
0
hello sir mam, i would like to try to have a private server, but most of the file here are ep7 and up... i just want an old ran... can anywant have it please share it thnks
Viewing all 24059 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>