public double Attack(StrikingDummy target, WeaponSkills weaponSkill) { var animationLocked = QueuedEffects.ContainsKey(Skills.StatusEffects.AnimationLocked); if (GcdDuration > 0 || animationLocked) { return(0); } var potency = WeaponLibrary.WeaponPotencies(weaponSkill, LastSkills); var damage = FormulaLibrary.WeaponSkills(potency, Weapon.WeaponDamage, GetDexterity(), Det, CalculateMultiplier(target, DamageType.Slashing)); if (StatusEffects.ContainsKey(Skills.StatusEffects.Duality)) { damage *= 2; StatusEffects.Remove(Skills.StatusEffects.Duality); } else { damage = (damage * CalculateCritChance() * FormulaLibrary.CritDmg(Crt)) + (damage * (1 - CalculateCritChance())); } WeaponLibrary.QueueEffect(this, target, weaponSkill); var gcdMultiplier = StatusEffects.ContainsKey(Skills.StatusEffects.Huton) ? 0.85 : 1.00; GcdDuration = (int)TimeSpan.FromSeconds(FormulaLibrary.Gcd(Sks, gcdMultiplier)).TotalMilliseconds; LastSkills.Push(weaponSkill); return(damage); }
public double CalculateCritChance() { var critChance = FormulaLibrary.CritChance(Crt); if (StatusEffects.ContainsKey(Skills.StatusEffects.InternalRelease)) { critChance += 0.10; } return(critChance); }
// -- Methods public double AutoAttack(StrikingDummy target) { if (AutoAttackDuration > 0) { return(0); } var damage = FormulaLibrary.AutoAttack(Weapon.AutoAttack, GetDexterity(), Det, Weapon.Delay, CalculateMultiplier(target, DamageType.Slashing)); damage = (damage * CalculateCritChance() * FormulaLibrary.CritDmg(Crt)) + (damage * (1 - CalculateCritChance())); AutoAttackDuration = StatusEffects.ContainsKey(Skills.StatusEffects.Huton) ? (int)(TimeSpan.FromSeconds(Weapon.Delay).TotalMilliseconds * 0.85) : (int)TimeSpan.FromSeconds(Weapon.Delay).TotalMilliseconds; return(damage); }
public double CalculateDamageOverTimeMultiplier(DamageType damageType, Actor target) { var multiplier = FormulaLibrary.SkillSpeedMultiplier(Sks); if (StatusEffects.ContainsKey(Skills.StatusEffects.BloodForBlood)) { multiplier *= 1.10; } if (target.StatusEffects.ContainsKey(Skills.StatusEffects.TrickAttack)) { multiplier *= 1.10; } if (damageType == DamageType.Slashing) { if (target.StatusEffects.ContainsKey(Skills.StatusEffects.DancingEdge) || target.StatusEffects.ContainsKey(Skills.StatusEffects.StormsEye)) { multiplier *= 1.10; } } if (damageType == DamageType.Physical || damageType == DamageType.Slashing) { if (StatusEffects.ContainsKey(Skills.StatusEffects.KissOfTheWasp)) { multiplier *= 1.20; } } if (damageType == DamageType.Magical) { if (target.StatusEffects.ContainsKey(Skills.StatusEffects.FoeRequiem)) { multiplier *= 1.10; } } return(multiplier); }
public void DecrementDamageOverTimeDuration(bool verbose) { foreach (var dot in DamageOverTimeEffects.ToList()) { var dotEffect = DamageOverTimeEffects[dot.Key]; if (GameEngine.GetCurrentGameTime() % (long)TimeSpan.FromSeconds(3).TotalMilliseconds == 0) { var damage = FormulaLibrary.WeaponSkills(dotEffect.Potency, dotEffect.WeaponDamage, dotEffect.Dex, dotEffect.Det, dotEffect.Multiplier); damage = (damage * dotEffect.CritChance * FormulaLibrary.CritDmg(dotEffect.Crt)) + (damage * (1 - dotEffect.CritChance)); if (verbose) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"{dot.Key} ticks for {damage}!"); } dotEffect.Target.DamageTaken += damage; } dotEffect.Duration--; if (dotEffect.Duration <= 0) { if (verbose) { Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine($"{dot.Key} has fallen off!"); } DamageOverTimeEffects.Remove(dot.Key); } else { DamageOverTimeEffects[dot.Key] = dotEffect; } } }
private static void RegenerateSkillSpeedRanks() { var minSkillSpeed = Convert.ToInt32(ConfigurationManager.AppSettings["MIN_SKS"]); var maxSkillSpeed = Convert.ToInt32(ConfigurationManager.AppSettings["MAX_SKS"]); Console.WriteLine($"Regenerating skill speed rankings ... values from { minSkillSpeed } to { maxSkillSpeed }. If this range is not the full range desired, please enter the full range in the program configuration."); var skillSpeedDps = new Dictionary<int, double>(); for (var i = minSkillSpeed; i <= maxSkillSpeed; i++) { var player = PlayerFactory.CreatePlayer(); player.Sks = i; var dps = RunSimulation(false, player); Console.WriteLine($"Skill Speed: { i }, DPS: { dps }"); skillSpeedDps.Add(i, dps); } using (var filestream = File.OpenWrite(FormulaLibrary.GetSkillSpeedRanksFilePath())) using (var writer = new StreamWriter(filestream)) { writer.Write(JsonConvert.SerializeObject(skillSpeedDps)); } }
private static void RunBestInSlotSolver() { var numIterations = 0; var accFilter = 0; var sksFilter = 0; var strictFilter = 0; var primaryStatFilter = 0; var secondaryStatFilter = 0; var recordedRuns = new List<RecordedDPS>(); // Read and filter inventory. var weaponItems = FilterWeapons(Inventory.Config.MainHand.Cast<InventorySection.MainHandElementCollection.ItemElement>().ToList()); var headItems = FilterItems<InventorySection.HeadElementCollection.ItemElement>(Inventory.Config.Head.Cast<InventorySection.HeadElementCollection.ItemElement>().ToList()); var bodyItems = FilterItems<InventorySection.BodyElementCollection.ItemElement>(Inventory.Config.Body.Cast<InventorySection.BodyElementCollection.ItemElement>().ToList()); var handItems = FilterItems<InventorySection.HandsElementCollection.ItemElement>(Inventory.Config.Hands.Cast<InventorySection.HandsElementCollection.ItemElement>().ToList()); var waistItems = FilterItems<InventorySection.WaistElementCollection.ItemElement>(Inventory.Config.Waist.Cast<InventorySection.WaistElementCollection.ItemElement>().ToList()); var legItems = FilterItems<InventorySection.LegsElementCollection.ItemElement>(Inventory.Config.Legs.Cast<InventorySection.LegsElementCollection.ItemElement>().ToList()); var feetItems = FilterItems<InventorySection.FeetElementCollection.ItemElement>(Inventory.Config.Feet.Cast<InventorySection.FeetElementCollection.ItemElement>().ToList()); var neckItems = FilterItems<InventorySection.NeckElementCollection.ItemElement>(Inventory.Config.Neck.Cast<InventorySection.NeckElementCollection.ItemElement>().ToList()); var earItems = FilterItems<InventorySection.EarsElementCollection.ItemElement>(Inventory.Config.Ears.Cast<InventorySection.EarsElementCollection.ItemElement>().ToList()); var wristItems = FilterItems<InventorySection.WristsElementCollection.ItemElement>(Inventory.Config.Wrists.Cast<InventorySection.WristsElementCollection.ItemElement>().ToList()); // We don't filter rings or food because they're a little different than just stat sticks. Rings are unique and food boost stats by percentages. var leftRingItems = Inventory.Config.LeftRing.Cast<InventorySection.LeftRingElementCollection.ItemElement>().ToList(); var rightRingItems = Inventory.Config.RightRing.Cast<InventorySection.RightRingElementCollection.ItemElement>().ToList(); var foodItems = Inventory.Config.Food.Cast<InventorySection.FoodElementCollection.ItemElement>().ToList(); // for all combinations foreach (var weapon in weaponItems) { foreach (var head in headItems) { foreach (var body in bodyItems) { foreach (var hands in handItems) { foreach (var waist in waistItems) { foreach (var legs in legItems) { foreach (var feet in feetItems) { foreach (var neck in neckItems) { foreach (var ears in earItems) { foreach (var wrists in wristItems) { foreach (var leftRing in leftRingItems) { foreach (var rightRing in rightRingItems) { // rings are unique if (rightRing.Name.Equals(leftRing.Name, StringComparison.OrdinalIgnoreCase)) { continue; } foreach (var food in foodItems) { var player = PlayerFactory.CreatePlayer(true); // add weapon player.Weapon = new Weapon { AutoAttack = weapon.Aa, WeaponDamage = weapon.Wd, Delay = weapon.Delay }; // add stats player.Dex += weapon.Dex + head.Dex + body.Dex + hands.Dex + waist.Dex + legs.Dex + feet.Dex + neck.Dex + ears.Dex + wrists.Dex + leftRing.Dex + rightRing.Dex; player.Acc += weapon.Acc + head.Acc + body.Acc + hands.Acc + waist.Acc + legs.Acc + feet.Acc + neck.Acc + ears.Acc + wrists.Acc + leftRing.Acc + rightRing.Acc; player.Crt += weapon.Crt + head.Crt + body.Crt + hands.Crt + waist.Crt + legs.Crt + feet.Crt + neck.Crt + ears.Crt + wrists.Crt + leftRing.Crt + rightRing.Crt; player.Det += weapon.Det + head.Det + body.Det + hands.Det + waist.Det + legs.Det + feet.Det + neck.Det + ears.Det + wrists.Det + leftRing.Det + rightRing.Det; player.Sks += weapon.Sks + head.Sks + body.Sks + hands.Sks + waist.Sks + legs.Sks + feet.Sks + neck.Sks + ears.Sks + wrists.Sks + leftRing.Sks + rightRing.Sks; // add food bonus var accBonus = (int)(player.Acc * food.AccModifier) - (int)player.Acc; player.Acc += accBonus >= food.MaxAccBonus ? food.MaxAccBonus : accBonus; var crtBonus = (int)(player.Crt * food.CrtModifier) - (int)player.Crt; player.Crt += crtBonus >= food.MaxCrtBonus ? food.MaxCrtBonus : crtBonus; var detBonus = (int)(player.Det * food.DetModifier) - (int)player.Det; player.Det += detBonus >= food.MaxDetBonus ? food.MaxDetBonus : detBonus; var sksBonus = (int)(player.Sks * food.SksModifier) - (int)player.Sks; player.Sks += sksBonus >= food.MaxSksBonus ? food.MaxSksBonus : sksBonus; // add party bonus player.Dex = (int)(player.Dex * 1.03); // run heuristics to pre-filter worse combinations if (player.Acc < Convert.ToInt32(ConfigurationManager.AppSettings["MIN_ACC"])) { accFilter++; continue; } if (player.Sks < Convert.ToInt32(ConfigurationManager.AppSettings["MIN_SKS"]) || player.Sks > Convert.ToInt32(ConfigurationManager.AppSettings["MAX_SKS"])) { sksFilter++; continue; } try { if (recordedRuns.Count > 0) { var skip = false; foreach (var recordedRun in recordedRuns) { var recordedPlayer = recordedRun.Player; if (player.Weapon.WeaponDamage <= recordedPlayer.Weapon.WeaponDamage && player.Dex <= recordedPlayer.Dex && player.Crt <= recordedPlayer.Crt && player.Det <= recordedPlayer.Det && FormulaLibrary.GetSkillSpeedRank(player.Sks) <= FormulaLibrary.GetSkillSpeedRank(recordedPlayer.Sks)) { strictFilter++; skip = true; break; } if (player.Weapon.WeaponDamage <= recordedPlayer.Weapon.WeaponDamage && player.Dex <= recordedPlayer.Dex && player.Crt >= recordedPlayer.Crt && player.Det >= recordedPlayer.Det && FormulaLibrary.GetSkillSpeedRank(player.Sks) <= FormulaLibrary.GetSkillSpeedRank(recordedPlayer.Sks)) { var crtGain = player.Crt - recordedPlayer.Crt; var detGain = player.Det - recordedPlayer.Det; var secondaryStatGain = crtGain * 1.5 + detGain; var strLoss = recordedPlayer.Dex - player.Dex; var wdLoss = recordedPlayer.Weapon.WeaponDamage - player.Weapon.WeaponDamage; var primaryStatLoss = strLoss + 5 * wdLoss; if (secondaryStatGain < primaryStatLoss) { primaryStatFilter++; skip = true; break; } } if (player.Weapon.WeaponDamage <= recordedPlayer.Weapon.WeaponDamage && player.Dex <= recordedPlayer.Dex && FormulaLibrary.GetSkillSpeedRank(player.Sks) <= FormulaLibrary.GetSkillSpeedRank(recordedPlayer.Sks)) { if (player.Crt > recordedPlayer.Crt && player.Det < recordedPlayer.Det) { var critGain = player.Crt - recordedPlayer.Crt; var detLoss = recordedPlayer.Det - player.Det; if (critGain * 1.75 <= detLoss) { secondaryStatFilter++; skip = true; break; } } if (player.Crt < recordedPlayer.Crt && player.Det > recordedPlayer.Det) { var detGain = player.Det - recordedPlayer.Det; var critLoss = recordedPlayer.Crt - player.Crt; if (detGain * 1.25 <= critLoss) { secondaryStatFilter++; skip = true; break; } } } } if (skip) { continue; } } } catch (Exception) { continue; } // run the sim try { numIterations++; var dps = RunSimulation(false, player); recordedRuns.Add(new RecordedDPS { Player = player, DPS = dps, EquipmentNames = new List<string> { weapon.Name, head.Name, body.Name, hands.Name, waist.Name, legs.Name, feet.Name, neck.Name, ears.Name, wrists.Name, leftRing.Name, rightRing.Name, food.Name } }); Console.WriteLine($"Iteration {numIterations} - DPS is: {dps}"); } catch (Exception) { // suppress errors due to SKS } } } } } } } } } } } } } } var counter = 1; foreach (var run in recordedRuns.OrderByDescending(r => r.DPS)) { Console.WriteLine($"--Number {counter++}"); Console.WriteLine("DPS: " + run.DPS); Console.WriteLine("Player Equipment:"); run.EquipmentNames.ForEach(Console.WriteLine); Console.WriteLine("Player stats:"); Console.WriteLine($"Dex: {run.Player.Dex}"); Console.WriteLine($"Acc: {run.Player.Acc}"); Console.WriteLine($"Crt: {run.Player.Crt}"); Console.WriteLine($"Det: {run.Player.Det}"); Console.WriteLine($"Sks: {run.Player.Sks}"); if (counter > 250) { break; } } Console.WriteLine("Diagnostics: "); Console.WriteLine($"AccFilter: {accFilter}"); Console.WriteLine($"SksFilter: {sksFilter}"); Console.WriteLine($"StrictFilter: {strictFilter}"); Console.WriteLine($"PrimaryStatFilter: {primaryStatFilter}"); Console.WriteLine($"SecondaryStatFilter: {secondaryStatFilter}"); }
public double UseDamageSpell(StrikingDummy target, Spells spell, bool verbose = false) { if (GcdDuration <= 0 && spell != Spells.PrePullSuiton) { var remainingCd = Cooldowns.ContainsKey(spell) ? Cooldowns[spell] : 0; throw new Exception($"Warning! Using off-gcd ability { spell } when GCD is available! Remaining cooldown on { spell }: { remainingCd }"); } if (Cooldowns.ContainsKey(spell) || QueuedEffects.ContainsKey(Skills.StatusEffects.AnimationLocked)) { return(0); } if (spell == Spells.FumaShuriken || spell == Spells.Raiton || spell == Spells.Suiton) { if (Cooldowns.ContainsKey(Spells.FumaShuriken) || Cooldowns.ContainsKey(Spells.Raiton) || Cooldowns.ContainsKey(Spells.Suiton)) { return(0); } } if (spell == Spells.TrickAttack) { if (!StatusEffects.ContainsKey(Skills.StatusEffects.Suiton)) { throw new Exception($"Warning! Cannot use TA without Suiton active!"); } if (verbose) { Console.WriteLine("Suiton removed after TA cast!"); } StatusEffects.Remove(Skills.StatusEffects.Suiton); } var potency = SpellLibrary.SpellPotencies(spell); var multiplier = CalculateMultiplier(target, SpellLibrary.SpellDamageType(spell)); var damage = FormulaLibrary.WeaponSkills(potency, Weapon.WeaponDamage, GetDexterity(), Det, multiplier); var guaranteeCrit = false; if (spell == Spells.FumaShuriken || spell == Spells.Raiton || spell == Spells.Suiton) { if (StatusEffects.ContainsKey(Skills.StatusEffects.Kassatsu)) { guaranteeCrit = true; StatusEffects.Remove(Skills.StatusEffects.Kassatsu); } } if (guaranteeCrit) { damage *= FormulaLibrary.CritDmg(Crt); } else { damage = (damage * CalculateCritChance() * FormulaLibrary.CritDmg(Crt)) + (damage * (1 - CalculateCritChance())); } SpellLibrary.QueueEffect(this, spell, target, verbose); if (spell == Spells.FumaShuriken) { Cooldowns.Add(spell, 500 + SpellLibrary.SpellCooldowns(spell)); } else if (spell == Spells.Raiton) { Cooldowns.Add(spell, 1000 + SpellLibrary.SpellCooldowns(spell)); } else if (spell == Spells.Suiton) { Cooldowns.Add(spell, 1500 + SpellLibrary.SpellCooldowns(spell)); } else if (spell == Spells.PrePullSuiton) { Cooldowns.Add(Spells.Suiton, SpellLibrary.SpellCooldowns(Spells.Suiton)); } else { Cooldowns.Add(spell, SpellLibrary.SpellCooldowns(spell)); } return(damage); }