diff --git a/src/Data/Item.elm b/src/Data/Item.elm index 5e37516..26d9e8c 100644 --- a/src/Data/Item.elm +++ b/src/Data/Item.elm @@ -5844,7 +5844,7 @@ unaimedRange kind = 0 Smg10mm -> - 0 + 25 Jhp10mm -> 0 @@ -6070,7 +6070,7 @@ aimedRange kind = 0 Smg10mm -> - 0 + 25 Jhp10mm -> 0 @@ -6522,7 +6522,7 @@ burstRange kind = 0 Smg10mm -> - 0 + 20 Jhp10mm -> 0 diff --git a/src/Logic.elm b/src/Logic.elm index 51577ef..d9e6b38 100644 --- a/src/Logic.elm +++ b/src/Logic.elm @@ -483,94 +483,99 @@ rangedChanceToHit r = 0 Just equippedWeapon -> - case neededSkill r.attackStyle (Item.types equippedWeapon) of - Nothing -> - -- Wanted to attack in an `attackStyle` the weapon can't do - 0 - - Just weaponSkill -> - let - weaponSkill_ : Int - weaponSkill_ = - Skill.get r.attackerSpecial r.attackerAddedSkillPercentages weaponSkill - - distancePenalty_ : Int - distancePenalty_ = - -- This already contains the Weapon Long Range perk calculations. - distancePenalty - { distanceHexes = r.distanceHexes - , equippedWeapon = r.equippedWeapon - , perception = r.attackerSpecial.perception - } - - ammoArmorClassModifier : Int - ammoArmorClassModifier = - r.equippedAmmo - |> Maybe.map Item.ammoArmorClassModifier - |> Maybe.withDefault 0 - - lightingPenalty_ : Int - lightingPenalty_ = - lightingPenalty - { isItDark = False - , hasNightVisionPerk = Perk.rank Perk.NightVision r.attackerPerks > 0 - , distanceHexes = r.distanceHexes - } - - shotPenalty : Int - shotPenalty = - case r.attackStyle of - Throw -> - 0 - - ShootSingleUnaimed -> - 0 - - ShootSingleAimed aim -> - aimedShotChanceToHitPenalty aim - - ShootBurst -> - burstShotChanceToHitPenalty - - -- These can't happen in ranged: - UnarmedUnaimed -> - 0 - - UnarmedAimed _ -> - 0 + if r.distanceHexes > Item.range r.attackStyle equippedWeapon then + -- Wanted to attack at a range the weapon can't do + 0 - MeleeUnaimed -> - 0 + else + case neededSkill r.attackStyle (Item.types equippedWeapon) of + Nothing -> + -- Wanted to attack in an `attackStyle` the weapon can't do + 0 + + Just weaponSkill -> + let + weaponSkill_ : Int + weaponSkill_ = + Skill.get r.attackerSpecial r.attackerAddedSkillPercentages weaponSkill + + distancePenalty_ : Int + distancePenalty_ = + -- This already contains the Weapon Long Range perk calculations. + distancePenalty + { distanceHexes = r.distanceHexes + , equippedWeapon = r.equippedWeapon + , perception = r.attackerSpecial.perception + } + + ammoArmorClassModifier : Int + ammoArmorClassModifier = + r.equippedAmmo + |> Maybe.map Item.ammoArmorClassModifier + |> Maybe.withDefault 0 + + lightingPenalty_ : Int + lightingPenalty_ = + lightingPenalty + { isItDark = False + , hasNightVisionPerk = Perk.rank Perk.NightVision r.attackerPerks > 0 + , distanceHexes = r.distanceHexes + } + + shotPenalty : Int + shotPenalty = + case r.attackStyle of + Throw -> + 0 + + ShootSingleUnaimed -> + 0 + + ShootSingleAimed aim -> + aimedShotChanceToHitPenalty aim + + ShootBurst -> + burstShotChanceToHitPenalty + + -- These can't happen in ranged: + UnarmedUnaimed -> + 0 + + UnarmedAimed _ -> + 0 + + MeleeUnaimed -> + 0 + + MeleeAimed _ -> + 0 + + strengthRequirementPenalty : Int + strengthRequirementPenalty = + strengthRequirementChanceToHitPenalty + { strength = r.attackerSpecial.strength + , equippedWeapon = equippedWeapon + } + + weaponAccuratePerk : Int + weaponAccuratePerk = + if Item.isAccurateWeapon equippedWeapon then + 20 - MeleeAimed _ -> + else 0 - - strengthRequirementPenalty : Int - strengthRequirementPenalty = - strengthRequirementChanceToHitPenalty - { strength = r.attackerSpecial.strength - , equippedWeapon = equippedWeapon - } - - weaponAccuratePerk : Int - weaponAccuratePerk = - if Item.isAccurateWeapon equippedWeapon then - 20 - - else - 0 - in - (weaponSkill_ - + (8 * r.attackerSpecial.perception) - -- weapon long range perk is already factored into the distancePenalty - + weaponAccuratePerk - - distancePenalty_ - - (r.targetArmorClass + ammoArmorClassModifier) - - lightingPenalty_ - - strengthRequirementPenalty - - shotPenalty - ) - |> clamp 0 95 + in + (weaponSkill_ + + (8 * r.attackerSpecial.perception) + -- weapon long range perk is already factored into the distancePenalty + + weaponAccuratePerk + - distancePenalty_ + - ((r.targetArmorClass * (100 - ammoArmorClassModifier)) // 100) + - lightingPenalty_ + - strengthRequirementPenalty + - shotPenalty + ) + |> clamp 0 95 meleeChanceToHit : @@ -592,6 +597,7 @@ meleeChanceToHit r = |> Maybe.withDefault 1 in if r.distanceHexes > weaponRange then + -- Wanted to attack at a range the weapon can't do 0 else diff --git a/tests/LogicTest.elm b/tests/LogicTest.elm index 27425ca..d985928 100644 --- a/tests/LogicTest.elm +++ b/tests/LogicTest.elm @@ -1,29 +1,157 @@ module LogicTest exposing (test) -import Data.Fight.AttackStyle as AttackStyle -import Data.Special as Special +import Data.Fight.AttackStyle as AttackStyle exposing (AttackStyle(..)) +import Data.Item as Item exposing (Kind(..)) +import Data.Perk exposing (Perk) +import Data.Skill exposing (Skill(..)) +import Data.Special as Special exposing (Special) import Expect +import Fuzz exposing (Fuzzer) import Logic -import SeqDict +import SeqDict exposing (SeqDict) import Test exposing (Test) +import TestHelpers test : Test test = Test.describe "Logic" - [ Test.describe "chanceToHit" - [ Test.test "Unarmed + good range" <| - \() -> + [ Test.describe "chanceToHit" <| + [ Test.fuzz chanceToHitArgsFuzzer "0..95" <| + \args -> + Logic.chanceToHit args + |> Expect.all + [ Expect.atLeast 0 + , Expect.atMost 95 + ] + , Test.fuzz2 chanceToHitArgsFuzzer (Fuzz.maybe TestHelpers.unarmedWeaponKindFuzzer) "Unarmed + good range: can hit" <| + \args maybeWeapon -> Logic.chanceToHit - { attackerAddedSkillPercentages = SeqDict.empty - , attackerPerks = SeqDict.empty - , attackerSpecial = Special.init - , distanceHexes = 1 - , equippedWeapon = Nothing - , equippedAmmo = Nothing - , targetArmorClass = 0 - , attackStyle = AttackStyle.UnarmedUnaimed + { args + | attackStyle = AttackStyle.UnarmedUnaimed + , equippedWeapon = maybeWeapon + , distanceHexes = 1 + , targetArmorClass = 0 } |> Expect.greaterThan 0 + , Test.fuzz2 chanceToHitArgsFuzzer (Fuzz.maybe TestHelpers.unarmedWeaponKindFuzzer) "Unarmed outside range: cannot hit" <| + \args maybeWeapon -> + Logic.chanceToHit + { args + | attackStyle = AttackStyle.UnarmedUnaimed + , equippedWeapon = maybeWeapon + , distanceHexes = args.distanceHexes + 1 + } + |> Expect.equal 0 + , Test.fuzz2 chanceToHitArgsFuzzer TestHelpers.meleeWeaponKindFuzzer "Melee + good range: can hit" <| + \args weapon -> + Logic.chanceToHit + { args + | attackStyle = AttackStyle.MeleeUnaimed + , equippedWeapon = Just weapon + , distanceHexes = 1 + , targetArmorClass = 0 + } + |> Expect.greaterThan 0 + , Test.fuzz2 chanceToHitArgsFuzzer TestHelpers.meleeWeaponKindFuzzer "Melee outside range: cannot hit" <| + \args weapon -> + Logic.chanceToHit + { args + | attackStyle = AttackStyle.MeleeUnaimed + , equippedWeapon = Just weapon + , distanceHexes = args.distanceHexes + 1 + } + |> Expect.equal 0 + , Test.fuzz2 chanceToHitArgsFuzzer TestHelpers.gunKindFuzzer "Ranged + good range: can hit" <| + \args weapon -> + Logic.chanceToHit + { args + | attackStyle = AttackStyle.ShootSingleUnaimed + , attackerSpecial = args.attackerSpecial |> Special.set Special.Strength 10 + , equippedWeapon = Just weapon + , distanceHexes = 1 + , targetArmorClass = 0 + } + |> Expect.greaterThan 0 + , Test.fuzz2 chanceToHitArgsFuzzer TestHelpers.gunKindFuzzer "Ranged outside range: cannot hit" <| + \args weapon -> + Logic.chanceToHit + { args + | attackStyle = AttackStyle.ShootSingleUnaimed + , equippedWeapon = Just weapon + , distanceHexes = args.distanceHexes + 80 + } + |> Expect.equal 0 ] ] + + +log : + { attackerAddedSkillPercentages : SeqDict Skill Int + , attackerPerks : SeqDict Perk Int + , attackerSpecial : Special + , distanceHexes : Int + , equippedWeapon : Maybe Item.Kind + , equippedAmmo : Maybe Item.Kind + , targetArmorClass : Int + , attackStyle : AttackStyle + } + -> + { attackerAddedSkillPercentages : SeqDict Skill Int + , attackerPerks : SeqDict Perk Int + , attackerSpecial : Special + , distanceHexes : Int + , equippedWeapon : Maybe Item.Kind + , equippedAmmo : Maybe Item.Kind + , targetArmorClass : Int + , attackStyle : AttackStyle + } +log args = + let + _ = + { attackerAddedSkillPercentages = SeqDict.toList args.attackerAddedSkillPercentages + , attackerPerks = SeqDict.toList args.attackerPerks + , attackerSpecial = args.attackerSpecial + , distanceHexes = args.distanceHexes + , equippedWeapon = args.equippedWeapon + , equippedAmmo = args.equippedAmmo + , targetArmorClass = args.targetArmorClass + , attackStyle = args.attackStyle + } + |> Debug.log "chanceToHit args" + in + args + + +chanceToHitArgsFuzzer : + Fuzzer + { attackerAddedSkillPercentages : SeqDict Skill Int + , attackerPerks : SeqDict Perk Int + , attackerSpecial : Special + , distanceHexes : Int + , equippedWeapon : Maybe Item.Kind + , equippedAmmo : Maybe Item.Kind + , targetArmorClass : Int + , attackStyle : AttackStyle + } +chanceToHitArgsFuzzer = + Fuzz.map8 + (\attackerAddedSkillPercentages attackerPerks attackerSpecial distanceHexes equippedWeapon equippedAmmo targetArmorClass attackStyle -> + { attackerAddedSkillPercentages = attackerAddedSkillPercentages + , attackerPerks = attackerPerks + , attackerSpecial = attackerSpecial + , distanceHexes = distanceHexes + , equippedWeapon = equippedWeapon + , equippedAmmo = equippedAmmo + , targetArmorClass = targetArmorClass + , attackStyle = attackStyle + } + ) + TestHelpers.addedSkillPercentagesFuzzer + TestHelpers.perksFuzzer + TestHelpers.specialFuzzer + TestHelpers.distanceFuzzer + TestHelpers.equippedWeaponKindFuzzer + TestHelpers.equippedAmmoKindFuzzer + TestHelpers.armorClassFuzzer + TestHelpers.attackStyleFuzzer diff --git a/tests/TestHelpers.elm b/tests/TestHelpers.elm index 2ca100d..2c16782 100644 --- a/tests/TestHelpers.elm +++ b/tests/TestHelpers.elm @@ -1,4 +1,55 @@ -module TestHelpers exposing (..) +module TestHelpers exposing + ( addedSkillPercentagesFuzzer + , ammoKindFuzzer + , armorClassFuzzer + , armorKindFuzzer + , attackStatsFuzzer + , attackStyleFuzzer + , capsFuzzer + , commandFuzzer + , conditionFuzzer + , deadEndToString + , distanceFuzzer + , dropsFuzzer + , enemyTypeFuzzer + , equippedAmmoKindFuzzer + , equippedArmorKindFuzzer + , equippedWeaponKindFuzzer + , expectEqualParseResult + , fightStrategyFuzzer + , gunKindFuzzer + , healingItemKindFuzzer + , hpFuzzer + , ifDataFuzzer + , itemFuzzer + , itemKindFuzzer + , itemsFuzzer + , maxApFuzzer + , maxHpFuzzer + , meleeWeaponKindFuzzer + , mostlyHealingItemKindFuzzer + , multilineInput + , naturalArmorClassFuzzer + , operatorDataFuzzer + , operatorFuzzer + , opponentFuzzer + , opponentTypeFuzzer + , parserTest + , perksFuzzer + , playerNameFuzzer + , playerOpponentFuzzer + , posixFuzzer + , problemToString + , randomSeedFuzzer + , removeNewlinesAtEnds + , sequenceFuzzer + , specialFuzzer + , traitsFuzzer + , unarmedWeaponKindFuzzer + , valueFuzzer + , weaponKindFuzzer + , xpFuzzer + ) import Data.Enemy as Enemy import Data.Fight as Fight @@ -39,14 +90,9 @@ import Test exposing (Test) import Time exposing (Posix) -oneOfValues : List a -> Fuzzer a -oneOfValues values = - Fuzz.oneOf <| List.map Fuzz.constant values - - playerNameFuzzer : Fuzzer PlayerName playerNameFuzzer = - oneOfValues + Fuzz.oneOfValues [ "janiczek" , "djetelina" , "Athano" @@ -84,6 +130,16 @@ maxHpFuzzer = Fuzz.intRange 1 200 +distanceFuzzer : Fuzzer Int +distanceFuzzer = + Fuzz.intRange 1 100 + + +armorClassFuzzer : Fuzzer Int +armorClassFuzzer = + Fuzz.intRange 0 50 + + maxApFuzzer : Fuzzer Int maxApFuzzer = Fuzz.intRange 5 20 @@ -271,7 +327,7 @@ randomSeedFuzzer = enemyTypeFuzzer : Fuzzer Enemy.Type enemyTypeFuzzer = - oneOfValues Enemy.allTypes + Fuzz.oneOfValues Enemy.allTypes traitsFuzzer : Fuzzer (SeqSet Trait) @@ -352,28 +408,58 @@ armorKindFuzzer : Fuzzer Item.Kind armorKindFuzzer = Item.all |> List.filter Item.isArmor - |> oneOfValues + |> Fuzz.oneOfValues weaponKindFuzzer : Fuzzer Item.Kind weaponKindFuzzer = Item.all |> List.filter Item.isWeapon - |> oneOfValues + |> Fuzz.oneOfValues + + +meleeWeaponKindFuzzer : Fuzzer Item.Kind +meleeWeaponKindFuzzer = + Item.all + |> List.filter (\kind -> List.member Item.MeleeWeapon (Item.types kind)) + |> Fuzz.oneOfValues + + +unarmedWeaponKindFuzzer : Fuzzer Item.Kind +unarmedWeaponKindFuzzer = + Item.all + |> List.filter (\kind -> List.member Item.UnarmedWeapon (Item.types kind)) + |> Fuzz.oneOfValues + + +gunKindFuzzer : Fuzzer Item.Kind +gunKindFuzzer = + Item.all + |> List.filter + (\kind -> + let + types = + Item.types kind + in + List.member Item.SmallGun types + || List.member Item.BigGun types + || List.member Item.EnergyWeapon types + ) + |> Fuzz.oneOfValues ammoKindFuzzer : Fuzzer Item.Kind ammoKindFuzzer = Item.all |> List.filter Item.isAmmo - |> oneOfValues + |> Fuzz.oneOfValues itemFuzzer : Fuzzer Item itemFuzzer = Fuzz.map3 Item (Fuzz.intRange 0 99999) - (oneOfValues Item.all) + (Fuzz.oneOfValues Item.all) (Fuzz.intRange 1 500) @@ -402,7 +488,7 @@ addedSkillPercentagesFuzzer = (Fuzz.intRange -10 300) ) |> Fuzz.sequence - |> Fuzz.map SeqDict.fromList + |> Fuzz.map (List.filter (\( _, pct ) -> pct /= 0) >> SeqDict.fromList) specialFuzzer : Fuzzer Special