/// <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);
// 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;
}
}
}
// Handle
cap.Handle();
}
return CombatSkillResult.Okay;
}