Skip to content

Commit

Permalink
Revamp critical chance
Browse files Browse the repository at this point in the history
  • Loading branch information
Janiczek committed Oct 16, 2024
1 parent 06d2717 commit fe518f6
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 30 deletions.
8 changes: 5 additions & 3 deletions src/Data/Fight/Generator.elm
Original file line number Diff line number Diff line change
Expand Up @@ -1170,9 +1170,11 @@ attack_ who ongoing attackStyle baseApCost =
baseCriticalChance =
Logic.baseCriticalChance
{ special = opponent.special
, hasFinesseTrait = Trait.isSelected Trait.Finesse opponent.traits
, moreCriticalPerkRanks = Perk.rank Perk.MoreCriticals opponent.perks
, hasSlayerPerk = Perk.rank Perk.Slayer opponent.perks > 0
, perks = opponent.perks
, traits = opponent.traits
, attackStyle = attackStyle
, chanceToHit = chance
, hitOrMissRoll = roll
}

attackStatsCriticalChanceBonus : Int
Expand Down
21 changes: 20 additions & 1 deletion src/Data/Perk.elm
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ type Perk
-- lvl 18
-- TODO SilentDeath -- needs sneaking in combat
-- lvl 24
-- TODO Sniper -- needs ranged weapons
| Sniper
| Slayer
-- special
| GeckoSkinning
Expand Down Expand Up @@ -146,6 +146,7 @@ all =
, Pathfinder
, Ranger
, Salesman
, Sniper
, Slayer
, Speaker
, Survivalist
Expand Down Expand Up @@ -214,6 +215,9 @@ name perk =
GainLuck ->
"Gain Luck"

Sniper ->
"Sniper"

Slayer ->
"Slayer"

Expand Down Expand Up @@ -427,6 +431,9 @@ maxRank perk =
BonusRateOfFire ->
1

Sniper ->
1

Slayer ->
1

Expand Down Expand Up @@ -498,6 +505,9 @@ encode perk =
GainLuck ->
"gain-luck"

Sniper ->
"sniper"

Slayer ->
"slayer"

Expand Down Expand Up @@ -643,6 +653,9 @@ decoder =
"gain-luck" ->
JD.succeed GainLuck

"sniper" ->
JD.succeed Sniper

"slayer" ->
JD.succeed Slayer

Expand Down Expand Up @@ -817,6 +830,9 @@ isApplicableForLevelup r perk =
GainLuck ->
r.level >= 12 && s.luck < 10

Sniper ->
r.level >= 24 && s.agility >= 8 && s.perception >= 8 && skill Skill.SmallGuns >= 80

Slayer ->
r.level >= 24 && s.agility >= 8 && s.strength >= 8 && skill Skill.Unarmed >= 80

Expand Down Expand Up @@ -1009,6 +1025,9 @@ description perk =
Salesman ->
"You are an adept salesperson. With this Perk you gain +20% towards your Barter skill."

Sniper ->
"You have mastered the firearm as a source of pain. This perk gives you an increased chance to score a critical hit with ranged weapons."

Slayer ->
"The Slayer walks the earth! In hand-to-hand combat all of your hits are upgraded to critical hits, causing destruction and mayhem."

Expand Down
2 changes: 0 additions & 2 deletions src/Frontend/News.elm
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,8 @@ items =
- Weapon/ammo part of item loot of enemies?
- Fight Strategy: number of available ammo
- Fight Strategy: walk away
- Fight Strategy: warning for distance <=0: min distance is 1
- refactoring: fight Opponents shouldn't hold attackStats and naturalArmorClass?
- make movement on the map challenging (random encounters, fights you can't skip, have to heal...)
- logic: Are we adding bonus to Critical Chance based on AimedShot?
- regain conciousness in fight (cost 1/2 of max AP)
~janiczek
Expand Down
106 changes: 82 additions & 24 deletions src/Logic.elm
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ module Logic exposing
, playerCombatCapsGained
, playerCombatXpGained
, price
, regainConciousnessApCost
, sequence
, skillPointCost
, skillPointsPerLevel
Expand Down Expand Up @@ -252,6 +253,11 @@ aimedShotApCostPenalty =
1


aimedShotCriticalChanceBonus : AimedShot -> Int
aimedShotCriticalChanceBonus aimedShot =
aimedShotChanceToHitPenalty aimedShot


aimedShotChanceToHitPenalty : AimedShot -> Int
aimedShotChanceToHitPenalty aimedShot =
case aimedShot of
Expand Down Expand Up @@ -962,51 +968,103 @@ meleeAttackStats r =

baseCriticalChance :
{ special : Special
, hasFinesseTrait : Bool
, moreCriticalPerkRanks : Int
, hasSlayerPerk : Bool
, traits : SeqSet Trait
, perks : SeqDict Perk Int
, attackStyle : AttackStyle
, chanceToHit : Int
, hitOrMissRoll : Int
}
-> Int
baseCriticalChance r =
-- TODO sniper perk and non-unarmed combat
unarmedBaseCriticalChance r


unarmedBaseCriticalChance :
{ special : Special
, hasFinesseTrait : Bool
, moreCriticalPerkRanks : Int
, hasSlayerPerk : Bool
}
-> Int
unarmedBaseCriticalChance r =
let
fromChanceToHit : Int
fromChanceToHit =
max 0 ((r.chanceToHit - r.hitOrMissRoll) // 10)

fromSpecial : Int
fromSpecial =
r.special.luck

fromFinesse : Int
fromFinesse =
if r.hasFinesseTrait then
if SeqSet.member Trait.Finesse r.traits then
10

else
0

fromMoreCriticals : Int
fromMoreCriticals =
r.moreCriticalPerkRanks * 5
5 * Perk.rank Perk.MoreCriticals r.perks

fromSlayer : Int
fromSlayer =
if r.hasSlayerPerk then
if Perk.rank Perk.Slayer r.perks > 0 then
100

else
0

fromSniper : Int
fromSniper =
if Perk.rank Perk.Sniper r.perks > 0 then
-- instead of fromSpecial 1% per Luck point, we get 10% per Luck point.
r.special.luck * 9

else
0

fromAimedShot : AimedShot -> Int
fromAimedShot aim =
aimedShotCriticalChanceBonus aim

unaimed : Int -> Int
unaimed fromEndgamePerk =
(fromChanceToHit
+ fromSpecial
+ fromFinesse
+ fromMoreCriticals
+ fromEndgamePerk
)
|> min 95

aimed : AimedShot -> Int -> Int
aimed aim fromEndgamePerk =
(fromChanceToHit
+ fromSpecial
+ fromFinesse
+ fromMoreCriticals
+ fromAimedShot aim
+ fromEndgamePerk
)
|> min 95
in
(fromSpecial
+ fromFinesse
+ fromMoreCriticals
+ fromSlayer
)
|> min 100
case r.attackStyle of
-- Slayer's 100% wins over the rest
UnarmedUnaimed ->
max fromSlayer (unaimed 0)

UnarmedAimed aim ->
max fromSlayer (aimed aim 0)

MeleeUnaimed ->
max fromSlayer (unaimed 0)

MeleeAimed aim ->
max fromSlayer (aimed aim 0)

-- Sniper is capped to 95%
Throw ->
unaimed fromSniper

ShootSingleUnaimed ->
unaimed fromSniper

ShootSingleAimed aim ->
aimed aim fromSniper

ShootBurst ->
unaimed fromSniper


price :
Expand Down
72 changes: 72 additions & 0 deletions tests/LogicTest.elm
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module LogicTest exposing (test)

import Data.Fight.AimedShot as AimedShot
import Data.Fight.AttackStyle as AttackStyle exposing (AttackStyle(..))
import Data.Item as Item exposing (Item)
import Data.Item.Kind as ItemKind
Expand All @@ -21,10 +22,53 @@ test : Test
test =
Test.describe "Logic"
[ chanceToHitSuite
, baseCriticalChanceSuite
, attackStatsSuite
]


baseCriticalChanceSuite : Test
baseCriticalChanceSuite =
Test.describe "baseCriticalChance"
[ Test.fuzz2 baseCriticalChanceArgsFuzzer
(Fuzz.oneOfValues
[ AttackStyle.UnarmedUnaimed
, AttackStyle.UnarmedAimed AimedShot.Eyes
, AttackStyle.MeleeUnaimed
, AttackStyle.MeleeAimed AimedShot.Torso
]
)
"Slayer always gets 100% if unarmed/melee"
<|
\args attackStyle ->
Logic.baseCriticalChance
{ args
| perks = args.perks |> SeqDict.insert Perk.Slayer 1
, attackStyle = attackStyle
}
|> Expect.equal 100
, Test.fuzz2 baseCriticalChanceArgsFuzzer
(Fuzz.oneOfValues
[ AttackStyle.Throw
, AttackStyle.ShootSingleUnaimed
, AttackStyle.ShootSingleAimed AimedShot.Eyes
, AttackStyle.ShootSingleAimed AimedShot.Torso
, AttackStyle.ShootBurst
]
)
"Sniper always gets 95% if Luck is 10"
<|
\args attackStyle ->
Logic.baseCriticalChance
{ args
| perks = args.perks |> SeqDict.insert Perk.Sniper 1
, attackStyle = attackStyle
, special = args.special |> Special.set Special.Luck 10
}
|> Expect.equal 95
]


attackStatsSuite : Test
attackStatsSuite =
Test.describe "attackStats"
Expand Down Expand Up @@ -282,3 +326,31 @@ chanceToHitArgsFuzzer =
|> Fuzz.andMap TestHelpers.preferredAmmoKindFuzzer
|> Fuzz.andMap TestHelpers.armorClassFuzzer
|> Fuzz.andMap TestHelpers.attackStyleFuzzer


baseCriticalChanceArgsFuzzer :
Fuzzer
{ special : Special
, traits : SeqSet Trait
, perks : SeqDict Perk Int
, attackStyle : AttackStyle
, chanceToHit : Int
, hitOrMissRoll : Int
}
baseCriticalChanceArgsFuzzer =
Fuzz.constant
(\special traits perks attackStyle chanceToHit hitOrMissRoll ->
{ special = special
, traits = traits
, perks = perks
, attackStyle = attackStyle
, chanceToHit = chanceToHit
, hitOrMissRoll = hitOrMissRoll
}
)
|> Fuzz.andMap TestHelpers.specialFuzzer
|> Fuzz.andMap TestHelpers.traitsFuzzer
|> Fuzz.andMap TestHelpers.perksFuzzer
|> Fuzz.andMap TestHelpers.attackStyleFuzzer
|> Fuzz.andMap (Fuzz.intRange 0 95)
|> Fuzz.andMap (Fuzz.intRange 0 100)

0 comments on commit fe518f6

Please sign in to comment.