package javastory.channel.handling;
import java.awt.Point;
import java.util.Collections;
import java.util.List;
import com.google.common.collect.Lists;
import javastory.channel.ChannelCharacter;
import javastory.channel.anticheat.CheatTracker;
import javastory.channel.anticheat.CheatingOffense;
import javastory.channel.client.ActivePlayerStats;
import javastory.channel.client.BuffStat;
import javastory.channel.client.ISkill;
import javastory.channel.client.MonsterStatus;
import javastory.channel.client.MonsterStatusEffect;
import javastory.channel.life.Monster;
import javastory.channel.maps.GameMap;
import javastory.channel.maps.GameMapItem;
import javastory.channel.maps.GameMapObject;
import javastory.channel.maps.GameMapObjectType;
import javastory.channel.maps.ItemDropType;
import javastory.channel.server.StatEffect;
import javastory.game.AttackType;
import javastory.game.AttackNature;
import javastory.game.Skills;
import javastory.game.data.MobInfo;
import javastory.game.data.SkillInfoProvider;
import javastory.io.PacketFormatException;
import javastory.io.PacketReader;
import javastory.server.TimerManager;
import javastory.tools.Randomizer;
import javastory.tools.packets.ChannelPackets;
public final class DamageParse {
private final static int[] charges = { 1211005, 1211006 };
private DamageParse() {
}
public static void applyAttack(final AttackInfo attack, final ISkill theSkill, final ChannelCharacter player, int attackCount,
final double maxDamagePerMonster, final StatEffect effect, final AttackType attack_type) {
if (!player.isAlive()) {
player.getCheatTracker().registerOffense(CheatingOffense.ATTACKING_WHILE_DEAD);
return;
}
player.getCheatTracker().checkAttack(attack.skill, attack.lastAttackTickCount);
if (attack.skill != 0) {
if (effect == null) {
player.getClient().write(ChannelPackets.enableActions());
return;
}
if (Skills.isMulungSkill(attack.skill)) {
if (player.getMapId() / 10000 != 92502) {
// AutobanManager.getInstance().autoban(player.getClient(),
// "Using Mu Lung dojo skill out of dojo maps.");
} else {
player.mulung_EnergyModify(false);
}
}
if (attack.targets > effect.getMobCount()) { // Must be done here,
// since NPE with
// normal atk
player.getCheatTracker().registerOffense(CheatingOffense.MISMATCHING_BULLETCOUNT);
return;
}
}
if (attack.hits > attackCount) {
if (attack.skill != 4211006) {
player.getCheatTracker().registerOffense(CheatingOffense.MISMATCHING_BULLETCOUNT);
return;
}
}
int totDamage = 0;
final GameMap map = player.getMap();
if (attack.skill == 4211006) { // meso explosion
for (final AttackPair oned : attack.allDamage) {
if (oned.attack != null) {
continue;
}
final GameMapObject mapobject = map.getMapObject(oned.objectid);
if (mapobject != null && mapobject.getType() == GameMapObjectType.ITEM) {
final GameMapItem mapitem = (GameMapItem) mapobject;
if (mapitem.getMeso() > 0) {
if (mapitem.isPickedUp()) {
return;
}
map.removeMapObject(mapitem);
map.broadcastMessage(ChannelPackets.explodeDrop(mapitem.getObjectId()));
mapitem.setPickedUp(true);
} else {
player.getCheatTracker().registerOffense(CheatingOffense.ETC_EXPLOSION);
return;
}
} else {
player.getCheatTracker().registerOffense(CheatingOffense.EXPLODING_NONEXISTANT);
return; // etc explosion, exploding nonexistant things, etc.
}
}
}
int fixeddmg, totDamageToOneMonster;
final ActivePlayerStats stats = player.getStats();
int CriticalDamage = stats.passive_sharpeye_percent();
final Integer SharpEye = player.getBuffedSkill_Y(BuffStat.SHARP_EYES);
if (SharpEye != null) {
CriticalDamage += SharpEye - 100; // Additional damage in percentage
}
final Integer SharpEye_ = player.getBuffedSkill_Y(BuffStat.THORNS);
if (SharpEye_ != null) {
CriticalDamage += SharpEye_ - 100; // Additional damage in
// percentage
}
byte ShdowPartnerAttackPercentage = 0;
if (attack_type == AttackType.RANGED_WITH_SHADOWPARTNER || attack_type == AttackType.NON_RANGED_WITH_MIRROR) {
final ISkill SP;
if (attack_type == AttackType.NON_RANGED_WITH_MIRROR) {
SP = SkillInfoProvider.getSkill(4331002);
} else {
switch (player.getJobId()) {
case 1410: // NightWalker
case 1411:
case 1412:
SP = SkillInfoProvider.getSkill(14111000);
break;
default:
SP = SkillInfoProvider.getSkill(4111002);
break;
} // x = normal atk, y = skill
}
final StatEffect shadowPartnerEffect = SP.getEffect(player.getCurrentSkillLevel(SP));
if (attack.skill != 0) {
ShdowPartnerAttackPercentage = (byte) shadowPartnerEffect.getY();
} else {
ShdowPartnerAttackPercentage = (byte) shadowPartnerEffect.getX();
}
attackCount /= 2; // hack xD
}
byte overallAttackCount; // Tracking of Shadow Partner additional
// damage.
double maxDamagePerHit = 0;
Monster monster;
MobInfo monsterstats;
boolean Tempest;
for (final AttackPair oned : attack.allDamage) {
monster = map.getMonsterByOid(oned.objectid);
if (monster != null) {
totDamageToOneMonster = 0;
monsterstats = monster.getStats();
fixeddmg = monsterstats.getFixedDamage();
Tempest = monster.getStatusSourceID(MonsterStatus.FREEZE) == 21120006;
if (!Tempest) {
if (!monster.isBuffed(MonsterStatus.WEAPON_IMMUNITY)) {
maxDamagePerHit = CalculateMaxWeaponDamagePerHit(player, monster, attack, theSkill, effect, maxDamagePerMonster, CriticalDamage);
} else {
maxDamagePerHit = 1;
}
}
overallAttackCount = 0; // Tracking of Shadow Partner additional
// damage.
for (Integer eachd : oned.attack) {
overallAttackCount++;
if (overallAttackCount - 1 == attackCount) { // Is a Shadow
// partner
// hit so
// let's
// divide it
// once
maxDamagePerHit = maxDamagePerHit / 100 * ShdowPartnerAttackPercentage;
}
// System.out.println("Client damage : " + eachd +
// " Server : " + maxDamagePerHit);
if (fixeddmg != -1) {
if (monsterstats.getOnlyNoramlAttack()) {
eachd = attack.skill != 0 ? 0 : fixeddmg;
} else {
eachd = fixeddmg;
}
} else {
if (monsterstats.getOnlyNoramlAttack()) {
eachd = attack.skill != 0 ? 0 : Math.min(eachd, (int) maxDamagePerHit); // Convert
// to
// server
// calculated
// damage
} else {
if (Tempest) { // Monster buffed with Tempest
if (eachd > monster.getMobMaxHp()) {
eachd = monster.getMobMaxHp();
player.getCheatTracker().registerOffense(CheatingOffense.HIGH_DAMAGE);
}
} else {
if (eachd > maxDamagePerHit) {
player.getCheatTracker().registerOffense(CheatingOffense.HIGH_DAMAGE);
if (eachd > maxDamagePerHit * 2) {
eachd = (int) maxDamagePerHit; // Convert
// to
// server
// calculated
// damage
player.getCheatTracker().registerOffense(CheatingOffense.HIGH_DAMAGE_3);
}
}
}
}
}
totDamageToOneMonster += eachd;
}
totDamage += totDamageToOneMonster;
player.checkMonsterAggro(monster);
if (player.getPosition().distanceSq(monster.getPosition()) > 400000.0) { // 600^2,
// 550
// is
// approximatly
// the
// range
// of
// ultis
player.getCheatTracker().registerOffense(CheatingOffense.ATTACK_FARAWAY_MONSTER); // ,
// Double.toString(Math.sqrt(distance))
}
// pickpocket
if (player.getBuffedValue(BuffStat.PICKPOCKET) != null) {
switch (attack.skill) {
case 0:
case 4001334:
case 4201005:
case 4211002:
case 4211004:
case 4221003:
case 4221007:
handlePickPocket(player, monster, oned);
break;
}
}
if (totDamageToOneMonster > 0) {
if (attack.skill == 3221007) {
if (player.isOnDMG()) {
player.sendNotice(5, "Damage: " + player.getSnipeDamage());
}
monster.damage(player, player.getSnipeDamage(), true);
} else {
if (totDamageToOneMonster >= 199999) {
// HACK
// Damage formula
totDamageToOneMonster = Math.min(ChannelCharacter.damageCap, Math.max(totDamageToOneMonster, totDamageToOneMonster
* (player.getStats().getTotalWatk() / 50)));
if (player.isOnDMG()) {
player.sendNotice(5, "Damage: " + totDamageToOneMonster);
}
}
monster.damage(player, totDamageToOneMonster, true);
}
// effects
switch (attack.skill) {
case 4101005: // drain
case 5111004: { // Energy Drain
stats.setHp(stats.getHp()
+ Math.min(monster.getMobMaxHp(), Math.min((int) ((double) totDamage
* (double) theSkill.getEffect(player.getCurrentSkillLevel(theSkill)).getX() / 100.0), stats.getMaxHp() / 2)), true);
break;
}
case 1311005: { // Sacrifice
final int remainingHP = stats.getHp() - totDamage * effect.getX() / 100;
stats.setHp(remainingHP > 1 ? (int) 1 : remainingHP);
break;
}
case 5211006:
case 22151002: // killer wing
case 5220011: {// homing
player.setLinkedMonsterId(monster.getObjectId());
break;
}
case 4301001:
case 4311002:
case 4311003:
case 4331000:
case 4331004:
case 4331005:
case 4341005:
case 4221007: // Boomerang Stab
case 4221001: // Assasinate
case 4211002: // Assulter
case 4201005: // Savage Blow
case 4001002: // Disorder
case 4001334: // Double Stab
case 4121007: // Triple Throw
case 4111005: // Avenger
case 4001344: { // Lucky Seven
// Venom
final ISkill skill = SkillInfoProvider.getSkill(4120005);
final ISkill skill2 = SkillInfoProvider.getSkill(4220005);
final ISkill skill3 = SkillInfoProvider.getSkill(4340001);
if (player.getCurrentSkillLevel(skill) > 0) {
final StatEffect venomEffect = skill.getEffect(player.getCurrentSkillLevel(skill));
MonsterStatusEffect monsterStatusEffect;
for (int i = 0; i < attackCount; i++) {
if (venomEffect.makeChanceResult()) {
if (monster.getVenomMulti() < 3) {
monster.setVenomMulti((byte) (monster.getVenomMulti() + 1));
monsterStatusEffect = new MonsterStatusEffect(Collections.singletonMap(MonsterStatus.POISON, 1), skill, null, false);
monster.applyStatus(player, monsterStatusEffect, false, venomEffect.getDuration(), true);
}
}
}
} else if (player.getCurrentSkillLevel(skill2) > 0) {
final StatEffect venomEffect = skill2.getEffect(player.getCurrentSkillLevel(skill2));
MonsterStatusEffect monsterStatusEffect;
for (int i = 0; i < attackCount; i++) {
if (venomEffect.makeChanceResult()) {
if (monster.getVenomMulti() < 3) {
monster.setVenomMulti((byte) (monster.getVenomMulti() + 1));
monsterStatusEffect = new MonsterStatusEffect(Collections.singletonMap(MonsterStatus.POISON, 1), skill2, null, false);
monster.applyStatus(player, monsterStatusEffect, false, venomEffect.getDuration(), true);
}
}
}
} else if (player.getCurrentSkillLevel(skill3) > 0) {
final StatEffect venomEffect = skill3.getEffect(player.getCurrentSkillLevel(skill3));
MonsterStatusEffect monsterStatusEffect;
for (int i = 0; i < attackCount; i++) {
if (venomEffect.makeChanceResult()) {
if (monster.getVenomMulti() < 3) {
monster.setVenomMulti((byte) (monster.getVenomMulti() + 1));
monsterStatusEffect = new MonsterStatusEffect(Collections.singletonMap(MonsterStatus.POISON, 1), skill3, null, false);
monster.applyStatus(player, monsterStatusEffect, false, venomEffect.getDuration(), true);
}
}
}
}
break;
}
case 21000002: // Double attack
case 21100001: // Triple Attack
case 21100002: // Pole Arm Push
case 21100004: // Pole Arm Smash
case 21110002: // Full Swing
case 21110003: // Pole Arm Toss
case 21110004: // Fenrir Phantom
case 21110006: // Whirlwind
case 21110007: // (hidden) Full Swing - Double Attack
case 21110008: // (hidden) Full Swing - Triple Attack
case 21120002: // Overswing
case 21120005: // Pole Arm finale
case 21120006: // Tempest
case 21120009: // (hidden) Overswing - Double Attack
case 21120010: { // (hidden) Overswing - Triple Attack
if (player.getBuffedValue(BuffStat.WK_CHARGE) != null) {
final ISkill skill = SkillInfoProvider.getSkill(21111005);
final StatEffect eff = skill.getEffect(player.getCurrentSkillLevel(skill));
monster.applyStatus(player, new MonsterStatusEffect(Collections.singletonMap(MonsterStatus.SPEED, eff.getX()), skill, null, false),
false, eff.getY() * 1000, false);
}
if (player.getBuffedValue(BuffStat.BODY_PRESSURE) != null) {
final ISkill skill = SkillInfoProvider.getSkill(21101003);
final StatEffect eff = skill.getEffect(player.getCurrentSkillLevel(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(BuffStat.COMBO_DRAIN) != null) {
final ISkill skill = SkillInfoProvider.getSkill(21100005);
final ActivePlayerStats stat = player.getStats();
stat.setHp(stat.getHp() + totDamage * skill.getEffect(player.getCurrentSkillLevel(skill)).getX() / 100, true);
}
break;
}
default: // passives attack bonuses
if (totDamageToOneMonster > 0) {
if (player.getBuffedValue(BuffStat.BLIND) != null) {
final ISkill skill = SkillInfoProvider.getSkill(3221006);
final StatEffect eff = skill.getEffect(player.getCurrentSkillLevel(skill));
if (eff.makeChanceResult()) {
final MonsterStatusEffect monsterStatusEffect = new MonsterStatusEffect(Collections.singletonMap(MonsterStatus.ACC, eff
.getX()), skill, null, false);
monster.applyStatus(player, monsterStatusEffect, false, eff.getY() * 1000, false);
}
} else if (player.getBuffedValue(BuffStat.HAMSTRING) != null) {
final ISkill skill = SkillInfoProvider.getSkill(3121007);
final StatEffect eff = skill.getEffect(player.getCurrentSkillLevel(skill));
if (eff.makeChanceResult()) {
final MonsterStatusEffect monsterStatusEffect = new MonsterStatusEffect(Collections.singletonMap(MonsterStatus.SPEED, eff
.getX()), skill, null, false);
monster.applyStatus(player, monsterStatusEffect, false, eff.getY() * 1000, false);
}
} else if (player.getJobId() == 121) { // WHITEKNIGHT
for (final int charge : charges) {
final ISkill skill = SkillInfoProvider.getSkill(charge);
if (player.isBuffFrom(BuffStat.WK_CHARGE, skill)) {
final MonsterStatusEffect monsterStatusEffect = new MonsterStatusEffect(Collections.singletonMap(MonsterStatus.FREEZE,
1), skill, null, false);
monster.applyStatus(player, monsterStatusEffect, false,
skill.getEffect(player.getCurrentSkillLevel(skill)).getY() * 2000, false);
break;
}
}
}
}
break;
}
if (effect != null && effect.getMonsterStati().size() > 0) {
if (effect.makeChanceResult()) {
monster.applyStatus(player, new MonsterStatusEffect(effect.getMonsterStati(), theSkill, null, false), effect.isPoison(), effect
.getDuration(), false);
}
}
}
}
}
if (attack.skill != 0 && (attack.targets > 0 || attack.skill != 4331003 && attack.skill != 4341002)) {
effect.applyTo(player, attack.position);
}
if (totDamage > 1) {
final CheatTracker tracker = player.getCheatTracker();
tracker.setAttacksWithoutHit(true);
final int offenseLimit;
switch (attack.skill) {
case 3121004:
case 5221004:
offenseLimit = 100;
break;
default:
offenseLimit = 500;
break;
}
if (tracker.getAttacksWithoutHit() > offenseLimit) {
tracker.registerOffense(CheatingOffense.ATTACK_WITHOUT_GETTING_HIT, Integer.toString(tracker.getAttacksWithoutHit()));
}
}
}
public static void applyAttackMagic(final AttackInfo attack, final ISkill theSkill, final ChannelCharacter player, final StatEffect effect) {
player.getCheatTracker().checkAttack(attack.skill, attack.lastAttackTickCount);
if (effect == null) {
player.getClient().write(ChannelPackets.enableActions());
return;
}
// if (attack.skill != 2301002) { // heal is both an attack and a special move (healing) so we'll let the whole applying magic live in the special move part
// effect.applyTo(player);
// }
if (attack.hits > effect.getAttackCount() || attack.targets > effect.getMobCount()) {
player.getCheatTracker().registerOffense(CheatingOffense.MISMATCHING_BULLETCOUNT);
return;
}
final ActivePlayerStats stats = player.getStats();
// double minDamagePerHit;
double maxDamagePerHit;
if (attack.skill == 2301002) {
maxDamagePerHit = 30000;
} else if (attack.skill == 1000 || attack.skill == 10001000 || attack.skill == 20001000 || attack.skill == 20011000) {
maxDamagePerHit = 40;
} else {
// Minimum Damage = BA * (INT * 0.5 + (MATK*0.058)² + MATK * 3.3)
// /100
// Maximum Damage = BA * (INT * 0.5 + (MATK*0.058)² + (Mastery *
// 0.9 * MATK) * 3.3) /100
final double v75 = effect.getMatk() * 0.058;
// minDamagePerHit = stats.getTotalMagic() * (stats.getInt() * 0.5 + (v75 * v75) + effect.getMatk() * 3.3) / 100;
maxDamagePerHit = stats.getTotalMagic() * (stats.getInt() * 0.5 + v75 * v75 + effect.getMastery() * 0.9 * effect.getMatk() * 3.3) / 100;
}
maxDamagePerHit *= 1.04; // Avoid any errors for now
final AttackNature element = player.getBuffedValue(BuffStat.ELEMENT_RESET) != null ? AttackNature.NEUTRAL : theSkill.getElement();
double MaxDamagePerHit = 0;
int totDamageToOneMonster, totDamage = 0, fixeddmg;
byte overallAttackCount;
boolean Tempest;
MobInfo monsterstats;
int CriticalDamage = stats.passive_sharpeye_percent();
final Integer SharpEye = player.getBuffedSkill_Y(BuffStat.SHARP_EYES);
if (SharpEye != null) {
CriticalDamage += SharpEye - 100; // Additional damage in percentage
}
final Integer SharpEye_ = player.getBuffedSkill_Y(BuffStat.THORNS);
if (SharpEye_ != null) {
CriticalDamage += SharpEye_ - 100; // Additional damage in
// percentage
}
final ISkill eaterSkill = SkillInfoProvider.getSkill(Skills.getMpEaterForJob(player.getJobId()));
final int eaterLevel = player.getCurrentSkillLevel(eaterSkill);
final GameMap map = player.getMap();
for (final AttackPair oned : attack.allDamage) {
final Monster monster = map.getMonsterByOid(oned.objectid);
if (monster != null) {
Tempest = monster.getStatusSourceID(MonsterStatus.FREEZE) == 21120006;
totDamageToOneMonster = 0;
monsterstats = monster.getStats();
fixeddmg = monsterstats.getFixedDamage();
if (!Tempest) {
if (!monster.isBuffed(MonsterStatus.MAGIC_IMMUNITY)) {
MaxDamagePerHit = CalculateMaxMagicDamagePerHit(player, theSkill, monster, monsterstats, stats, element, CriticalDamage,
maxDamagePerHit);
} else {
maxDamagePerHit = 1;
}
}
overallAttackCount = 0;
for (Integer eachd : oned.attack) {
overallAttackCount++;
/*
* if (attack.skill == 2221006) { // Chain Lightning
* maxDamagePerMob *= (byte) 0.7 ^ (byte)
* (overallAttackCount - 1); maxDamagePerMob *= 3.333 *
* ((byte) (1 - 0.7) ^ (byte) (attack.targets)); }
*/
if (fixeddmg != -1) {
eachd = monsterstats.getOnlyNoramlAttack() ? 0 : fixeddmg; // Magic
// is
// always
// not
// a
// normal
// attack
} else {
if (monsterstats.getOnlyNoramlAttack()) {
eachd = 0; // Magic is always not a normal attack
} else {
// System.out.println("Client damage : " + eachd + " Server : " + MaxDamagePerHit);
if (Tempest) { // Buffed with Tempest
// In special case such as Chain lightning, the
// damage will be reduced from the maxMP.
if (eachd > monster.getMobMaxHp()) {
eachd = monster.getMobMaxHp();
player.getCheatTracker().registerOffense(CheatingOffense.HIGH_DAMAGE_MAGIC);
}
} else {
if (eachd > MaxDamagePerHit) {
// System.out.println("EXCEED!!! Client damage : " + eachd + " Server : " + MaxDamagePerHit);
eachd = (int) MaxDamagePerHit; // Convert to
// server
// calculated
// damage
player.getCheatTracker().registerOffense(CheatingOffense.HIGH_DAMAGE_MAGIC);
}
}
}
}
totDamageToOneMonster += eachd;
}
totDamage += totDamageToOneMonster;
player.checkMonsterAggro(monster);
if (player.getPosition().distanceSq(monster.getPosition()) > 400000.0) { // 600^2,
// 550
// is
// approximatly
// the
// range
// of
// ultis
player.getCheatTracker().registerOffense(CheatingOffense.ATTACK_FARAWAY_MONSTER);
}
if (attack.skill == 2301002 && !monsterstats.getUndead()) {
player.getCheatTracker().registerOffense(CheatingOffense.HEAL_ATTACKING_UNDEAD);
return;
}
if (totDamageToOneMonster > 0) {
if (totDamageToOneMonster >= 199999) {
// HACK
// Damage Formula
totDamageToOneMonster = Math.min(ChannelCharacter.damageCap, Math.max(totDamageToOneMonster, totDamageToOneMonster
* (player.getStats().getTotalMagic() / 50)));
if (player.isOnDMG()) {
player.sendNotice(5, "Damage: " + totDamageToOneMonster);
}
}
monster.damage(player, totDamageToOneMonster, true);
// effects
switch (attack.skill) {
case 2221003:
monster.setTempEffectiveness(AttackNature.FIRE, theSkill.getEffect(player.getCurrentSkillLevel(theSkill)).getDuration());
break;
case 2121003:
monster.setTempEffectiveness(AttackNature.ICE, theSkill.getEffect(player.getCurrentSkillLevel(theSkill)).getDuration());
break;
}
if (effect != null && effect.getMonsterStati().size() > 0) {
if (effect.makeChanceResult()) {
monster.applyStatus(player, new MonsterStatusEffect(effect.getMonsterStati(), theSkill, null, false), effect.isPoison(), effect
.getDuration(), false);
}
}
if (eaterLevel > 0) {
eaterSkill.getEffect(eaterLevel).applyPassive(player, monster);
}
}
}
}
if (attack.skill != 2301002) {
effect.applyTo(player);
}
if (totDamage > 1) {
final CheatTracker tracker = player.getCheatTracker();
tracker.setAttacksWithoutHit(true);
if (tracker.getAttacksWithoutHit() > 500) {
tracker.registerOffense(CheatingOffense.ATTACK_WITHOUT_GETTING_HIT, Integer.toString(tracker.getAttacksWithoutHit()));
}
}
}
private static double CalculateMaxMagicDamagePerHit(final ChannelCharacter chr, final ISkill skill, final Monster monster, final MobInfo mobstats,
final ActivePlayerStats stats, final AttackNature elem, final Integer sharpEye, final double maxDamagePerMonster) {
final int dLevel = Math.max(mobstats.getLevel() - chr.getLevel(), 0);
final int Accuracy = stats.getTotalInt() / 10 + stats.getTotalLuk() / 10;
final int MinAccuracy = mobstats.getEvasion() * (dLevel * 2 + 51) / 120;
// FullAccuracy = Avoid * (dLevel * 2 + 51) / 50
// miss :P or HACK :O
if (MinAccuracy > Accuracy && skill.getId() != 1000 && skill.getId() != 10001000 && skill.getId() != 20001000 && skill.getId() != 20011000) {
return 0;
}
double elemMaxDamagePerMob;
switch (monster.getEffectiveness(elem)) {
case IMMUNE:
elemMaxDamagePerMob = 1;
break;
case NORMAL:
elemMaxDamagePerMob = ElementalStaffAttackBonus(elem, maxDamagePerMonster / 100 * stats.element_amp_percent, stats);
break;
case WEAK:
elemMaxDamagePerMob = ElementalStaffAttackBonus(elem, maxDamagePerMonster * 1.5 / 100 * stats.element_amp_percent, stats);
break;
case STRONG:
elemMaxDamagePerMob = ElementalStaffAttackBonus(elem, maxDamagePerMonster * 0.5 / 100 * stats.element_amp_percent, stats);
break;
default:
throw new RuntimeException("Unknown enum constant");
}
// Calculate monster magic def
// Min damage = (MIN before defense) - MDEF*.6
// Max damage = (MAX before defense) - MDEF*.5
elemMaxDamagePerMob -= mobstats.getMagicDefense() * 0.5;
// Calculate Sharp eye bonus
if (sharpEye != null) {
elemMaxDamagePerMob += elemMaxDamagePerMob / 100 * sharpEye;
}
// if (skill.isChargeSkill()) {
// elemMaxDamagePerMob = (float) ((90 * ((System.currentTimeMillis() - chr.getKeyDownSkill_Time()) / 1000) + 10) * elemMaxDamagePerMob * 0.01);
// }
if (skill.isChargeSkill() && chr.getKeyDownSkill_Time() == 0) {
return 0;
}
switch (skill.getId()) {
case 1000:
case 10001000:
case 20001000:
case 20011000:
elemMaxDamagePerMob = 40;
break;
}
if (elemMaxDamagePerMob > ChannelCharacter.getDamageCap()) {
elemMaxDamagePerMob = ChannelCharacter.getDamageCap();
} else if (elemMaxDamagePerMob < 0) {
elemMaxDamagePerMob = 1;
}
return elemMaxDamagePerMob;
}
private static double ElementalStaffAttackBonus(final AttackNature elem, final double elemMaxDamagePerMob, final ActivePlayerStats stats) {
switch (elem) {
case FIRE:
return elemMaxDamagePerMob / 100 * stats.element_fire;
case ICE:
return elemMaxDamagePerMob / 100 * stats.element_ice;
case LIGHTING:
return elemMaxDamagePerMob / 100 * stats.element_light;
case POISON:
return elemMaxDamagePerMob / 100 * stats.element_psn;
default:
return elemMaxDamagePerMob / 100 * stats.def;
}
}
private static void handlePickPocket(final ChannelCharacter player, final Monster mob, final AttackPair oned) {
final int maxmeso = player.getBuffedValue(BuffStat.PICKPOCKET).intValue();
final ISkill skill = SkillInfoProvider.getSkill(4211003);
final StatEffect s = skill.getEffect(player.getCurrentSkillLevel(skill));
for (final Integer eachd : oned.attack) {
if (s.makeChanceResult()) {
TimerManager.getInstance().schedule(new Runnable() {
@Override
public void run() {
final int amount = Math.min((int) Math.max((double) eachd / (double) 20000 * maxmeso, 1), maxmeso);
final int x = (int) (mob.getPosition().getX() + Randomizer.nextInt(100) - 50);
final int y = (int) mob.getPosition().getY();
final Point position = new Point(x, y);
player.getMap().spawnMesoDrop(amount, position, mob, player, true, ItemDropType.DEFAULT);
}
}, 100);
}
}
}
private static double CalculateMaxWeaponDamagePerHit(final ChannelCharacter player, final Monster monster, final AttackInfo attack, final ISkill theSkill,
final StatEffect attackEffect, double maximumDamageToMonster, final Integer CriticalDamagePercent) {
if (player.getMapId() / 1000000 == 914) { // aran
return 199999;
}
AttackNature element = AttackNature.NEUTRAL;
if (theSkill != null) {
element = theSkill.getElement();
switch (theSkill.getId()) {
case 1000:
case 10001000:
case 20001000:
case 20011000:
maximumDamageToMonster = 40;
break;
case 3221007: // Sniping
maximumDamageToMonster = ChannelCharacter.getDamageCap();
break;
case 4211006: // Meso Explosion
maximumDamageToMonster = ChannelCharacter.getDamageCap();
break;
/*
* case 4221001: // Assasinate maximumDamageToMonster = 400000;
* break;
*/
case 1009: // Bamboo Trust
case 10001009:
case 20001009:
case 20011009:
maximumDamageToMonster = monster.getStats().isBoss() ? monster.getMobMaxHp() / 30 * 100 : monster.getMobMaxHp();
break;
case 3211006: // Sniper Strafe
if (monster.getStatusSourceID(MonsterStatus.FREEZE) == 3211003) { // blizzard
// in
// effect
maximumDamageToMonster = monster.getHp();
}
break;
}
}
if (player.getBuffedValue(BuffStat.WK_CHARGE) != null) {
final int chargeSkillId = player.getBuffSource(BuffStat.WK_CHARGE);
switch (chargeSkillId) {
case 1211003:
case 1211004:
element = AttackNature.FIRE;
break;
case 1211005:
case 1211006:
case 21111005:
element = AttackNature.ICE;
break;
case 1211007:
case 1211008:
case 15101006:
element = AttackNature.LIGHTING;
break;
case 1221003:
case 1221004:
case 11111007:
element = AttackNature.HOLY;
break;
case 12101005:
element = AttackNature.NEUTRAL;
break;
default:
throw new RuntimeException("Unknown enum constant");
}
final ISkill skill = SkillInfoProvider.getSkill(chargeSkillId);
maximumDamageToMonster *= skill.getEffect(player.getCurrentSkillLevel(skill)).getDamage() / 100.0;
}
double elementalMaxDamagePerMonster;
if (element != AttackNature.NEUTRAL) {
double elementalEffect;
switch (attack.skill) {
case 3211003:
case 3111003: // inferno and blizzard
elementalEffect = attackEffect.getX() / 200.0;
break;
default:
elementalEffect = 0.5;
break;
}
switch (monster.getEffectiveness(element)) {
case IMMUNE:
elementalMaxDamagePerMonster = 1;
break;
case NORMAL:
elementalMaxDamagePerMonster = maximumDamageToMonster;
break;
case WEAK:
elementalMaxDamagePerMonster = maximumDamageToMonster * (1.0 + elementalEffect);
break;
case STRONG:
elementalMaxDamagePerMonster = maximumDamageToMonster * (1.0 - elementalEffect);
break;
default:
throw new RuntimeException("Unknown enum constant");
}
} else {
elementalMaxDamagePerMonster = maximumDamageToMonster;
}
// Calculate mob def
final short moblevel = monster.getStats().getLevel();
final short d = moblevel > player.getLevel() ? (short) (moblevel - player.getLevel()) : 0;
elementalMaxDamagePerMonster = elementalMaxDamagePerMonster * (1 - 0.01 * d) - monster.getStats().getPhysicalDefense() * 0.5;
// Calculate passive bonuses + Sharp Eye
elementalMaxDamagePerMonster += elementalMaxDamagePerMonster / 100 * CriticalDamagePercent;
// if (theSkill.isChargeSkill()) {
// elementalMaxDamagePerMonster = (double) (90 * (System.currentTimeMillis() - player.getKeyDownSkill_Time()) / 2000 + 10) * elementalMaxDamagePerMonster * 0.01;
// }
if (theSkill != null && theSkill.isChargeSkill() && player.getKeyDownSkill_Time() == 0) {
return 0;
}
if (elementalMaxDamagePerMonster > ChannelCharacter.getDamageCap()) {
elementalMaxDamagePerMonster = ChannelCharacter.getDamageCap();
} else if (elementalMaxDamagePerMonster < 0) {
elementalMaxDamagePerMonster = 1;
}
return elementalMaxDamagePerMonster;
}
public static AttackInfo parseDmgMa(final PacketReader lea) throws PacketFormatException {
final AttackInfo ret = new AttackInfo();
lea.skip(1);
final boolean unkk = lea.readByte() == -1;
lea.skip(unkk ? 7 : 8);
ret.tbyte = lea.readByte();
ret.targets = (byte) (ret.tbyte >>> 4 & 0xF);
ret.hits = (byte) (ret.tbyte & 0xF);
lea.skip(8); // ?
ret.skill = lea.readInt();
lea.skip(16); // ORDER [4] bytes on v.79, [4] bytes on v.80, [1] byte on
// v.82
switch (ret.skill) {
case 2121001: // Big Bang
case 2221001:
case 2321001:
case 22121000: // breath
case 22151001:
ret.charge = lea.readInt();
break;
default:
ret.charge = -1;
break;
}
lea.skip(1);
ret.display = lea.readByte(); // Always zero?
ret.animation = lea.readByte();
ret.speed = lea.readByte(); // Confirmed
lea.skip(1); // Weapon subclass
ret.lastAttackTickCount = lea.readInt(); // Ticks
lea.skip(4); // 0
int oid, damage;
List<Integer> allDamageNumbers;
ret.allDamage = Lists.newArrayList();
for (int i = 0; i < ret.targets; i++) {
oid = lea.readInt();
lea.skip(14); // [1] Always 6?, [3] unk, [4] Pos1, [4] Pos2, [2]
// seems to change randomly for some attack
allDamageNumbers = Lists.newArrayList();
for (int j = 0; j < ret.hits; j++) {
damage = lea.readInt();
allDamageNumbers.add(Integer.valueOf(damage));
}
lea.skip(4); // CRC of monster [Wz Editing]
ret.allDamage.add(new AttackPair(Integer.valueOf(oid), allDamageNumbers));
}
return ret;
}
public static AttackInfo parseDmgM(final PacketReader lea) throws PacketFormatException {
final AttackInfo ret = new AttackInfo();
lea.skip(1);
final boolean unkk = lea.readByte() == -1;
lea.skip(unkk ? 7 : 8);
ret.tbyte = lea.readByte();
ret.targets = (byte) (ret.tbyte >>> 4 & 0xF);
ret.hits = (byte) (ret.tbyte & 0xF);
lea.skip(8);
ret.skill = lea.readInt();
// ORDER [4] bytes on v.79, [4] bytes on v.80, [1] byte on v.82
lea.skip(16);
switch (ret.skill) {
case 4341002: // Final cut
case 5101004: // Corkscrew
case 5201002: // Gernard
case 14111006: // Poison bomb
case 15101003: // Cygnus corkscrew
ret.charge = lea.readInt();
break;
default:
ret.charge = 0;
break;
}
// ORDER [4] bytes on v.79, [4] bytes on v.80, [1] byte on v.82
lea.skip(1);
ret.display = lea.readByte(); // Always zero?
ret.animation = lea.readByte();
lea.skip(1); // Weapon class
ret.speed = lea.readByte(); // Confirmed
ret.lastAttackTickCount = lea.readInt(); // Ticks
lea.skip(4);
ret.allDamage = Lists.newArrayList();
if (ret.skill == 4211006) {
// Meso Explosion
return parseMesoExplosion(lea, ret);
}
int oid, damage;
List<Integer> allDamageNumbers;
for (int i = 0; i < ret.targets; i++) {
oid = lea.readInt();
// System.out.println(tools.HexTool.toString(lea.readBytes(14)));
// [1] Always 6?, [3] unk, [4] Pos1, [4] Pos2, [2] seems to change
// randomly for some attack
lea.skip(14);
allDamageNumbers = Lists.newArrayList();
for (int j = 0; j < ret.hits; j++) {
damage = lea.readInt();
// System.out.println("Damage: " + damage);
allDamageNumbers.add(Integer.valueOf(damage));
}
// CRC of monster [Wz Editing]
lea.skip(4);
ret.allDamage.add(new AttackPair(oid, allDamageNumbers));
}
ret.position = lea.readVector();
return ret;
}
public static AttackInfo parseDmgR(final PacketReader lea) throws PacketFormatException {
final AttackInfo ret = new AttackInfo();
lea.skip(1);
final boolean unkk = lea.readByte() == -1;
lea.skip(unkk ? 7 : 8);
ret.tbyte = lea.readByte();
ret.targets = (byte) (ret.tbyte >>> 4 & 0xF);
ret.hits = (byte) (ret.tbyte & 0xF);
lea.skip(8);
ret.skill = lea.readInt();
// ORDER [4] bytes on v.79, [4] bytes on v.80, [1] byte on v.82
lea.skip(16);
switch (ret.skill) {
case 3121004: // Hurricane
case 3221001: // Pierce
case 5221004: // Rapidfire
case 13111002: // Cygnus Hurricane
lea.skip(4); // extra 4 bytes
break;
}
// ORDER [4] bytes on v.79, [4] bytes on v.80, [1] byte on v.82
lea.skip(1);
ret.display = lea.readByte(); // Always zero?
ret.animation = lea.readByte();
lea.skip(1); // Weapon class
ret.speed = lea.readByte(); // Confirmed
ret.lastAttackTickCount = lea.readInt(); // Ticks
lea.skip(4); // 0
ret.slot = (byte) lea.readShort();
ret.csstar = (byte) lea.readShort();
ret.AOE = lea.readByte(); // is AOE or not, TT/ Avenger = 41, Showdown =
// 0
int damage, oid;
List<Integer> allDamageNumbers;
ret.allDamage = Lists.newArrayList();
for (int i = 0; i < ret.targets; i++) {
oid = lea.readInt();
// System.out.println(tools.HexTool.toString(lea.readBytes(14)));
// [1] Always 6?, [3] unk, [4] Pos1, [4] Pos2, [2] seems to change
// randomly for some attack
lea.skip(14);
allDamageNumbers = Lists.newArrayList();
for (int j = 0; j < ret.hits; j++) {
damage = lea.readInt();
allDamageNumbers.add(Integer.valueOf(damage));
}
// CRC of monster [Wz Editing]
lea.skip(4);
// System.out.println(tools.HexTool.toString(lea.readBytes(4)));
ret.allDamage.add(new AttackPair(Integer.valueOf(oid), allDamageNumbers));
}
lea.skip(4);
ret.position = lea.readVector();
return ret;
}
public static AttackInfo parseMesoExplosion(final PacketReader lea, final AttackInfo ret) throws PacketFormatException {
byte bullets;
if (ret.hits == 0) {
lea.skip(4);
bullets = lea.readByte();
for (int j = 0; j < bullets; j++) {
ret.allDamage.add(new AttackPair(Integer.valueOf(lea.readInt()), null));
lea.skip(1);
}
lea.skip(2); // 8F 02
return ret;
}
int oid;
List<Integer> allDamageNumbers;
for (int i = 0; i < ret.targets; i++) {
oid = lea.readInt();
lea.skip(12);
bullets = lea.readByte();
allDamageNumbers = Lists.newArrayList();
for (int j = 0; j < bullets; j++) {
allDamageNumbers.add(Integer.valueOf(lea.readInt()));
}
ret.allDamage.add(new AttackPair(Integer.valueOf(oid), allDamageNumbers));
lea.skip(4); // C3 8F 41 94, 51 04 5B 01
}
lea.skip(4);
bullets = lea.readByte();
for (int j = 0; j < bullets; j++) {
ret.allDamage.add(new AttackPair(Integer.valueOf(lea.readInt()), null));
lea.skip(1);
}
lea.skip(2); // 8F 02/ 63 02
return ret;
}
}