/// <summary> /// Process an entity's auto attack /// </summary> /// <param name="numAttacks">The number of attacks to process</param> /// <param name="source">The attacking entity</param> /// <param name="target">The target entity</param> public static void ProcessEntityAutoAttack(EntityAnimate source, EntityAnimate target) { if (source == null || target == null) { return; } var rand = World.Random; var attackRating = 0; var weapons = source.GetItemsEquippedAt(ItemSlot.OneHandedWeapon, ItemSlot.TwoHandedWeapon).Cast <ItemWeapon>().ToList(); var skillResults = new List <ImproveSkillResult>(); string supportSkillToImprove = ""; // Calculate the number of attacks var numAttacks = CombatHelper.CalcEntityMaxNumAttacks(source); var attackChanceList = new List <double>(); // Protect unknown circumstances if (numAttacks < 1) { Logger.Info(nameof(CombatHandler), nameof(ProcessEntityAutoAttack), $"Number of attacks for {source.Name}: ID {source.Prototype} was less than 1."); string sourceShort = source.ShortDescription ?? ""; string targetShort = target.ShortDescription.FirstLetterToUpperCaseOrConvertNullToEmptyString(); source.IOHandler?.QueueRawOutput($"You struggle to attack {targetShort}, but can't!"); target.IOHandler?.QueueRawOutput($"{sourceShort} struggles to attack you, but can't!"); return; } // Get support skill for later improvement if (source.GetType() == typeof(Player)) { if (weapons.Count == 0) { // Unarmed supportSkillToImprove = SkillMap.SkillToFriendlyName(typeof(Unarmed)); } else if (weapons.Count == 1) { // Two handed? if (weapons[0].Slot == ItemSlot.TwoHandedWeapon) { supportSkillToImprove = SkillMap.SkillToFriendlyName(typeof(TwoHanded)); } else { supportSkillToImprove = SkillMap.SkillToFriendlyName(typeof(OneHanded)); } } else { // Dual wielding supportSkillToImprove = SkillMap.SkillToFriendlyName(typeof(DualWield)); } } // Build attack chance list for (int i = 0; i < numAttacks; i++) { attackChanceList.Add(CombatHelper.CalculateAttackChance(attackRating, i)); } // Iterate attack chance list and process attacks for (int i = 0; i < attackChanceList.Count; i++) { int damage = 0; bool didHit = false; // Improve support skills if (source.GetType() == typeof(Player)) { skillResults.Add(source.ImproveSkill(supportSkillToImprove, attackChanceList[i])); } // Process the attack if it succeeds and handle skill improvement if (rand.NextDouble() <= attackChanceList[i]) { didHit = true; if (weapons.Count == 0) { // Unarmed damage damage = (source.Tier.Level * 2) + (int)source.ModifiedAttributes.Might * 3 / 5; damage = rand.Next((int)Math.Floor(damage * 0.9), (int)Math.Ceiling(damage * 1.1)); if (source.GetType() == typeof(Player)) { skillResults.Add(source.ImproveSkill(SkillMap.SkillToFriendlyName(typeof(Unarmed)), 1)); } } else if (weapons.Count == 1) { // Single weapon damage if (weapons[0].Slot == ItemSlot.TwoHandedWeapon) { damage = rand.Next(weapons[0].MinDamage, weapons[0].MaxDamage + 1) + ((int)source.ModifiedAttributes.Might / 3); } else { damage = rand.Next(weapons[0].MinDamage, weapons[0].MaxDamage + 1) + ((int)source.ModifiedAttributes.Might / 5); } if (source.GetType() == typeof(Player)) { skillResults.Add(source.ImproveSkill(SkillMap.WeaponTypeToSkillName(weapons[0].WeaponType), 1)); } } else { // Dual wield; alternate weapons for each attack int weaponIndex = i % 2; damage = rand.Next(weapons[weaponIndex].MinDamage, weapons[0].MaxDamage + 1) + ((int)source.ModifiedAttributes.Might / 5); if (source.GetType() == typeof(Player)) { skillResults.Add(source.ImproveSkill(SkillMap.WeaponTypeToSkillName(weapons[weaponIndex].WeaponType), 1)); } } } if (didHit) { if (damage < 1) { damage = 1; } var crit = rand.NextDouble() * 100 <= source.ModifiedQualities.CriticalHit; if (crit) { damage = (int)(damage + (damage * source.ModifiedQualities.CriticalDamage / 100)); } var(hitPoints, died) = target.ModifyCurrentHealth(0 - damage, true); source.IOHandler?.QueueRawOutput($"You{(crit ? " critically" : "")} hit {target.ShortDescription} for {damage} damage{(crit ? "!" : ".")} ({target.CurrentHitPoints}/{target.ModifiedPools.HitPoints})"); target.IOHandler?.QueueRawOutput($"{source.ShortDescription}{(crit ? " critically" : "")} hits you for {damage}{(crit ? "!" : ".")}"); // Handle target death if (died) { Exit(target.Instance); if (target.GetType() == typeof(Mob)) { DataAccess.Remove <Mob>(target.Instance, CacheType.Instance); } if (target.GetType() == typeof(Player)) { target.IOHandler?.QueueRawOutput("You have died!"); var args = new Commands.CommandEventArgs( DataAccess.GetAll <World>(CacheType.Instance)?[0].Instance.ToString(), target, null); new Goto().Execute(args); } source.IOHandler?.QueueRawOutput($"You have slain {target.ShortDescription}!"); if (target.Currency.HasAnyValue()) { source.Currency += target.Currency; source.IOHandler?.QueueRawOutput($"You gain {target.Currency}."); } break; } } } // Handle skill improvement messages // TODO: Add configuration options to allow for suppressions skill improvement messages. foreach (var result in skillResults) { source.IOHandler?.QueueRawOutput(result.ImprovedMessage); } }