/// <summary> /// Handles attack. /// </summary> /// <param name="attacker">The creature attacking.</param> /// <param name="skill">The skill being used.</param> /// <param name="targetEntityId">The entity id of the target.</param> /// <returns></returns> public CombatSkillResult Use(Creature attacker, Skill skill, long targetEntityId) { if (attacker.IsStunned) { return(CombatSkillResult.Okay); } var target = attacker.Region.GetCreature(targetEntityId); if (target == null) { return(CombatSkillResult.Okay); } if (!attacker.GetPosition().InRange(target.GetPosition(), attacker.AttackRangeFor(target))) { return(CombatSkillResult.OutOfRange); } attacker.StopMove(); var targetPosition = target.StopMove(); // Counter if (Counterattack.Handle(target, attacker)) { return(CombatSkillResult.Okay); } var rightWeapon = attacker.Inventory.RightHand; var leftWeapon = attacker.Inventory.LeftHand; var magazine = attacker.Inventory.Magazine; var dualWield = (rightWeapon != null && leftWeapon != null && leftWeapon.Data.WeaponType != 0); var maxHits = (byte)(dualWield ? 2 : 1); int prevId = 0; for (byte i = 1; i <= maxHits; ++i) { var weapon = (i == 1 ? rightWeapon : leftWeapon); var weaponIsKnuckle = (weapon != null && weapon.Data.HasTag("/knuckle/")); var aAction = new AttackerAction(CombatActionType.Hit, attacker, skill.Info.Id, targetEntityId); var tAction = new TargetAction(CombatActionType.TakeHit, target, attacker, skill.Info.Id); var cap = new CombatActionPack(attacker, skill.Info.Id, aAction, tAction); cap.Hit = i; cap.MaxHits = maxHits; cap.PrevId = prevId; prevId = cap.Id; // Default attacker options aAction.Set(AttackerOptions.Result); if (dualWield) { aAction.Set(AttackerOptions.DualWield); } // Base damage var damage = attacker.GetRndDamage(weapon); // Critical Hit CriticalHit.Handle(attacker, attacker.GetCritChanceFor(target), ref damage, tAction); // Subtract target def/prot SkillHelper.HandleDefenseProtection(target, ref damage); // Defense Defense.Handle(aAction, tAction, ref damage); // Mana Shield ManaShield.Handle(target, ref damage, tAction); // Deal with it! if (damage > 0) { target.TakeDamage(tAction.Damage = damage, attacker); } // Aggro target.Aggro(attacker); // Evaluate caused damage if (!target.IsDead) { if (tAction.Type != CombatActionType.Defended) { target.Stability -= this.GetStabilityReduction(attacker, weapon) / maxHits; // React normal for CombatMastery, knock down if // FH and not dual wield, don't knock at all if dual. if (skill.Info.Id != SkillId.FinalHit) { // Originally we thought you knock enemies back, unless it's a critical // hit, but apparently you knock *down* under normal circumstances. // More research to be done. if (target.IsUnstable && target.Is(RaceStands.KnockBackable)) { //tAction.Set(tAction.Has(TargetOptions.Critical) ? TargetOptions.KnockDown : TargetOptions.KnockBack); tAction.Set(TargetOptions.KnockDown); } } else if (!dualWield && !weaponIsKnuckle) { target.Stability = Creature.MinStability; tAction.Set(TargetOptions.KnockDown); } } } else { tAction.Set(TargetOptions.FinishingKnockDown); } // React to knock back if (tAction.IsKnockBack) { attacker.Shove(target, KnockBackDistance); aAction.Set(AttackerOptions.KnockBackHit2); // Remove dual wield option if last hit doesn't come from // the second weapon. if (cap.MaxHits != cap.Hit) { aAction.Options &= ~AttackerOptions.DualWield; } } // Set stun time if (tAction.Type != CombatActionType.Defended) { aAction.Stun = GetAttackerStun(attacker, weapon, tAction.IsKnockBack && (skill.Info.Id != SkillId.FinalHit || !dualWield)); tAction.Stun = GetTargetStun(attacker, weapon, tAction.IsKnockBack); } // Second hit doubles stun time for normal hits if (cap.Hit == 2 && !tAction.IsKnockBack) { aAction.Stun *= 2; } // Update current weapon SkillHelper.UpdateWeapon(attacker, target, weapon); cap.Handle(); // No second hit if target was knocked back if (tAction.IsKnockBack) { break; } } return(CombatSkillResult.Okay); }
/// <summary> /// Handles attack. /// </summary> /// <param name="attacker">The creature attacking.</param> /// <param name="skill">The skill being used.</param> /// <param name="targetEntityId">The entity id of the target.</param> /// <returns></returns> public CombatSkillResult Use(Creature attacker, Skill skill, long targetEntityId) { if (attacker.IsStunned) { return(CombatSkillResult.Okay); } var target = attacker.Region.GetCreature(targetEntityId); if (target == null) { return(CombatSkillResult.Okay); } if (!attacker.GetPosition().InRange(target.GetPosition(), attacker.AttackRangeFor(target))) { return(CombatSkillResult.OutOfRange); } attacker.StopMove(); var targetPosition = target.StopMove(); // Counter if (Counterattack.Handle(target, attacker)) { return(CombatSkillResult.Okay); } var rightWeapon = attacker.Inventory.RightHand; var leftWeapon = attacker.Inventory.LeftHand; var magazine = attacker.Inventory.Magazine; var maxHits = (byte)(attacker.IsDualWielding ? 2 : 1); int prevId = 0; for (byte i = 1; i <= maxHits; ++i) { var weapon = (i == 1 ? rightWeapon : leftWeapon); var weaponIsKnuckle = (weapon != null && weapon.Data.HasTag("/knuckle/")); var aAction = new AttackerAction(CombatActionType.Attacker, attacker, targetEntityId); aAction.Set(AttackerOptions.Result); var tAction = new TargetAction(CombatActionType.TakeHit, target, attacker, skill.Info.Id); tAction.Set(TargetOptions.Result); var cap = new CombatActionPack(attacker, skill.Info.Id, aAction, tAction); cap.Hit = i; cap.Type = (attacker.IsDualWielding ? CombatActionPackType.TwinSwordAttack : CombatActionPackType.NormalAttack); cap.PrevId = prevId; prevId = cap.Id; // Default attacker options aAction.Set(AttackerOptions.Result); if (attacker.IsDualWielding) { aAction.Set(AttackerOptions.DualWield); aAction.WeaponParameterType = (byte)(i == 1 ? 2 : 1); } // Base damage var damage = (i == 1 ? attacker.GetRndRightHandDamage() : attacker.GetRndLeftHandDamage()); // Elementals damage *= attacker.CalculateElementalDamageMultiplier(target); // Critical Hit var critChance = (i == 1 ? attacker.GetRightCritChance(target.Protection) : attacker.GetLeftCritChance(target.Protection)); CriticalHit.Handle(attacker, critChance, ref damage, tAction); // Subtract target def/prot SkillHelper.HandleDefenseProtection(target, ref damage); // Defense Defense.Handle(aAction, tAction, ref damage); // Mana Shield ManaShield.Handle(target, ref damage, tAction); // Heavy Stander // Can only happen on the first hit var pinged = (cap.Hit == 1 && HeavyStander.Handle(attacker, target, ref damage, tAction)); // Deal with it! if (damage > 0) { target.TakeDamage(tAction.Damage = damage, attacker); SkillHelper.HandleInjury(attacker, target, damage); } // Knock down on deadly if (target.Conditions.Has(ConditionsA.Deadly)) { tAction.Set(TargetOptions.KnockDown); tAction.Stun = GetTargetStun(attacker, weapon, tAction.IsKnockBack); } // Aggro target.Aggro(attacker); // Evaluate caused damage if (!target.IsDead) { if (tAction.SkillId != SkillId.Defense) { target.Stability -= this.GetStabilityReduction(attacker, weapon) / maxHits; // React normal for CombatMastery, knock down if // FH and not dual wield, don't knock at all if dual. if (skill.Info.Id != SkillId.FinalHit) { // Originally we thought you knock enemies back, unless it's a critical // hit, but apparently you knock *down* under normal circumstances. // More research to be done. if (target.IsUnstable && target.Is(RaceStands.KnockBackable)) { //tAction.Set(tAction.Has(TargetOptions.Critical) ? TargetOptions.KnockDown : TargetOptions.KnockBack); tAction.Set(TargetOptions.KnockDown); } } else if (!attacker.IsDualWielding && !weaponIsKnuckle) { target.Stability = Creature.MinStability; tAction.Set(TargetOptions.KnockDown); } } } else { tAction.Set(TargetOptions.FinishingKnockDown); } // React to knock back if (tAction.IsKnockBack) { attacker.Shove(target, KnockBackDistance); aAction.Set(AttackerOptions.KnockBackHit2); } // Set stun time if not defended, Defense handles the stun // in case the target used it. if (tAction.SkillId != SkillId.Defense) { aAction.Stun = GetAttackerStun(attacker, weapon, tAction.IsKnockBack && skill.Info.Id != SkillId.FinalHit); tAction.Stun = GetTargetStun(attacker, weapon, tAction.IsKnockBack); } // Set increased stun if target pinged if (pinged) { aAction.Stun = GetAttackerStun(attacker, weapon, true); } // Second hit doubles stun time for normal hits if (cap.Hit == 2 && !tAction.IsKnockBack && !pinged) { aAction.Stun *= 2; } // Update current weapon SkillHelper.UpdateWeapon(attacker, target, weapon); // Consume stamina for weapon var staminaUsage = (weapon != null ? weapon.Data.StaminaUsage : Creature.BareHandStaminaUsage); if (attacker.Stamina < staminaUsage) { Send.Notice(attacker, Localization.Get("Your stamina is too low to fight properly!")); } attacker.Stamina -= staminaUsage; // No second hit if defended, pinged, or knocked back if (tAction.IsKnockBack || tAction.SkillId == SkillId.Defense || pinged) { // Set to 1 to prevent second run maxHits = 1; // Remove dual wield option if last hit doesn't come from // the second weapon. If this isn't done, the client shows // the second hit. if (cap.Hit != 2) { aAction.Options &= ~AttackerOptions.DualWield; } } // Handle cap.Handle(); } return(CombatSkillResult.Okay); }
/// <summary> /// Handles attack. /// </summary> /// <param name="attacker">The creature attacking.</param> /// <param name="skill">The skill being used.</param> /// <param name="targetEntityId">The entity id of the target.</param> /// <returns></returns> public CombatSkillResult Use(Creature attacker, Skill skill, long targetEntityId) { if (attacker.IsStunned) { return(CombatSkillResult.Okay); } var mainTarget = attacker.Region.GetCreature(targetEntityId); if (mainTarget == null) { return(CombatSkillResult.Okay); } if (!attacker.GetPosition().InRange(mainTarget.GetPosition(), attacker.AttackRangeFor(mainTarget))) { return(CombatSkillResult.OutOfRange); } attacker.StopMove(); // Get targets, incl. splash. var targets = new HashSet <Creature>() { mainTarget }; targets.UnionWith(attacker.GetTargetableCreaturesInCone(mainTarget.GetPosition(), attacker.GetTotalSplashRadius(), attacker.GetTotalSplashAngle())); // Counter if (Counterattack.Handle(targets, attacker)) { return(CombatSkillResult.Okay); } var rightWeapon = attacker.Inventory.RightHand; var leftWeapon = attacker.Inventory.LeftHand; var magazine = attacker.Inventory.Magazine; var maxHits = (byte)(attacker.IsDualWielding ? 2 : 1); int prevId = 0; for (byte i = 1; i <= maxHits; ++i) { var weapon = (i == 1 ? rightWeapon : leftWeapon); var weaponIsKnuckle = (weapon != null && weapon.Data.HasTag("/knuckle/")); var aAction = new AttackerAction(CombatActionType.Attacker, attacker, targetEntityId); aAction.Set(AttackerOptions.Result); if (attacker.IsDualWielding) { aAction.Set(AttackerOptions.DualWield); aAction.WeaponParameterType = (byte)(i == 1 ? 2 : 1); } var cap = new CombatActionPack(attacker, skill.Info.Id, aAction); cap.Hit = i; cap.Type = (attacker.IsDualWielding ? CombatActionPackType.TwinSwordAttack : CombatActionPackType.NormalAttack); cap.PrevId = prevId; prevId = cap.Id; var mainDamage = (i == 1 ? attacker.GetRndRightHandDamage() : attacker.GetRndLeftHandDamage()); foreach (var target in targets) { if (target.IsDead) { continue; } target.StopMove(); var tAction = new TargetAction(CombatActionType.TakeHit, target, attacker, skill.Info.Id); tAction.Set(TargetOptions.Result); cap.Add(tAction); // Base damage var damage = mainDamage; // Elementals damage *= attacker.CalculateElementalDamageMultiplier(target); // Splash modifier if (target != mainTarget) { damage *= attacker.GetSplashDamage(weapon); } // Critical Hit var critChance = (i == 1 ? attacker.GetRightCritChance(target.Protection) : attacker.GetLeftCritChance(target.Protection)); CriticalHit.Handle(attacker, critChance, ref damage, tAction); // Subtract target def/prot SkillHelper.HandleDefenseProtection(target, ref damage); // Conditions SkillHelper.HandleConditions(attacker, target, ref damage); // Defense Defense.Handle(aAction, tAction, ref damage); // Mana Shield ManaShield.Handle(target, ref damage, tAction); // Heavy Stander // Can only happen on the first hit var pinged = (cap.Hit == 1 && HeavyStander.Handle(attacker, target, ref damage, tAction)); // Deal with it! if (damage > 0) { target.TakeDamage(tAction.Damage = damage, attacker); SkillHelper.HandleInjury(attacker, target, damage); } // Knock down on deadly if (target.Conditions.Has(ConditionsA.Deadly)) { tAction.Set(TargetOptions.KnockDown); tAction.Stun = GetTargetStun(attacker, weapon, tAction.IsKnockBack); } // Aggro if (target == mainTarget) { target.Aggro(attacker); } // Evaluate caused damage if (!target.IsDead) { if (tAction.SkillId != SkillId.Defense) { target.Stability -= this.GetStabilityReduction(attacker, weapon) / maxHits; // React normal for CombatMastery, knock down if // FH and not dual wield, don't knock at all if dual. if (skill.Info.Id != SkillId.FinalHit) { // Originally we thought you knock enemies back, unless it's a critical // hit, but apparently you knock *down* under normal circumstances. // More research to be done. if (target.IsUnstable && target.Is(RaceStands.KnockBackable)) { //tAction.Set(tAction.Has(TargetOptions.Critical) ? TargetOptions.KnockDown : TargetOptions.KnockBack); tAction.Set(TargetOptions.KnockDown); } } else if (!attacker.IsDualWielding && !weaponIsKnuckle && target.Is(RaceStands.KnockBackable)) { target.Stability = Creature.MinStability; tAction.Set(TargetOptions.KnockDown); } } } else { tAction.Set(TargetOptions.FinishingKnockDown); } // React to knock back if (tAction.IsKnockBack) { attacker.Shove(target, KnockBackDistance); if (target == mainTarget) { aAction.Set(AttackerOptions.KnockBackHit2); } } // Set stun time if not defended, Defense handles the stun // in case the target used it. if (tAction.SkillId != SkillId.Defense) { if (target == mainTarget) { aAction.Stun = GetAttackerStun(attacker, weapon, tAction.IsKnockBack && skill.Info.Id != SkillId.FinalHit); } tAction.Stun = GetTargetStun(attacker, weapon, tAction.IsKnockBack); } if (target == mainTarget) { // Set increased stun if target pinged if (pinged) { aAction.Stun = GetAttackerStun(attacker, weapon, true); } // Second hit doubles stun time for normal hits if (cap.Hit == 2 && !tAction.IsKnockBack && !pinged) { aAction.Stun *= 2; } // Update current weapon SkillHelper.UpdateWeapon(attacker, target, ProficiencyGainType.Melee, weapon); // Consume stamina for weapon var staminaUsage = (weapon != null ? weapon.Data.StaminaUsage : Creature.BareHandStaminaUsage); if (attacker.Stamina < staminaUsage) { Send.Notice(attacker, Localization.Get("Your stamina is too low to fight properly!")); } attacker.Stamina -= staminaUsage; // No second hit if defended, pinged, or knocked back if (tAction.IsKnockBack || tAction.SkillId == SkillId.Defense || pinged) { // Set to 1 to prevent second run maxHits = 1; // Remove dual wield option if last hit doesn't come from // the second weapon. If this isn't done, the client shows // the second hit. if (cap.Hit != 2) { aAction.Options &= ~AttackerOptions.DualWield; } } // Reduce attacker's knockback stun in new combat, to allow // movement after sooner. // It's unknown when exactly this was added, but older EU logs // don't have this packet, so we'll assume it was part of the the // new combat, which's purpose was to be faster. // Sending the packet appears to reset the movement lock, and // officials seem to send this about 1s after the attack, for // an effective 1s movement lock after an attack. // If it's send for non-knockback hits, it can add a delay, // maybe increasing the time of the lock, like for dual-wielding. if (AuraData.FeaturesDb.IsEnabled("CombatSystemRenewal")) { if (tAction.IsKnockBack) { Task.Delay(1000).ContinueWith(_ => Send.CharacterLockUpdate(attacker, 18, 1500)); } } } } // Handle cap.Handle(); } return(CombatSkillResult.Okay); }
/// <summary> /// Handles skill usage. /// </summary> /// <param name="attacker"></param> /// <param name="skill"></param> /// <param name="targetEntityId"></param> /// <returns></returns> public override CombatSkillResult Use(Creature attacker, Skill skill, long targetEntityId) { // Check target var mainTarget = attacker.Region.GetCreature(targetEntityId); if (mainTarget == null) { return(CombatSkillResult.InvalidTarget); } // Check range var targetPosition = mainTarget.GetPosition(); if (!attacker.GetPosition().InRange(targetPosition, attacker.AttackRangeFor(mainTarget))) { return(CombatSkillResult.OutOfRange); } // Stop movement attacker.StopMove(); // Get targets, incl. splash. // Splash happens from r5 onwards, but we'll base it on Var4, // which is the splash damage and first != 0 on r5. var targets = new HashSet <Creature>() { mainTarget }; if (skill.RankData.Var4 != 0) { targets.UnionWith(attacker.GetTargetableCreaturesInCone(mainTarget.GetPosition(), attacker.GetTotalSplashRadius(), attacker.GetTotalSplashAngle())); } // Counter if (Counterattack.Handle(targets, attacker)) { return(CombatSkillResult.Okay); } // Prepare combat actions var aAction = new AttackerAction(CombatActionType.HardHit, attacker, targetEntityId); aAction.Set(AttackerOptions.Result | AttackerOptions.KnockBackHit2); aAction.Stun = StunTime; var cap = new CombatActionPack(attacker, skill.Info.Id, aAction); // Calculate damage var mainDamage = this.GetDamage(attacker, skill); foreach (var target in targets) { // Stop movement target.StopMove(); var tAction = new TargetAction(CombatActionType.TakeHit, target, attacker, skill.Info.Id); tAction.Set(TargetOptions.Result | TargetOptions.Smash); cap.Add(tAction); // Damage var damage = mainDamage; // Elementals damage *= attacker.CalculateElementalDamageMultiplier(target); // Splash modifier if (target != mainTarget) { damage *= (skill.RankData.Var4 / 100f); } // Critical Hit var critChance = this.GetCritChance(attacker, target, skill); CriticalHit.Handle(attacker, critChance, ref damage, tAction); // Subtract target def/prot SkillHelper.HandleDefenseProtection(target, ref damage); // Mana Shield ManaShield.Handle(target, ref damage, tAction); // Heavy Stander HeavyStander.Handle(attacker, target, ref damage, tAction); // Apply damage if (damage > 0) { target.TakeDamage(tAction.Damage = damage, attacker); SkillHelper.HandleInjury(attacker, target, damage); } // Aggro if (target == mainTarget) { target.Aggro(attacker); } if (target.IsDead) { tAction.Set(TargetOptions.FinishingHit | TargetOptions.Finished); } // Set Stun/Knockback target.Stun = tAction.Stun = StunTime; target.Stability = Creature.MinStability; // Set knockbacked position attacker.Shove(target, KnockbackDistance); } // Response Send.SkillUseStun(attacker, skill.Info.Id, AfterUseStun, 1); // Update both weapons SkillHelper.UpdateWeapon(attacker, mainTarget, ProficiencyGainType.Melee, attacker.RightHand, attacker.LeftHand); // Action! cap.Handle(); return(CombatSkillResult.Okay); }
/// <summary> /// Handles skill usage. /// </summary> /// <param name="attacker"></param> /// <param name="skill"></param> /// <param name="targetEntityId"></param> /// <returns></returns> public override CombatSkillResult Use(Creature attacker, Skill skill, long targetEntityId) { // Check target var target = attacker.Region.GetCreature(targetEntityId); if (target == null) { return(CombatSkillResult.InvalidTarget); } // Check range var targetPosition = target.GetPosition(); if (!attacker.GetPosition().InRange(targetPosition, attacker.AttackRangeFor(target))) { return(CombatSkillResult.OutOfRange); } // Stop movement attacker.StopMove(); target.StopMove(); // Counter if (Counterattack.Handle(target, attacker)) { return(CombatSkillResult.Okay); } // Prepare combat actions var aAction = new AttackerAction(CombatActionType.HardHit, attacker, skill.Info.Id, targetEntityId); aAction.Set(AttackerOptions.Result | AttackerOptions.KnockBackHit2); var tAction = new TargetAction(CombatActionType.TakeHit, target, attacker, skill.Info.Id); tAction.Set(TargetOptions.Result | TargetOptions.Smash); var cap = new CombatActionPack(attacker, skill.Info.Id, aAction, tAction); // Calculate damage var damage = this.GetDamage(attacker, skill); var critChance = this.GetCritChance(attacker, target, skill); // Critical Hit CriticalHit.Handle(attacker, critChance, ref damage, tAction); // Subtract target def/prot SkillHelper.HandleDefenseProtection(target, ref damage); // Mana Shield ManaShield.Handle(target, ref damage, tAction); // Apply damage target.TakeDamage(tAction.Damage = damage, attacker); // Aggro target.Aggro(attacker); if (target.IsDead) { tAction.Set(TargetOptions.FinishingHit | TargetOptions.Finished); } // Set Stun/Knockback attacker.Stun = aAction.Stun = StunTime; target.Stun = tAction.Stun = StunTime; target.Stability = Creature.MinStability; // Set knockbacked position attacker.Shove(target, KnockbackDistance); // Response Send.SkillUseStun(attacker, skill.Info.Id, AfterUseStun, 1); // Update both weapons SkillHelper.UpdateWeapon(attacker, target, attacker.RightHand, attacker.LeftHand); // Action! cap.Handle(); return(CombatSkillResult.Okay); }
public CombatSkillResult UseWithoutRangeCheck(Creature attacker, Skill skill, long targetEntityId, Creature mainTarget, SkillId interceptingSkillId = SkillId.None) { //Against Smash Skill smash = mainTarget.Skills.Get(SkillId.Smash); if (interceptingSkillId == SkillId.None && smash != null && mainTarget.Skills.IsReady(SkillId.Smash)) { interceptingSkillId = SkillId.Smash; } var rightWeapon = attacker.Inventory.RightHand; var leftWeapon = attacker.Inventory.LeftHand; var dualWield = (rightWeapon != null && leftWeapon != null && leftWeapon.Data.WeaponType != 0 && (leftWeapon.HasTag("/weapon/edged/") || leftWeapon.HasTag("/weapon/blunt/"))); // Against Combat Mastery Skill combatMastery = mainTarget.Skills.Get(SkillId.CombatMastery); var simultaneousAttackStun = 0; if (interceptingSkillId == SkillId.None) { if (combatMastery != null && (mainTarget.Skills.ActiveSkill == null || mainTarget.Skills.ActiveSkill == combatMastery || mainTarget.Skills.IsReady(SkillId.FinalHit)) && mainTarget.IsInBattleStance && mainTarget.Target == attacker && mainTarget.AttemptingAttack && (!mainTarget.IsStunned || mainTarget.IsKnockedDown)) { var attackerStunTime = CombatMastery.GetAttackerStun(attacker, attacker.RightHand, false); var mainTargetStunTime = CombatMastery.GetAttackerStun(mainTarget, mainTarget.Inventory.RightHand, false); var slowestStun = CombatMastery.GetAttackerStun(1, AttackSpeed.VerySlow, false); var additionalStun = slowestStun + (CombatMastery.GetAttackerStun(5, AttackSpeed.VeryFast, false) / 2); //Fastest stun divided by two so that the fastest stun doesn't always beat out the slowest stun. The addition is so that the subtration (Ex. additionalStun - attackerStunTime) ends in the desired range. var formulaMultiplier = 320; //Multiplier to keep the result reasonable, found through trial and error? var formulaEqualizer = 50; //Balances the subtraction to keep the result in a reasonable range and balanced out no matter the order. double chances = ((((additionalStun - attackerStunTime) / slowestStun) * formulaMultiplier) - (((additionalStun - mainTargetStunTime) / slowestStun) * formulaMultiplier)) + formulaEqualizer; //Probability in percentage that you will not lose. chances = Math2.Clamp(0.0, 99.0, chances); //Cap the stun, just in case. if (((mainTarget.LastKnockedBackBy == attacker && mainTarget.KnockDownTime > attacker.KnockDownTime && mainTarget.KnockDownTime.AddMilliseconds(mainTargetStunTime) > DateTime.Now || /*attackerStunTime > initialTargetStunTime && */ !Math2.Probability(chances) && !(attacker.LastKnockedBackBy == mainTarget && attacker.KnockDownTime > mainTarget.KnockDownTime && attacker.KnockDownTime.AddMilliseconds(attackerStunTime) > DateTime.Now)))) { if (!Math2.Probability(chances)) //Probability in percentage that it will be an interception instead of a double hit. Always in favor of the faster attacker. { if (mainTarget.CanTarget(attacker) && mainTarget.Can(Locks.Attack)) //TODO: Add Hit lock when available. { var skillHandler = ChannelServer.Instance.SkillManager.GetHandler <ICombatSkill>(combatMastery.Info.Id); if (skillHandler == null) { Log.Error("CombatMastery.Use: Target's skill handler not found for '{0}'.", combatMastery.Info.Id); return(CombatSkillResult.Okay); } ((CombatMastery)skillHandler).UseWithoutRangeCheck(mainTarget, combatMastery, attacker.EntityId, attacker, SkillId.CombatMastery); return(CombatSkillResult.Okay); } } else { interceptingSkillId = SkillId.CombatMastery; if (mainTarget.CanTarget(attacker) && mainTarget.Can(Locks.Attack)) //TODO: Add Hit lock when available. { var skillHandler = ChannelServer.Instance.SkillManager.GetHandler <ICombatSkill>(combatMastery.Info.Id); if (skillHandler == null) { Log.Error("CombatMastery.Use: Target's skill handler not found for '{0}'.", combatMastery.Info.Id); } else { ((CombatMastery)skillHandler).UseWithoutRangeCheck(mainTarget, combatMastery, attacker.EntityId, attacker, SkillId.CombatMastery); simultaneousAttackStun = attacker.Stun; attacker.Stun = 0; } } } } else { if (Math2.Probability(chances)) //Probability in percentage that it will be an interception instead of a double hit. Always in favor of the faster attacker. { interceptingSkillId = SkillId.CombatMastery; } else { interceptingSkillId = SkillId.CombatMastery; if (mainTarget.CanTarget(attacker) && mainTarget.Can(Locks.Attack)) //TODO: Add Hit lock when available. { var skillHandler = ChannelServer.Instance.SkillManager.GetHandler <ICombatSkill>(combatMastery.Info.Id); if (skillHandler == null) { Log.Error("CombatMastery.Use: Target's skill handler not found for '{0}'.", combatMastery.Info.Id); } else { ((CombatMastery)skillHandler).UseWithoutRangeCheck(mainTarget, combatMastery, attacker.EntityId, attacker, SkillId.CombatMastery); simultaneousAttackStun = attacker.Stun; attacker.Stun = 0; } } } } } } attacker.StopMove(); mainTarget.StopMove(); // Get targets, incl. splash. var targets = new HashSet <Creature>() { mainTarget }; targets.UnionWith(attacker.GetTargetableCreaturesInCone(mainTarget.GetPosition(), attacker.GetTotalSplashRadius(), attacker.GetTotalSplashAngle())); // Counter if (Counterattack.Handle(targets, attacker)) { return(CombatSkillResult.Okay); } var magazine = attacker.Inventory.Magazine; var maxHits = (byte)(attacker.IsDualWielding ? 2 : 1); int prevId = 0; var knockedBackTargets = new HashSet <long>(); for (byte i = 1; i <= maxHits; ++i) { var weapon = (i == 1 ? rightWeapon : leftWeapon); var weaponIsKnuckle = (weapon != null && weapon.Data.HasTag("/knuckle/")); AttackerAction aAction; if (interceptingSkillId == SkillId.Smash) { aAction = new AttackerAction(CombatActionType.SimultaneousHit, attacker, targetEntityId); } else if (interceptingSkillId == SkillId.CombatMastery) { aAction = new AttackerAction(CombatActionType.SimultaneousHit, attacker, targetEntityId); } else { aAction = new AttackerAction(CombatActionType.Attacker, attacker, targetEntityId); } aAction.Set(AttackerOptions.Result); if (attacker.IsDualWielding) { aAction.Set(AttackerOptions.DualWield); aAction.WeaponParameterType = (byte)(i == 1 ? 2 : 1); } var cap = new CombatActionPack(attacker, skill.Info.Id); if (interceptingSkillId != SkillId.Smash) { cap.Add(aAction); } cap.Hit = i; cap.Type = (attacker.IsDualWielding ? CombatActionPackType.TwinSwordAttack : CombatActionPackType.NormalAttack); cap.PrevId = prevId; prevId = cap.Id; var mainDamage = (i == 1 ? attacker.GetRndRightHandDamage() : attacker.GetRndLeftHandDamage()); foreach (var target in targets) { // Skip targets that were knocked back, as they aren't in // range anymore. if (knockedBackTargets.Contains(target.EntityId)) { continue; } target.StopMove(); TargetAction tAction; if (target == mainTarget) { if (interceptingSkillId == SkillId.Smash) { tAction = new TargetAction(CombatActionType.CounteredHit, target, attacker, SkillId.Smash); } else if (interceptingSkillId == SkillId.CombatMastery) { tAction = new TargetAction(CombatActionType.CounteredHit, target, attacker, target.Skills.IsReady(SkillId.FinalHit) ? SkillId.FinalHit : SkillId.CombatMastery); } else { tAction = new TargetAction(CombatActionType.TakeHit, target, attacker, target.Skills.IsReady(SkillId.FinalHit) ? SkillId.FinalHit : SkillId.CombatMastery); } } else { tAction = new TargetAction(CombatActionType.TakeHit, target, attacker, target.Skills.IsReady(SkillId.FinalHit) ? SkillId.FinalHit : SkillId.CombatMastery); } tAction.Set(TargetOptions.Result); cap.Add(tAction); if (target == mainTarget && interceptingSkillId == SkillId.Smash) { cap.Add(aAction); } // Base damage var damage = mainDamage; // Elementals damage *= attacker.CalculateElementalDamageMultiplier(target); // Splash modifier if (target != mainTarget) { damage *= attacker.GetSplashDamage(weapon); } // Critical Hit var critChance = (i == 1 ? attacker.GetRightCritChance(target.Protection) : attacker.GetLeftCritChance(target.Protection)); CriticalHit.Handle(attacker, critChance, ref damage, tAction); // Subtract target def/prot SkillHelper.HandleDefenseProtection(target, ref damage); // Conditions SkillHelper.HandleConditions(attacker, target, ref damage); // Defense Defense.Handle(aAction, tAction, ref damage); // Mana Shield ManaShield.Handle(target, ref damage, tAction); // Heavy Stander // Can only happen on the first hit var pinged = (cap.Hit == 1 && HeavyStander.Handle(attacker, target, ref damage, tAction)); // Deal with it! if (damage > 0) { target.TakeDamage(tAction.Damage = damage, attacker); SkillHelper.HandleInjury(attacker, target, damage); } // Knock down on deadly if (target.Conditions.Has(ConditionsA.Deadly)) { tAction.Set(TargetOptions.KnockDown); tAction.Stun = GetTargetStun(attacker, weapon, tAction.IsKnockBack); } // Aggro if (target == mainTarget) { target.Aggro(attacker); } // Evaluate caused damage if (!target.IsDead) { if (tAction.SkillId != SkillId.Defense) { target.Stability -= this.GetStabilityReduction(attacker, weapon) / maxHits; // React normal for CombatMastery, knock down if // FH and not dual wield, don't knock at all if dual. if (skill.Info.Id != SkillId.FinalHit) { // Originally we thought you knock enemies back, unless it's a critical // hit, but apparently you knock *down* under normal circumstances. // More research to be done. if (target.IsUnstable && target.Is(RaceStands.KnockBackable)) { //tAction.Set(tAction.Has(TargetOptions.Critical) ? TargetOptions.KnockDown : TargetOptions.KnockBack); tAction.Set(TargetOptions.KnockDown); } } else if (!attacker.IsDualWielding && !weaponIsKnuckle && target.Is(RaceStands.KnockBackable)) { target.Stability = Creature.MinStability; tAction.Set(TargetOptions.KnockDown); } } } else { tAction.Set(TargetOptions.FinishingKnockDown); } // React to knock back if (tAction.IsKnockBack) { attacker.Shove(target, KnockBackDistance); if (target == mainTarget) { aAction.Set(AttackerOptions.KnockBackHit2); } knockedBackTargets.Add(target.EntityId); } // Set stun time if not defended, Defense handles the stun // in case the target used it. if (tAction.SkillId != SkillId.Defense) { if (target == mainTarget) { aAction.Stun = GetAttackerStun(attacker, weapon, tAction.IsKnockBack && skill.Info.Id != SkillId.FinalHit); } tAction.Stun = GetTargetStun(attacker, weapon, tAction.IsKnockBack); } if (target == mainTarget) { // Set increased stun if target pinged if (pinged) { aAction.Stun = GetAttackerStun(attacker, weapon, true); } // Second hit doubles stun time for normal hits if (cap.Hit == 2 && !tAction.IsKnockBack && !pinged) { aAction.Stun *= 2; } // Update current weapon SkillHelper.UpdateWeapon(attacker, target, ProficiencyGainType.Melee, weapon); // Consume stamina for weapon var staminaUsage = (rightWeapon != null && rightWeapon.Data.StaminaUsage != 0 ? rightWeapon.Data.StaminaUsage : 0.7f) + (dualWield ? leftWeapon.Data.StaminaUsage : 0f); if (attacker.Stamina < staminaUsage) { Send.Notice(attacker, Localization.Get("Your stamina is too low to fight properly!")); } attacker.Stamina -= staminaUsage; // No second hit if defended, pinged, or knocked back if (tAction.IsKnockBack || tAction.SkillId == SkillId.Defense || pinged) { // Set to 1 to prevent second run maxHits = 1; // Remove dual wield option if last hit doesn't come from // the second weapon. If this isn't done, the client shows // the second hit. if (cap.Hit != 2) { aAction.Options &= ~AttackerOptions.DualWield; } } // Reduce attacker's knockback stun in new combat, to allow // movement after sooner. // It's unknown when exactly this was added, but older EU logs // don't have this packet, so we'll assume it was part of the the // new combat, which's purpose was to be faster. // Sending the packet appears to reset the movement lock, and // officials seem to send this about 1s after the attack, for // an effective 1s movement lock after an attack. // If it's send for non-knockback hits, it can add a delay, // maybe increasing the time of the lock, like for dual-wielding. if (AuraData.FeaturesDb.IsEnabled("CombatSystemRenewal")) { if (tAction.IsKnockBack) { Task.Delay(1000).ContinueWith(_ => Send.CharacterLockUpdate(attacker, 18, 1500)); } } } } // Handle cap.Handle(); } attacker.AttemptingAttack = false; return(CombatSkillResult.Okay); }
/// <summary> /// Handles skill usage while ignoring range. /// </summary> /// <param name="attacker"></param> /// <param name="skill"></param> /// <param name="targetEntityId"></param> /// <returns></returns> public CombatSkillResult UseWithoutRangeCheck(Creature attacker, Skill skill, long targetEntityId, Creature mainTarget, SkillId interceptingSkillId = SkillId.None) { // Against Normal Attack Skill combatMastery = mainTarget.Skills.Get(SkillId.CombatMastery); if (interceptingSkillId == SkillId.None && combatMastery != null && (mainTarget.Skills.ActiveSkill == null || mainTarget.Skills.ActiveSkill == combatMastery || mainTarget.Skills.IsReady(SkillId.FinalHit)) && mainTarget.IsInBattleStance && mainTarget.Target == attacker && mainTarget.AttemptingAttack && (!mainTarget.IsStunned || mainTarget.IsKnockedDown)) { if (mainTarget.CanTarget(attacker) && mainTarget.Can(Locks.Attack)) //TODO: Add Hit lock when available. { var skillHandler = ChannelServer.Instance.SkillManager.GetHandler <ICombatSkill>(combatMastery.Info.Id); if (skillHandler == null) { Log.Error("Smash.Use: Target's skill handler not found for '{0}'.", combatMastery.Info.Id); return(CombatSkillResult.Okay); } ((CombatMastery)skillHandler).UseWithoutRangeCheck(mainTarget, combatMastery, attacker.EntityId, attacker, SkillId.Smash); return(CombatSkillResult.Okay); } } // Against Smash Skill smash = mainTarget.Skills.Get(SkillId.Smash); if (interceptingSkillId == SkillId.None && smash != null && mainTarget.Skills.IsReady(SkillId.Smash) && mainTarget.IsInBattleStance && mainTarget.Target == attacker && !mainTarget.IsStunned) { var attackerStunTime = CombatMastery.GetAttackerStun(attacker, attacker.RightHand, false); var mainTargetStunTime = CombatMastery.GetAttackerStun(mainTarget, mainTarget.Inventory.RightHand, false); var slowestStun = CombatMastery.GetAttackerStun(1, AttackSpeed.VerySlow, false); var additionalStun = slowestStun + (CombatMastery.GetAttackerStun(5, AttackSpeed.VeryFast, false) / 2); //Fastest stun divided by two so that the fastest stun doesn't always beat out the slowest stun. The addition is so that the subtration (Ex. additionalStun - attackerStunTime) ends in the desired range. var formulaMultiplier = 320; //Multiplier to keep the result reasonable, found through trial and error? var formulaEqualizer = 50; //Balances the subtraction to keep the result in a reasonable range and balanced out no matter the order. double chances = ((((additionalStun - attackerStunTime) / slowestStun) * formulaMultiplier) - (((additionalStun - mainTargetStunTime) / slowestStun) * formulaMultiplier)) + formulaEqualizer; //Probability in percentage that you will not lose. chances = Math2.Clamp(0.0, 99.0, chances); //Cap the stun, just in case. if (((mainTarget.LastKnockedBackBy == attacker && mainTarget.KnockDownTime > attacker.KnockDownTime && mainTarget.KnockDownTime.AddMilliseconds(mainTargetStunTime) > DateTime.Now || /*attackerStunTime > initialTargetStunTime && */ !(attacker.LastKnockedBackBy == mainTarget && attacker.KnockDownTime > mainTarget.KnockDownTime && attacker.KnockDownTime.AddMilliseconds(attackerStunTime) > DateTime.Now)))) { if (mainTarget.CanTarget(attacker) && mainTarget.Can(Locks.Attack)) //TODO: Add Hit lock when available. { var skillHandler = ChannelServer.Instance.SkillManager.GetHandler <ICombatSkill>(smash.Info.Id); if (skillHandler == null) { Log.Error("Smash.Use: Target's skill handler not found for '{0}'.", smash.Info.Id); return(CombatSkillResult.Okay); } ((Smash)skillHandler).UseWithoutRangeCheck(mainTarget, smash, attacker.EntityId, attacker, SkillId.Smash); return(CombatSkillResult.Okay); } } else { interceptingSkillId = SkillId.Smash; } } // Stop movement attacker.StopMove(); mainTarget.StopMove(); // Get targets, incl. splash. // Splash happens from r5 onwards, but we'll base it on Var4, // which is the splash damage and first != 0 on r5. var targets = new HashSet <Creature>() { mainTarget }; if (skill.RankData.Var4 != 0) { targets.UnionWith(attacker.GetTargetableCreaturesInCone(mainTarget.GetPosition(), attacker.GetTotalSplashRadius(), attacker.GetTotalSplashAngle())); } // Counter if (Counterattack.Handle(targets, attacker)) { return(CombatSkillResult.Okay); } // Prepare combat actions var aAction = new AttackerAction(CombatActionType.HardHit, attacker, targetEntityId); aAction.Set(AttackerOptions.Result | AttackerOptions.KnockBackHit2); aAction.Stun = StunTime; var cap = new CombatActionPack(attacker, skill.Info.Id); // Calculate damage var mainDamage = this.GetDamage(attacker, skill); foreach (var target in targets) { // Stop movement target.StopMove(); TargetAction tAction; if (target == mainTarget) { if (interceptingSkillId == SkillId.Smash) { aAction.Options |= AttackerOptions.Result; tAction = new TargetAction(CombatActionType.CounteredHit, target, attacker, SkillId.Smash); } else { tAction = new TargetAction(CombatActionType.TakeHit, target, attacker, skill.Info.Id); } } else { tAction = new TargetAction(CombatActionType.TakeHit, target, attacker, skill.Info.Id); } tAction.Set(TargetOptions.Result | TargetOptions.Smash); cap.Add(tAction); if (target == mainTarget) { cap.Add(aAction); } // Damage var damage = mainDamage; // Elementals damage *= attacker.CalculateElementalDamageMultiplier(target); // Splash modifier if (target != mainTarget) { damage *= (skill.RankData.Var4 / 100f); } // Critical Hit var critChance = this.GetCritChance(attacker, target, skill); CriticalHit.Handle(attacker, critChance, ref damage, tAction); // Subtract target def/prot SkillHelper.HandleDefenseProtection(target, ref damage); // Conditions SkillHelper.HandleConditions(attacker, target, ref damage); // Mana Shield ManaShield.Handle(target, ref damage, tAction); // Heavy Stander HeavyStander.Handle(attacker, target, ref damage, tAction); // Apply damage if (damage > 0) { target.TakeDamage(tAction.Damage = damage, attacker); SkillHelper.HandleInjury(attacker, target, damage); } // Aggro if (target == mainTarget) { target.Aggro(attacker); } if (target.IsDead) { tAction.Set(TargetOptions.FinishingHit | TargetOptions.Finished); } // Set Stun/Knockback target.Stun = tAction.Stun = StunTime; target.Stability = Creature.MinStability; // Set knockbacked position attacker.Shove(target, KnockbackDistance); } // Response Send.SkillUseStun(attacker, skill.Info.Id, AfterUseStun, 1); // Update both weapons SkillHelper.UpdateWeapon(attacker, mainTarget, ProficiencyGainType.Melee, attacker.RightHand, attacker.LeftHand); // Action! cap.Handle(); return(CombatSkillResult.Okay); }