/// <summary> /// Rage for the attacker of an AttackAction /// </summary> public static void GenerateDefaultAttackerRage(AttackAction action) { var attacker = action.Attacker; // only generate Rage for white damage if (action.IsWeaponAttack) { double hitFactor; if (action.Weapon == attacker.OffHandWeapon) { hitFactor = 1.75; } else { hitFactor = 3.5; } if (action.IsCritical) { hitFactor *= 2; } hitFactor *= action.Weapon.AttackTime; var lvl = attacker.Level; var c = 0.0092f * lvl * lvl + 3.23f * lvl + 4.27f; var rage = ((15 * action.ActualDamage / c) + (hitFactor / 2000f)) + 1; // Multiplied by 2 to match an approximate value, check the formula instead. attacker.Power += (int)(rage*2); } }
/// <summary> /// Rage for the victim of an AttackAction /// </summary> public static void GenerateDefaultVictimRage(AttackAction action) { var victim = action.Victim; var lvl = victim.Level; var c = (int)(0.0092 * lvl * lvl + 3.23f * lvl + 4.27f); // polynomial rage co-efficient victim.Power += (5 * (action.ActualDamage + 1) / (2 * c))*10; }
/// <summary> /// Any spell and ranged damage /// SMSG_SPELLNONMELEEDAMAGELOG /// </summary> /* Target: High: 0xF530 (Unit) - Low: 619710 - Entry: UmbralBrute (30922) Caster: High: 0x0000 (Player) - Low: 2211871 Spell: ClassSkillArcaneShot_11 (49045) Damage: 776 Overkill: 0 SchoolMask: Arcane (64) Absorbed: 0 Resisted: 0 UnkByte1: 0 UnkByte2 (Unused): 0 Blocked: 0 HitFlags: 37 UnkByte3 (Unused): 0 */ public static void SendMagicDamage(AttackAction state) { using (var packet = new RealmPacketOut(RealmServerOpCode.SMSG_SPELLNONMELEEDAMAGELOG, 40)) { state.Victim.EntityId.WritePacked(packet); if (state.Attacker != null) { state.Attacker.EntityId.WritePacked(packet); } else { packet.Write((byte)0); } packet.Write(state.SpellEffect != null ? state.SpellEffect.Spell.Id : 0); packet.Write(state.Damage); packet.Write(0); // overkill? packet.Write((byte)state.Schools); packet.Write(state.Absorbed); packet.Write(state.Resisted); //packet.Write(0); // is always 0 packet.Write((state.Schools & DamageSchoolMask.Physical) != 0); packet.Write((byte)0); // 0 or 1 packet.Write(state.Blocked); // also flags 0x8, 0x10, var hitFlags = state.IsCritical ? SpellLogFlags.Critical : SpellLogFlags.None; packet.Write((int)hitFlags); packet.Write((byte)0);// unused by client state.Victim.SendPacketToArea(packet, true); } }
public static void SendAttackerStateUpdate(AttackAction action) { using (var packet = new RealmPacketOut(RealmServerOpCode.SMSG_ATTACKERSTATEUPDATE, 100)) { var evade = action.VictimState == VictimState.Evade; if (evade) { //action.HitFlags |= HitFlags.HitFlag_0x800 | HitFlags.Absorb_1; } packet.Write((uint)action.HitFlags); action.Attacker.EntityId.WritePacked(packet); action.Victim.EntityId.WritePacked(packet); var dmg = action.ActualDamage; packet.Write(dmg); packet.Write(0); // unknown (overkill?) //damage count const byte damageCount = 1; packet.Write(damageCount); for (byte i = 0; i < damageCount; i++) { packet.Write((uint) action.Schools); packet.Write((float) dmg); packet.Write(dmg); } if (action.HitFlags.HasAny(HitFlags.Absorb_1 | HitFlags.Absorb_2)) { for (byte i = 0; i < damageCount; i++) { packet.Write(action.Absorbed); } } if (action.HitFlags.HasAny(HitFlags.Resist_1 | HitFlags.Resist_2)) { for (byte i =0;i<damageCount;i++) { packet.Write(action.Resisted); } } packet.Write((byte)action.VictimState); if (evade) { packet.Write(0x1000002); } else { packet.Write(dmg > 0 ? -1 : 0); // 0 if no damage, else -1 or 1000 or very rarely something else (eg when evading) } packet.Write(0);// this is a spell id if (action.HitFlags.HasAny(HitFlags.Block)) { packet.Write(action.Blocked); } //if ((hitFlags & HitFlags.HitFlag_0x800000) != 0) //{ // packet.Write(0); //} //if ((hitFlags & HitFlags.HitFlag_0x1) != 0) //{ // packet.Write(0); // packet.Write((float)0); // packet.Write((float)0); // packet.Write((float)0); // packet.Write((float)0); // packet.Write((float)0); // packet.Write((float)0); // packet.Write((float)0); // packet.Write((float)0); // for (int i = 0; i < 5; i++) // { // packet.Write(0); // packet.Write(0); // } // packet.Write(0); //} action.Victim.SendPacketToArea(packet, true); } }
/// <summary> /// Does spell-damage to this Unit /// </summary> public void DoSpellDamage(Unit attacker, SpellEffect effect, int dmg) { DamageSchool school; if (effect != null) { school = GetLeastResistant(effect.Spell); } else { school = DamageSchool.Physical; } if (IsEvading || IsImmune(school) || IsInvulnerable || !IsAlive) { return; } var action = attacker != null ? attacker.m_AttackAction : m_AttackAction; if (action == null || action.IsInUse) { // currently in use action = new AttackAction(attacker); } else { action.Attacker = attacker; action.HitFlags = 0; action.VictimState = 0; action.Weapon = null; } if (effect != null) { action.UsedSchool = school; action.Schools = effect.Spell.SchoolMask; action.IsDot = effect.IsPeriodic; } else { action.UsedSchool = DamageSchool.Physical; action.Schools = DamageSchoolMask.Physical; action.IsDot = false; } var resChance = GetResistChance(this, action.UsedSchool); action.Victim = this; if (attacker != null) { action.Damage = attacker.AddDamageMods(dmg, action.SpellEffect, action.UsedSchool); if (effect != null && !action.IsDot && !effect.Spell.AttributesExB.Has(SpellAttributesExB.CannotCrit) && attacker.CalcSpellCritChance(this, action.UsedSchool, resChance, effect.Spell) > Utility.Random(0f, 100f)) { action.Damage = attacker.CalcCritDamage(action.ActualDamage, this, effect).RoundInt(); action.IsCritical = true; } else { action.IsCritical = false; } } action.Absorbed = Absorb(action.UsedSchool, action.Damage); action.Resisted = (int)Math.Round(action.Damage * resChance); action.Blocked = 0; // TODO: Deflect action.SpellEffect = effect; //TODO: figure this out: However, when spells do only damage, it's not just a full hit or full miss situation. Pure damage spells can be resisted for 0%, 25%, 50%, 75%, or 100% of their regular damage. DoRawDamage(action); CombatLogHandler.SendMagicDamage(action); action.OnFinished(); //Your average resistance can still be anywhere betweeen 0% and 75%. If your average resistance is maxed out, then there's a really good chance of having 75% of the spell's damage be resisted. //There's also a fairly good chance of having 100% of the spell's damage be resisted, a slightly lower chance of 50% of its damage being resisted, a small chances of only 25%, or even 0% of the damage being resisted. //It's a weighted average. Visualize it as a bell curve around your average resistance. }
/// <summary> /// Do a single attack using the given Weapon and AttackAction. /// </summary> /// <param name="weapon"></param> /// <param name="action"></param> public void Strike(IWeapon weapon, AttackAction action, Unit target) { if (weapon == null) { log.Error("Trying to strike without weapon: " + this); return; } //if (IsMovementConrolled) //{ // // stop running when landing a hit // m_Movement.Stop(); //} // calc damage if (weapon == m_offhandWeapon) { action.Damage = Utility.Random((int)MinOffHandDamage, (int)MaxOffHandDamage + 1); } else { if (weapon.IsRanged) { action.Damage = Utility.Random((int)MinRangedDamage, (int)MaxRangedDamage + 1); } else { action.Damage = Utility.Random((int)MinDamage, (int)MaxDamage + 1); } } action.Victim = target; action.Attacker = this; action.Weapon = weapon; if (m_pendingCombatAbility != null && m_pendingCombatAbility.IsCasting) { // Pending combat ability var ability = m_pendingCombatAbility; // get boni, damage and let the Spell impact var multiplier = 100; SpellEffect effect = null; foreach (var effectHandler in ability.Handlers) { if (effectHandler.Effect.IsStrikeEffectFlat) { action.Damage += effectHandler.CalcEffectValue(); } else if (effectHandler.Effect.IsStrikeEffectPct) { multiplier += effectHandler.CalcEffectValue(); } if (effect == null) { // use the first effect effect = effectHandler.Effect; } } action.Damage = (action.Damage * multiplier) / 100; // send pending animation if (ability.IsInstant) { m_pendingCombatAbility.SendCastStart(); } if (!ability.Spell.IsRangedAbility) { m_pendingCombatAbility.CheckHitAndSendSpellGo(true); } // prevent the ability from cancelling itself m_pendingCombatAbility = null; action.Schools = ability.Spell.SchoolMask; action.SpellEffect = effect; if (ability.Spell.IsAreaSpell) { var totalDmg = action.Damage; action.IsDot = false; // AoE spell foreach (var targ in ability.Targets) { action.Reset(this, (Unit)targ, weapon, totalDmg); action.DoAttack(); if (ability.Spell.IsDualWieldAbility) { action.Reset(this, (Unit)targ, weapon, Utility.Random((int)MinOffHandDamage, (int)MaxOffHandDamage + 1)); action.DoAttack(); } } } else { // single target if (!action.DoAttack() && ability.Spell.AttributesExC.Has(SpellAttributesExC.RequiresTwoWeapons)) { // missed and is not attacking with both weapons -> don't trigger spell CancelPendingAbility(); return; } } // Impact and trigger remaining effects (if not cancelled) if (!ability.Spell.IsRangedAbility) { ability.Impact(ability.Spell.IsOnNextStrike); } } else { m_extraAttacks += 1; do { action.Schools = weapon.Damages.AllSchools(); if (action.Schools == DamageSchoolMask.None) { action.Schools = DamageSchoolMask.Physical; } // normal attack action.DoAttack(); } while (--m_extraAttacks > 0); } action.OnFinished(); }