// level 58 test // expected hit 3267; calc 3265 // expected crit 10,633; calc 10626 // expected pc 8821; calc 8815 // expected crit pc 28,710; calc 28691 type Crit = private { critc: decimal } with static member ValidateCritRate crit = if crit<0.0m || crit > 1.0m then Error () else Ok crit static member Create value = Crit.TryCreate value |> function | Ok v -> v | Error e -> failwithf "Crit create error: %A" e static member TryCreate value = Crit.ValidateCritRate value |> Result.map(fun x -> {critc=x}) member x.CritChance = x.critc override x.ToString() = if x.critc > 0.999m then "100%" else $"%0.2f{x.critc * 100.0m}%%" type Weapon = { Name: string Dmg: int Crit: Crit CritDmg: decimal SlotsRemaining: int } type WeaponSummary = { Name: string AvgDmg: int SlotsRemaining: int Hit: int CritHit: int Dmg: int Crit: string CritDmg: decimal PerfectComboDmg: int CritPerfectComboDmg: int } // varies by class and level // hit unarmed to check? //let unarmed= 931 // perfect combo 170% made 931 -> 2514 let characterBaseDmg = 338 // thief 58-> 338, 71 -> 506 // perfect Combo starts at 100% so it is effectively + 1 let includeBaseDmg, perfectComboOpt = true, Some 1.7m let critBuff = Some 0.15m let baseAtk = 1.75m // + 1.0m // baseAtk value is + 1 printfn "BaseAtk Bonus%%: %0.2f" baseAtk if includeBaseDmg then printfn "BaseDmg: %i" characterBaseDmg perfectComboOpt |> Option.iter(printfn "%0.2f Perfect Combo") let nextWeapon = { Name="Scimitar" Dmg=984 Crit=Crit.Create 0.3149m CritDmg=92.21m SlotsRemaining=8 } let addDmg dmg count (w: Weapon) = { w with Name = w.Name + $" Dmg%i{count}"; Dmg=w.Dmg + dmg * count } let addCrit (crit:Crit) count (w: Weapon) = let addSomeCrit count = w.Crit.CritChance + (crit.CritChance) * decimal count |> Crit.TryCreate let setupNewWeapon nextCrit count = { w with Name = w.Name + $" Crit%i{count}"; Crit = nextCrit } [ count .. -1 .. 0] |> Seq.choose(fun count -> addSomeCrit count |> function | Ok nextCrit -> setupNewWeapon nextCrit count |> Some | Error _ -> None ) let addCritDmg cDmg count (w: Weapon) = { w with Name = w.Name + $" CritDmg%i{count}"; CritDmg= w.CritDmg + cDmg * decimal count } let calcPerfectCombo hit = match perfectComboOpt with | Some pc -> decimal hit * (1.0m + pc) |> Some | None -> None let summarize avghit (hit:int) (critMod:decimal) (w: Weapon) = let pcd = calcPerfectCombo hit let cHit = decimal hit * critMod |> int { Name=w.Name Hit=hit SlotsRemaining=w.SlotsRemaining AvgDmg= avghit Dmg= w.Dmg Crit= string w.Crit CritDmg= w.CritDmg CritHit= cHit PerfectComboDmg= pcd |> Option.map int |> Option.defaultValue hit CritPerfectComboDmg= pcd |> Option.map((*) critMod) |> Option.map int |> Option.defaultValue cHit } let weapons = [ { // at 58, 175% base, 170% perfect combo this weapon is hitting for 3267/8821 perfect combo/10633 crit/28710 p crit Name="Jagged Blade" Dmg=2_336 Crit=Crit.Create 0.4424m SlotsRemaining=0 CritDmg=125.47m } { Name="Dagger" Dmg=369 Crit= Crit.Create 0.6563m SlotsRemaining=0 CritDmg=437.99m } nextWeapon nextWeapon |> addDmg 54 10 match nextWeapon |> addCrit (Crit.Create 0.0977m) 5 |> Seq.tryHead with | None -> () | Some w -> w nextWeapon |> addCritDmg 45.07m 10 { Name="test" Dmg=100 SlotsRemaining=0 Crit=Crit.Create 1.0m CritDmg=100.0m } ] let addCharacterBase dmg = if includeBaseDmg then (decimal characterBaseDmg * (1.0m + baseAtk)) else (1.0m + baseAtk) |> int |> (+) dmg weapons |> List.map(fun w -> // unless crit % can go over 100%, let's fail if we find one if w.Crit.CritChance < 0.0m || w.Crit.CritChance > 1.0m then failwith $"Unexpected crit rate %s{w.Name}:%0.2f{w.Crit.CritChance}" let hit = w.Dmg |> addCharacterBase |> int let critMod = (200.0m + w.CritDmg) / 100.0m let critChance = match critBuff with | None -> w.Crit.CritChance | Some cb -> let x = w.Crit.CritChance + cb let value = min x 1.0m //(w.Name,x,value).Dump("x") value let hitWeight = 1.0m - critChance let critWeight = critChance let avgHit = decimal hit * hitWeight + decimal (decimal hit * critMod) * critWeight |> int //(w.Crit,critWeight, avgHit).Dump() summarize avgHit hit critMod w ) |> List.sortByDescending(fun ws -> ws.AvgDmg, ws.Name) |> Dump |> ignore printfn "Level 58 thief, 175%% base, 170%% perfect combo" printfn "Jagged Blade: 2,336dmg, 44.24%% crit, 125.47%% crit damage" printfn "game gives 3267 hit, 10633 crit" printfn "Perfect Combos: 8821, 28710 crit"