// Author: wackyracer / Joren McGrew // This is the formula for maximum melee damage dealt by any class. // TODO: This formula does not take the 3rd job skills into account yet. TODO // This formula is from: https://web.archive.org/web/20081009190310/http://www.southperry.net:80/forums/showthread.php?t=855&page=2 // Formula Name: General Formula // MAX = (Primary Stat + Secondary Stat) * Weapon Attack / 100 // I have added elemental damage amplification/reduction to the formula. // I have added mob resistance calculation for damage reduction to the formula. // I have added mob level difference from character (if mob level is higher than character's). // My formula looks like... // MAX = ((Primary Stat + Secondary Stat) * Weapon Attack / 100) * Element Modifier * Level Difference - (Mob Weapon Defense * 0.5) public static double MaximumMeleeDamage(Character chr, Mob mob, int Targets = 1, int SkillID = 99) { var MeleeSkill = chr.Skills.GetSkillLevelData(SkillID, out byte MeleeSkillLevel); string WeaponType; double WeaponAttack = chr.PrimaryStats.BuffWeaponAttack.N + chr.Inventory.GetTotalWAttackInEquips(false); try // FAILSAFE { WeaponType = GetWeaponType(chr); // What weapon was used? Sword, Axe, etc.? double PrimaryStat = 0.0; double SecondaryStat = 0.0; switch (WeaponType) { case "1HS": // ONE HANDED SWORD PrimaryStat = chr.PrimaryStats.GetStrAddition() * 4.0; SecondaryStat = chr.PrimaryStats.GetDexAddition(); break; case "1HA": // ONE HANDED AXE case "1HBW": // ONE HANDED BLUNT WEAPON case "WAND": // WAND case "STAFF": // STAFF PrimaryStat = chr.PrimaryStats.GetStrAddition() * 4.4; SecondaryStat = chr.PrimaryStats.GetDexAddition(); break; case "2HS": // TWO HANDED SWORD PrimaryStat = chr.PrimaryStats.GetStrAddition() * 4.6; SecondaryStat = chr.PrimaryStats.GetDexAddition(); break; case "2HA": // TWO HANDED AXE case "2HBW": // TWO HANDED BLUNT WEAPON PrimaryStat = chr.PrimaryStats.GetStrAddition() * 4.8; SecondaryStat = chr.PrimaryStats.GetDexAddition(); break; case "SPEAR": // SPEAR case "PA": // POLE ARM PrimaryStat = chr.PrimaryStats.GetStrAddition() * 5.0; SecondaryStat = chr.PrimaryStats.GetDexAddition(); break; case "DAGGERNT": // NON-THIEF USING DAGGER PrimaryStat = chr.PrimaryStats.GetStrAddition() * 4.0; SecondaryStat = chr.PrimaryStats.GetDexAddition(); break; case "DAGGERT": // THIEF USING DAGGER PrimaryStat = chr.PrimaryStats.GetLukAddition() * 3.6; SecondaryStat = chr.PrimaryStats.GetStrAddition() + chr.PrimaryStats.GetDexAddition(); break; case "BOW": // BOW PrimaryStat = chr.PrimaryStats.GetDexAddition() * 3.4; SecondaryStat = chr.PrimaryStats.GetStrAddition(); break; case "XBOW": // CROSSBOW PrimaryStat = chr.PrimaryStats.GetDexAddition() * 3.6; SecondaryStat = chr.PrimaryStats.GetStrAddition(); break; case "CLAW": // CLAW return((chr.PrimaryStats.GetLukAddition() * 1.0 + chr.PrimaryStats.GetStrAddition() + chr.PrimaryStats.GetDexAddition()) * WeaponAttack / 150.0); case "NONE": // ERROR! default: Program.MainForm.LogAppend("Woops! Something went wrong with depicting the Weapon Type!"); break; } if (MeleeSkill != null) // If the player used a melee skill... { switch (SkillID) { case 1001004: // POWER STRIKE case 4201004: // STEAL return((((PrimaryStat + SecondaryStat) * WeaponAttack / 100.0) * ElementModifier(mob, SkillID) * LevelDisadvantageModifierDmg(chr.PrimaryStats.Level, mob.Data.Level) - (mob.Data.PDD * 0.5)) * MeleeSkill.Damage); case 1001005: // SLASH BLAST return(((((PrimaryStat + SecondaryStat) * WeaponAttack / 100.0) * ElementModifier(mob, SkillID) * LevelDisadvantageModifierDmg(chr.PrimaryStats.Level, mob.Data.Level) - (mob.Data.PDD * 0.5)) * MeleeSkill.Damage) * Targets); case 4001334: // DOUBLE STAB case 4201005: // SAVAGE BLOW return(((((PrimaryStat + SecondaryStat) * WeaponAttack / 100.0) * ElementModifier(mob, SkillID) * LevelDisadvantageModifierDmg(chr.PrimaryStats.Level, mob.Data.Level) - (mob.Data.PDD * 0.5)) * MeleeSkill.Damage) * MeleeSkill.HitCount); default: return(((PrimaryStat + SecondaryStat) * WeaponAttack / 100.0) * ElementModifier(mob, SkillID) * LevelDisadvantageModifierDmg(chr.PrimaryStats.Level, mob.Data.Level) - (mob.Data.PDD * 0.5)); } } else { return(((PrimaryStat + SecondaryStat) * WeaponAttack / 100.0) * ElementModifier(mob, SkillID) * LevelDisadvantageModifierDmg(chr.PrimaryStats.Level, mob.Data.Level) - (mob.Data.PDD * 0.5)); } } catch (Exception melee) { Program.MainForm.LogAppend("MELEE ANTI-CHEAT EXCEPTION: " + melee); } return(0.0); }
// Author: wackyracer / Joren McGrew // This is the formula for maximum damage dealt by regular ranged attacks. // Each class has a different formula for their ranged attacks. // The General Formula is: MAX = (Primary Stat + Secondary Stat) * Weapon Attack / 100 // Including the mob weapon defense resistance calculation, level difference amplification/reduction formulas, and critical strike formula... // Something seems off here. I can smell it. I think Bow/Crossbow Mastery needs to be taken into account but I can't find the formula for it online... // Guess I can only find out with testing... F3. public static double MaximumRangedDamage(Character chr, Mob mob, int SkillID, int StarID, byte Targets, int ClientTotalDamage) { // If they're a Bowman, Thief, or GM... if (chr.PrimaryStats.Job / 100 == 3 || chr.PrimaryStats.Job / 100 == 4 || chr.PrimaryStats.Job / 100 == 5) { // Bow // Primary: DEX * 3.4 // Secondary: STR if (StarID / 1000 == 2060) { double chrSTR = chr.PrimaryStats.GetStrAddition(); double chrDEX = chr.PrimaryStats.GetDexAddition(); double chrWeaponAttack = chr.PrimaryStats.BuffWeaponAttack.N + chr.Inventory.GetTotalWAttackInEquips(true); double MaxDamageWithoutCrit = ((chrDEX * 3.4 + chrSTR) * chrWeaponAttack / 100.0) * LevelDisadvantageModifierDmg(chr.PrimaryStats.Level, mob.Data.Level) - (mob.Data.PDD * 0.5); double MaxDamageWithCrit = ((chrDEX * 3.4 + chrSTR) * chrWeaponAttack / 100.0) * LevelDisadvantageModifierDmg(chr.PrimaryStats.Level, mob.Data.Level) - (mob.Data.PDD * 0.5) * CriticalStrikeModifier(chr); if (ClientTotalDamage <= MaxDamageWithoutCrit) { switch (SkillID) // NEEDS TESTING { case 3001004: // Arrow Blow var chrArrowBlowData = chr.Skills.GetSkillLevelData(3001004, out byte ArrowBlowLevel); return(MaxDamageWithoutCrit * (chrArrowBlowData.Damage / 100.0)); case 3001005: // Double Shot var chrDoubleShotData = chr.Skills.GetSkillLevelData(3001005, out byte DoubleShotLevel); return((MaxDamageWithoutCrit * (chrDoubleShotData.Damage / 100.0)) * 2.0); case 3100001: // Final Attack return(MaxDamageWithoutCrit * (FinalAttackModifier(chr, SkillID))); default: return(MaxDamageWithoutCrit); } } else { switch (SkillID) // NEEDS TESTING { case 3001004: // Arrow Blow var chrArrowBlowData = chr.Skills.GetSkillLevelData(3001004, out byte ArrowBlowLevel); return(MaxDamageWithCrit * (chrArrowBlowData.Damage / 100.0)); case 3001005: // Double Shot var chrDoubleShotData = chr.Skills.GetSkillLevelData(3001005, out byte DoubleShotLevel); return((MaxDamageWithCrit * (chrDoubleShotData.Damage / 100.0)) * 2.0); case 3100001: // Final Attack return(MaxDamageWithCrit * (FinalAttackModifier(chr, SkillID))); default: return(MaxDamageWithCrit); } } } // Crossbow // Primary: DEX * 3.6 // Secondary: STR else if (StarID / 1000 == 2061) { double chrSTR = chr.PrimaryStats.GetStrAddition(); double chrDEX = chr.PrimaryStats.GetDexAddition(); double chrWeaponAttack = chr.PrimaryStats.BuffWeaponAttack.N + chr.Inventory.GetTotalWAttackInEquips(true); double MaxDamageWithoutCrit = ((chrDEX * 3.6 + chrSTR) * chrWeaponAttack / 100.0) * LevelDisadvantageModifierDmg(chr.PrimaryStats.Level, mob.Data.Level) - (mob.Data.PDD * 0.5); double MaxDamageWithCrit = ((chrDEX * 3.6 + chrSTR) * chrWeaponAttack / 100.0) * LevelDisadvantageModifierDmg(chr.PrimaryStats.Level, mob.Data.Level) - (mob.Data.PDD * 0.5) * CriticalStrikeModifier(chr); if (ClientTotalDamage <= MaxDamageWithoutCrit) { switch (SkillID) // NEEDS TESTING { case 3001004: // Arrow Blow var chrArrowBlowData = chr.Skills.GetSkillLevelData(3001004, out byte ArrowBlowLevel); return(MaxDamageWithoutCrit * (chrArrowBlowData.Damage / 100.0)); case 3001005: // Double Shot var chrDoubleShotData = chr.Skills.GetSkillLevelData(3001005, out byte DoubleShotLevel); return((MaxDamageWithoutCrit * (chrDoubleShotData.Damage / 100.0)) * 2.0); case 3200001: // Final Attack return(MaxDamageWithoutCrit * (FinalAttackModifier(chr, SkillID))); case 3201005: // Iron Arrow (needs testing) //var chrIronArrowData = chr.Skills.GetSkillLevelData(3201005, out byte IronArrowLevel); return(MaxDamageWithoutCrit * (10.0 * (1.0 - Math.Pow(0.9, Targets)))); default: return(MaxDamageWithoutCrit); } } else { switch (SkillID) // NEEDS TESTING { case 3001004: // Arrow Blow var chrArrowBlowData = chr.Skills.GetSkillLevelData(3001004, out byte ArrowBlowLevel); return(MaxDamageWithCrit * chrArrowBlowData.Damage); case 3001005: // Double Shot var chrDoubleShotData = chr.Skills.GetSkillLevelData(3001005, out byte DoubleShotLevel); return((MaxDamageWithCrit * chrDoubleShotData.Damage) * 2.0); case 3200001: // Final Attack return(MaxDamageWithCrit * (FinalAttackModifier(chr, SkillID))); case 3201005: // Iron Arrow (needs testing) //var chrIronArrowData = chr.Skills.GetSkillLevelData(3201005, out byte IronArrowLevel); return(MaxDamageWithCrit * (10.0 * (1.0 - Math.Pow(0.9, Targets)))); default: return(MaxDamageWithCrit); } } } // Throwing Stars (Thieves) // Primary: LUK * 3.6 // Secondary: STR + DEX else if (StarID / 10000 == 207) { double chrSTR = chr.PrimaryStats.GetStrAddition(); double chrDEX = chr.PrimaryStats.GetDexAddition(); double chrLUK = chr.PrimaryStats.GetLukAddition(); double chrWeaponAttack = chr.PrimaryStats.BuffWeaponAttack.N + chr.Inventory.GetTotalWAttackInEquips(true); double MaxDamageWithoutCrit = ((chrLUK * 3.6 + (chrSTR + chrDEX)) * chrWeaponAttack / 100.0) * LevelDisadvantageModifierDmg(chr.PrimaryStats.Level, mob.Data.Level) - (mob.Data.PDD * 0.5); double MaxDamageWithCrit = ((chrLUK * 3.6 + (chrSTR + chrDEX)) * chrWeaponAttack / 100.0) * LevelDisadvantageModifierDmg(chr.PrimaryStats.Level, mob.Data.Level) - (mob.Data.PDD * 0.5) * CriticalStrikeModifier(chr); if (chr.PrimaryStats.Job / 10 == 41 || chr.PrimaryStats.Job / 100 == 5) { if (ClientTotalDamage <= MaxDamageWithoutCrit) { // NEEDS TESTING if (SkillID == 4101005) // Drain { var chrDrainData = chr.Skills.GetSkillLevelData(4101005, out byte DrainLevel); return(MaxDamageWithoutCrit * (chrDrainData.Damage / 100.0)); } else { return(MaxDamageWithoutCrit); } } else { if (SkillID == 4101005) // Drain { var chrDrainData = chr.Skills.GetSkillLevelData(4101005, out byte DrainLevel); return(MaxDamageWithCrit * (chrDrainData.Damage / 100.0)); } else { return(MaxDamageWithCrit); } } } else { return(MaxDamageWithoutCrit); } } else { return(0.0); // HAAAAAAAAAAAAAAAAAAAAAAX -throws PC monitor- } } else { return(0.0); // HAAAAAAAAAAAAAAAAAAAAAAX -throws PC monitor- } }
public static void HandleCharacterDamage(Character chr, Packet pr) { //1A FF 03 00 00 00 00 00 00 00 00 04 87 01 00 00 00 sbyte attack = pr.ReadSByte(); int damage = pr.ReadInt(); int reducedDamage = damage; int actualHPEffect = -damage; int actualMPEffect = 0; int healSkillId = 0; Mob mob = null; if (chr.AssertForHack(damage < -1, "Less than -1 (" + damage + ") damage in HandleCharacterDamage")) { return; } if (chr.PrimaryStats.HP == 0) { return; } byte mobSkillId = 0, mobSkillLevel = 0; if (attack <= -2) { mobSkillLevel = pr.ReadByte(); mobSkillId = pr.ReadByte(); // (short >> 8) Trace.WriteLine($"Got a hit with {attack} attack, mobSkillLevel {mobSkillLevel}, mobSkillId {mobSkillId}"); } else { int magicAttackElement = 0; if (pr.ReadBool()) { magicAttackElement = pr.ReadInt(); // 0 = no element (Grendel the Really Old, 9001001) // 1 = Ice (Celion? blue, 5120003) // 2 = Lightning (Regular big Sentinel, 3000000) // 3 = Fire (Fire sentinel, 5200002) } var mobMapId = pr.ReadInt(); var mobId = pr.ReadInt(); mob = chr.Field.GetMob(mobMapId); if (mob == null || mobId != mob.MobID) { return; } // Newer ver: int nCalcDamageMobStatIndex var stance = pr.ReadByte(); var isReflected = pr.ReadBool(); byte reflectHitAction = 0; short reflectX = 0, reflectY = 0; if (isReflected) { reflectHitAction = pr.ReadByte(); reflectX = pr.ReadShort(); reflectY = pr.ReadShort(); } if (chr.PrimaryStats.BuffMagicGuard.HasReferenceId(Constants.Magician.Skills.MagicGuard) && chr.PrimaryStats.MP > 0) { // Absorbs X amount of damage. :) var skillId = chr.PrimaryStats.BuffMagicGuard.R; byte skillLevel; var sld = chr.Skills.GetSkillLevelData(skillId, out skillLevel); int damageEaten = (int)Math.Round((damage * (sld.XValue / 100.0d))); // MagicGuard doesn't show reduced damage. Trace.WriteLine($"Reducing damage by MG. Reflected {damageEaten}"); //Program.MainForm.LogAppend("MG Damage before change: " + actualHPEffect); actualHPEffect += damageEaten; //Program.MainForm.LogAppend("MG Damage after change: " + actualHPEffect); actualMPEffect = -damageEaten; healSkillId = skillId; } if (chr.PrimaryStats.BuffPowerGuard.HasReferenceId(Constants.Fighter.Skills.PowerGuard) || chr.PrimaryStats.BuffPowerGuard.HasReferenceId(Constants.Page.Skills.PowerGuard)) { var skillId = chr.PrimaryStats.BuffPowerGuard.R; byte skillLevel; var sld = chr.Skills.GetSkillLevelData(skillId, out skillLevel); int damageReflectedBack = (int)(damage * (sld.XValue / 100.0d)); if (damageReflectedBack > mob.MaxHP) { damageReflectedBack = (int)(mob.MaxHP * 0.1); } if (mob.IsBoss) { damageReflectedBack /= 2; } mob.GiveDamage(chr, damageReflectedBack); MobPacket.SendMobDamageOrHeal(chr, mobId, damageReflectedBack, false, false); mob.CheckDead(mob.Position); Trace.WriteLine($"Reducing damage by PG. Reflected {damageReflectedBack}"); actualHPEffect += damageReflectedBack; // Buff 'damaged' hp, so its less healSkillId = skillId; } if (chr.PrimaryStats.BuffMesoGuard.IsSet()) { var skillId = Constants.ChiefBandit.Skills.MesoGuard; var sld = chr.Skills.GetSkillLevelData( skillId, out var skillLevel ); if (sld != null) { var percentage = sld.XValue; var damageReduction = reducedDamage / 2; var mesoLoss = damageReduction * percentage / 100; if (damageReduction != 0) { var playerMesos = chr.Inventory.Mesos; var maxMesosUsable = Math.Min(chr.PrimaryStats.BuffMesoGuard.MesosLeft, playerMesos); if (mesoLoss > maxMesosUsable) { // New calculation. in our version it should actually 'save' the // mesos for a bit. damageReduction = 100 * maxMesosUsable / percentage; mesoLoss = maxMesosUsable; } if (mesoLoss > 0) { chr.PrimaryStats.BuffMesoGuard.MesosLeft -= mesoLoss; MesosTransfer.PlayerUsedSkill(chr.ID, mesoLoss, skillId); chr.AddMesos(-(mesoLoss), false); Trace.WriteLine($"Reducing damage by mesos. Mesos: {mesoLoss}, maxMesos {maxMesosUsable}, reduction {damageReduction}"); actualHPEffect += damageReduction; reducedDamage -= reducedDamage; } if (chr.PrimaryStats.BuffMesoGuard.MesosLeft <= 0) { // Debuff when out of mesos chr.PrimaryStats.RemoveByReference(skillId); } } } } SendCharacterDamageByMob( chr, attack, damage, reducedDamage, healSkillId, mobMapId, mobId, stance, isReflected, reflectHitAction, reflectX, reflectY ); } Trace.WriteLine($"Showing damage: {reducedDamage}, {damage}"); Trace.WriteLine($"Applying damage: HP {actualHPEffect}, MP: {actualMPEffect}"); if (actualHPEffect < 0) { chr.ModifyHP((short)actualHPEffect); } if (actualMPEffect < 0) { chr.ModifyMP((short)actualMPEffect); } if (mobSkillLevel != 0 && mobSkillId != 0) { // Check if the skill exists and has any extra effect. if (!DataProvider.MobSkills.TryGetValue(mobSkillId, out var skillLevels)) { return; } // Still going strong if (!skillLevels.TryGetValue(mobSkillLevel, out var msld)) { return; } OnStatChangeByMobSkill(chr, msld); } else if (mob != null) { // CUser::OnStatChangeByMobAttack if (mob.Data.Attacks == null || !mob.Data.Attacks.TryGetValue((byte)attack, out var mad)) { return; } // Okay, we've got an attack... if (mad.Disease <= 0) { return; } // Shit's poisonous! // Hmm... We could actually make snails give buffs... hurr if (!DataProvider.MobSkills.TryGetValue(mad.Disease, out var skillLevels)) { return; } // Still going strong if (!skillLevels.TryGetValue(mad.SkillLevel, out var msld)) { return; } OnStatChangeByMobSkill(chr, msld); } }