/*
OrpheusMS: MapleStory Private Server based on OdinMS
Copyright (C) 2012 Aaron Weiss <aaron@deviant-core.net>
Patrick Huy <patrick.huy@frz.cc>
Matthias Butz <matze@odinms.de>
Jan Christian Meyer <vimes@odinms.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.server.handlers.channel;
import client.Equip;
import client.IItem;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import client.ISkill;
import client.Item;
import client.MapleBuffStat;
import client.MapleCharacter;
import client.MapleInventoryType;
import client.MapleJob;
import client.MapleStat;
import client.SkillFactory;
import client.status.MonsterStatus;
import client.status.MonsterStatusEffect;
import constants.ItemConstants;
import constants.skills.Assassin;
import constants.skills.Bandit;
import constants.skills.Bishop;
import constants.skills.Bowmaster;
import constants.skills.Brawler;
import constants.skills.ChiefBandit;
import constants.skills.Cleric;
import constants.skills.Corsair;
import constants.skills.FPArchMage;
import constants.skills.Gunslinger;
import constants.skills.ILArchMage;
import constants.skills.Marauder;
import constants.skills.Marksman;
import constants.skills.NightWalker;
import constants.skills.Outlaw;
import constants.skills.Paladin;
import constants.skills.Rogue;
import constants.skills.Shadower;
import constants.skills.ThunderBreaker;
import constants.skills.WindArcher;
import server.life.MonsterDropEntry;
import tools.Randomizer;
import net.AbstractMaplePacketHandler;
import client.autoban.AutobanFactory;
import constants.skills.Aran;
import constants.skills.Crossbowman;
import constants.skills.DawnWarrior;
import constants.skills.Fighter;
import constants.skills.Hunter;
import constants.skills.Page;
import constants.skills.Spearman;
import java.util.HashMap;
import java.util.Map;
import server.MapleBuffStatDelta;
import server.MapleItemInformationProvider;
import server.MapleStatEffect;
import server.TimerManager;
import server.life.Element;
import server.life.ElementalEffectiveness;
import server.life.MapleMonster;
import server.life.MapleMonsterInformationProvider;
import server.maps.MapleMap;
import server.maps.MapleMapItem;
import server.maps.MapleMapObject;
import server.maps.MapleMapObjectType;
import server.partyquest.Pyramid;
import tools.MaplePacketCreator;
import tools.data.input.LittleEndianAccessor;
public abstract class AbstractDealDamageHandler extends AbstractMaplePacketHandler {
public static class AttackInfo {
public int numAttacked, numDamage, numAttackedAndDamage, skill, skilllevel, stance, direction, rangedirection, charge, display;
public Map<Integer, List<Integer>> allDamage;
public boolean isHH = false;
public int speed = 4;
public MapleStatEffect getAttackEffect(MapleCharacter chr, ISkill theSkill) {
ISkill mySkill = theSkill;
if (mySkill == null) {
mySkill = SkillFactory.getSkill(skill);
}
int skillLevel = chr.getSkillLevel(mySkill);
if (mySkill.getId() % 10000000 == 1020) {
if (chr.getPartyQuest() instanceof Pyramid) {
if (((Pyramid) chr.getPartyQuest()).useSkill()) {
skillLevel = 1;
}
}
}
if (skillLevel == 0) {
return null;
}
if (display > 80) { //Hmm
if (!theSkill.getAction()) {
AutobanFactory.FAST_ATTACK.autoban(chr, "WZ Edit; adding action to a skill: " + display);
return null;
}
}
return mySkill.getEffect(skillLevel);
}
}
@SuppressWarnings("unused")
protected synchronized void applyAttack(AttackInfo attack, final MapleCharacter player, int attackCount) {
ISkill theSkill = null;
MapleStatEffect attackEffect = null;
try {
if (player.isBanned()) {
return;
}
if (attack.skill != 0) {
theSkill = SkillFactory.getSkill(attack.skill);
attackEffect = attack.getAttackEffect(player, theSkill);
if (attackEffect == null) {
player.getClient().announce(MaplePacketCreator.enableActions());
return;
}
if (player.getMp() < attackEffect.getMpCon()) {
AutobanFactory.MPCON.addPoint(player.getAutobanManager(), "Skill: " + attack.skill + "; Player MP: " + player.getMp() + "; MP Needed: " + attackEffect.getMpCon());
}
if (attack.skill != Cleric.HEAL) {
if (player.isAlive()) {
attackEffect.applyTo(player);
} else {
player.getClient().announce(MaplePacketCreator.enableActions());
}
}
int mobCount = attackEffect.getMobCount();
if (attack.skill == DawnWarrior.FINAL_ATTACK || attack.skill == Page.FINAL_ATTACK_BW || attack.skill == Page.FINAL_ATTACK_SWORD || attack.skill == Fighter.FINAL_ATTACK_SWORD
|| attack.skill == Fighter.FINAL_ATTACK_AXE || attack.skill == Spearman.FINAL_ATTACK_SPEAR || attack.skill == Spearman.FINAL_ATTACK_POLEARM || attack.skill == WindArcher.FINAL_ATTACK
|| attack.skill == DawnWarrior.FINAL_ATTACK || attack.skill == Hunter.FINAL_ATTACK || attack.skill == Crossbowman.FINAL_ATTACK) {
mobCount = 15;//:(
}
if (attack.numAttacked > mobCount) {
AutobanFactory.MOB_COUNT.autoban(player, "Skill: " + attack.skill + "; Count: " + attack.numAttacked + " Max: " + attackEffect.getMobCount());
return;
}
}
if (!player.isAlive()) {
return;
}
//WTF IS THIS F3,1
/*if (attackCount != attack.numDamage && attack.skill != ChiefBandit.MESO_EXPLOSION && attack.skill != NightWalker.VAMPIRE && attack.skill != WindArcher.WIND_SHOT && attack.skill != Aran.COMBO_SMASH && attack.skill != Aran.COMBO_PENRIL && attack.skill != Aran.COMBO_TEMPEST && attack.skill != NightLord.NINJA_AMBUSH && attack.skill != Shadower.NINJA_AMBUSH) {
return;
}*/
int totDamage = 0;
final MapleMap map = player.getMap();
if (attack.skill == ChiefBandit.MESO_EXPLOSION) {
int delay = 0;
for (Integer oned : attack.allDamage.keySet()) {
MapleMapObject mapobject = map.getMapObject(oned.intValue());
if (mapobject != null && mapobject.getType() == MapleMapObjectType.ITEM) {
final MapleMapItem mapitem = (MapleMapItem) mapobject;
if (mapitem.getMeso() > 9) {
synchronized (mapitem) {
if (mapitem.isPickedUp()) {
return;
}
TimerManager.getInstance().schedule(new Runnable() {
@Override
public void run() {
map.removeMapObject(mapitem);
map.broadcastMessage(MaplePacketCreator.removeItemFromMap(mapitem.getObjectId(), 4, 0), mapitem.getPosition());
mapitem.setPickedUp(true);
}
}, delay);
delay += 100;
}
} else if (mapitem.getMeso() == 0) {
return;
}
} else if (mapobject != null && mapobject.getType() != MapleMapObjectType.MONSTER) {
return;
}
}
}
for (Integer oned : attack.allDamage.keySet()) {
final MapleMonster monster = map.getMonsterByOid(oned.intValue());
if (monster != null) {
int totDamageToOneMonster = 0;
List<Integer> onedList = attack.allDamage.get(oned);
for (Integer eachd : onedList) {
totDamageToOneMonster += eachd.intValue();
}
totDamage += totDamageToOneMonster;
player.checkMonsterAggro(monster);
if (player.getBuffedValue(MapleBuffStat.PICKPOCKET) != null && (attack.skill == 0 || attack.skill == Rogue.DOUBLE_STAB || attack.skill == Bandit.SAVAGE_BLOW || attack.skill == ChiefBandit.ASSAULTER || attack.skill == ChiefBandit.BAND_OF_THIEVES || attack.skill == Shadower.ASSASSINATE || attack.skill == Shadower.TAUNT || attack.skill == Shadower.BOOMERANG_STEP)) {
ISkill pickpocket = SkillFactory.getSkill(ChiefBandit.PICKPOCKET);
int delay = 0;
final int maxmeso = player.getBuffedValue(MapleBuffStat.PICKPOCKET).intValue();
for (final Integer eachd : onedList) {
if (pickpocket.getEffect(player.getSkillLevel(pickpocket)).makeChanceResult()) {
TimerManager.getInstance().schedule(new Runnable() {
@Override
public void run() {
player.getMap().spawnMesoDrop(Math.min((int) Math.max(((double) eachd / (double) 20000) * (double) maxmeso, (double) 1), maxmeso), new Point((int) (monster.getPosition().getX() + Randomizer.nextInt(100) - 50), (int) (monster.getPosition().getY())), monster, player, true, (byte) 0);
}
}, delay);
delay += 100;
}
}
} else if (attack.skill == Marksman.SNIPE) {
totDamageToOneMonster = 195000 + Randomizer.nextInt(5000);
} else if (attack.skill == Marauder.ENERGY_DRAIN || attack.skill == ThunderBreaker.ENERGY_DRAIN || attack.skill == NightWalker.VAMPIRE || attack.skill == Assassin.DRAIN) {
player.addHP(Math.min(monster.getMaxHp(), Math.min((int) ((double) totDamage * (double) SkillFactory.getSkill(attack.skill).getEffect(player.getSkillLevel(SkillFactory.getSkill(attack.skill))).getX() / 100.0), player.getMaxHp() / 2)));
} else if (attack.skill == Bandit.STEAL) {
ISkill steal = SkillFactory.getSkill(Bandit.STEAL);
if (Math.random() < 0.3 && steal.getEffect(player.getSkillLevel(steal)).makeChanceResult()) { //Else it drops too many cool stuff :(
List<MonsterDropEntry> toSteals = MapleMonsterInformationProvider.getInstance().retrieveDrop(monster.getId());
Collections.shuffle(toSteals);
int toSteal = toSteals.get(rand(0, (toSteals.size() - 1))).itemId;
MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
IItem item = null;
if (ItemConstants.getInventoryType(toSteal).equals(MapleInventoryType.EQUIP)) {
item = ii.randomizeStats((Equip) ii.getEquipById(toSteal));
} else {
item = new Item(toSteal, (byte) 0, (short) 1, -1);
}
player.getMap().spawnItemDrop(monster, player, item, monster.getPosition(), false, false);
monster.addStolen(toSteal);
}
} else if (attack.skill == FPArchMage.FIRE_DEMON) {
monster.setTempEffectiveness(Element.ICE, ElementalEffectiveness.WEAK, SkillFactory.getSkill(FPArchMage.FIRE_DEMON).getEffect(player.getSkillLevel(SkillFactory.getSkill(FPArchMage.FIRE_DEMON))).getDuration() * 1000);
} else if (attack.skill == ILArchMage.ICE_DEMON) {
monster.setTempEffectiveness(Element.FIRE, ElementalEffectiveness.WEAK, SkillFactory.getSkill(ILArchMage.ICE_DEMON).getEffect(player.getSkillLevel(SkillFactory.getSkill(ILArchMage.ICE_DEMON))).getDuration() * 1000);
} else if (attack.skill == Outlaw.HOMING_BEACON || attack.skill == Corsair.BULLSEYE) {
player.setMarkedMonster(monster.getObjectId());
player.announce(MaplePacketCreator.giveBuff(1, attack.skill, Collections.singletonList(new MapleBuffStatDelta(MapleBuffStat.HOMING_BEACON, monster.getObjectId()))));
}
if (player.getBuffedValue(MapleBuffStat.HAMSTRING) != null) {
ISkill hamstring = SkillFactory.getSkill(Bowmaster.HAMSTRING);
if (hamstring.getEffect(player.getSkillLevel(hamstring)).makeChanceResult()) {
MonsterStatusEffect monsterStatusEffect = new MonsterStatusEffect(Collections.singletonMap(MonsterStatus.SPEED, hamstring.getEffect(player.getSkillLevel(hamstring)).getX()), hamstring, null, false);
monster.applyStatus(player, monsterStatusEffect, false, hamstring.getEffect(player.getSkillLevel(hamstring)).getY() * 1000);
}
}
if (player.getBuffedValue(MapleBuffStat.BLIND) != null) {
ISkill blind = SkillFactory.getSkill(Marksman.BLIND);
if (blind.getEffect(player.getSkillLevel(blind)).makeChanceResult()) {
MonsterStatusEffect monsterStatusEffect = new MonsterStatusEffect(Collections.singletonMap(MonsterStatus.ACC, blind.getEffect(player.getSkillLevel(blind)).getX()), blind, null, false);
monster.applyStatus(player, monsterStatusEffect, false, blind.getEffect(player.getSkillLevel(blind)).getY() * 1000);
}
}
final int id = player.getJob().getId();
if (id == 121 || id == 122) {
for (int charge = 1211005; charge < 1211007; charge++) {
ISkill chargeSkill = SkillFactory.getSkill(charge);
if (player.isBuffFrom(MapleBuffStat.WK_CHARGE, chargeSkill)) {
final ElementalEffectiveness iceEffectiveness = monster.getEffectiveness(Element.ICE);
if (totDamageToOneMonster > 0 && iceEffectiveness == ElementalEffectiveness.NORMAL || iceEffectiveness == ElementalEffectiveness.WEAK) {
monster.applyStatus(player, new MonsterStatusEffect(Collections.singletonMap(MonsterStatus.FREEZE, 1), chargeSkill, null, false), false, chargeSkill.getEffect(player.getSkillLevel(chargeSkill)).getY() * 2000);
}
break;
}
}
} else if (player.getBuffedValue(MapleBuffStat.BODY_PRESSURE) != null || player.getBuffedValue(MapleBuffStat.COMBO_DRAIN) != null) {
ISkill skill;
if (player.getBuffedValue(MapleBuffStat.BODY_PRESSURE) != null) {
skill = SkillFactory.getSkill(21101003);
final MapleStatEffect eff = skill.getEffect(player.getSkillLevel(skill));
if (eff.makeChanceResult()) {
monster.applyStatus(player, new MonsterStatusEffect(Collections.singletonMap(MonsterStatus.NEUTRALISE, 1), skill, null, false), false, eff.getX() * 1000, false);
}
}
if (player.getBuffedValue(MapleBuffStat.COMBO_DRAIN) != null) {
skill = SkillFactory.getSkill(21100005);
player.setHp(player.getHp() + ((totDamage * skill.getEffect(player.getSkillLevel(skill)).getX()) / 100), true);
player.updateSingleStat(MapleStat.HP, player.getHp());
}
} else if (id == 412 || id == 422 || id == 1411) {
ISkill type = SkillFactory.getSkill(player.getJob().getId() == 412 ? 4120005 : (player.getJob().getId() == 1411 ? 14110004 : 4220005));
if (player.getSkillLevel(type) > 0) {
MapleStatEffect venomEffect = type.getEffect(player.getSkillLevel(type));
for (int i = 0; i < attackCount; i++) {
if (venomEffect.makeChanceResult()) {
if (monster.getVenomMulti() < 3) {
monster.setVenomMulti((monster.getVenomMulti() + 1));
MonsterStatusEffect monsterStatusEffect = new MonsterStatusEffect(Collections.singletonMap(MonsterStatus.POISON, 1), type, null, false);
monster.applyStatus(player, monsterStatusEffect, false, venomEffect.getDuration(), true);
}
}
}
}
}
if (attack.skill != 0) {
if (attackEffect.getFixDamage() != -1) {
if (totDamageToOneMonster != attackEffect.getFixDamage() && totDamageToOneMonster != 0) {
AutobanFactory.FIX_DAMAGE.autoban(player, String.valueOf(totDamageToOneMonster) + " damage");
}
}
}
if (totDamageToOneMonster > 0 && attackEffect != null && attackEffect.getMonsterStati().size() > 0) {
if (attackEffect.makeChanceResult()) {
monster.applyStatus(player, new MonsterStatusEffect(attackEffect.getMonsterStati(), theSkill, null, false), attackEffect.isPoison(), attackEffect.getDuration());
}
}
if (player.getJob().equals(MapleJob.LEGEND) || player.getJob().isA(MapleJob.ARAN4)) {
byte comboLevel = (byte) (player.getJob().equals(MapleJob.LEGEND) ? 10 : player.getSkillLevel(Aran.COMBO_ABILITY));
if (comboLevel > 0) {
final long currentTime = System.currentTimeMillis();
short combo = 0;
if (attack.skill == Aran.COMBO_SMASH || attack.skill == Aran.COMBO_PENRIL || attack.skill == Aran.COMBO_TEMPEST) {
player.setCombo(combo);//WHY NOT USE COMBO LOL
}
for (Integer amount : onedList) {
combo = player.getCombo();
if ((currentTime - player.getLastCombo()) > 3000 && combo > 0) {
combo = 0;
player.cancelEffectFromBuffStat(MapleBuffStat.ARAN_COMBO);
}
combo++;
switch (combo) {
case 10:
case 20:
case 30:
case 40:
case 50:
case 60:
case 70:
case 80:
case 90:
case 100:
if ((combo / 10) <= comboLevel) {
SkillFactory.getSkill(21000000).getEffect(combo / 10).applyComboBuff(player, combo);
}
break;
}
player.setCombo(combo);
}
player.setLastCombo(currentTime);
}
}
if (attack.isHH && !monster.isBoss()) {
map.damageMonster(player, monster, monster.getHp() - 1);
} else if (attack.isHH) {
int HHDmg = (player.calculateMaxBaseDamage(player.getTotalWatk()) * (SkillFactory.getSkill(Paladin.HEAVENS_HAMMER).getEffect(player.getSkillLevel(SkillFactory.getSkill(Paladin.HEAVENS_HAMMER))).getDamage() / 100));
map.damageMonster(player, monster, (int) (Math.floor(Math.random() * (HHDmg / 5) + HHDmg * .8)));
} else {
map.damageMonster(player, monster, totDamageToOneMonster);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
protected AttackInfo parseDamage(LittleEndianAccessor lea, MapleCharacter chr, boolean ranged) {
AttackInfo ret = new AttackInfo();
lea.readByte();
ret.numAttackedAndDamage = lea.readByte();
ret.numAttacked = (ret.numAttackedAndDamage >>> 4) & 0xF;
ret.numDamage = ret.numAttackedAndDamage & 0xF;
ret.allDamage = new HashMap<Integer, List<Integer>>();
ret.skill = lea.readInt();
if (ret.skill > 0) {
ret.skilllevel = chr.getSkillLevel(ret.skill);
}
if (ret.skill == FPArchMage.BIG_BANG || ret.skill == ILArchMage.BIG_BANG || ret.skill == Bishop.BIG_BANG || ret.skill == Gunslinger.GRENADE || ret.skill == Brawler.CORKSCREW_BLOW || ret.skill == ThunderBreaker.CORKSCREW_BLOW || ret.skill == NightWalker.POISON_BOMB) {
ret.charge = lea.readInt();
} else {
ret.charge = 0;
}
if (ret.skill == Paladin.HEAVENS_HAMMER) {
ret.isHH = true;
}
lea.skip(8);
ret.display = lea.readByte();
ret.direction = lea.readByte();
ret.stance = lea.readByte();
if (ret.skill == ChiefBandit.MESO_EXPLOSION) {
if (ret.numAttackedAndDamage == 0) {
lea.skip(10);
int bullets = lea.readByte();
for (int j = 0; j < bullets; j++) {
int mesoid = lea.readInt();
lea.skip(1);
ret.allDamage.put(Integer.valueOf(mesoid), null);
}
return ret;
} else {
lea.skip(6);
}
for (int i = 0; i < ret.numAttacked + 1; i++) {
int oid = lea.readInt();
if (i < ret.numAttacked) {
lea.skip(12);
int bullets = lea.readByte();
List<Integer> allDamageNumbers = new ArrayList<Integer>();
for (int j = 0; j < bullets; j++) {
int damage = lea.readInt();
allDamageNumbers.add(Integer.valueOf(damage));
}
ret.allDamage.put(Integer.valueOf(oid), allDamageNumbers);
lea.skip(4);
} else {
int bullets = lea.readByte();
for (int j = 0; j < bullets; j++) {
int mesoid = lea.readInt();
lea.skip(1);
ret.allDamage.put(Integer.valueOf(mesoid), null);
}
}
}
return ret;
}
if (ranged) {
lea.readByte();
ret.speed = lea.readByte();
lea.readByte();
ret.rangedirection = lea.readByte();
lea.skip(7);
if (ret.skill == Bowmaster.HURRICANE || ret.skill == Marksman.PIERCING_ARROW || ret.skill == Corsair.RAPID_FIRE || ret.skill == WindArcher.HURRICANE) {
lea.skip(4);
}
} else {
lea.readByte();
ret.speed = lea.readByte();
lea.skip(4);
}
for (int i = 0; i < ret.numAttacked; i++) {
int oid = lea.readInt();
lea.skip(14);
List<Integer> allDamageNumbers = new ArrayList<Integer>();
for (int j = 0; j < ret.numDamage; j++) {
int damage = lea.readInt();
if (ret.skill == Marksman.SNIPE) {
damage += 0x80000000; //Critical
}
allDamageNumbers.add(Integer.valueOf(damage));
}
if (ret.skill != 5221004) {
lea.skip(4);
}
ret.allDamage.put(Integer.valueOf(oid), allDamageNumbers);
}
return ret;
}
private static int rand(int l, int u) {
return (int) ((Math.random() * (u - l + 1)) + l);
}
}