From 470bcedff91e2713e1f900ac5708159971f05162 Mon Sep 17 00:00:00 2001 From: Martin Janiczek Date: Sun, 6 Oct 2024 16:14:07 +0200 Subject: [PATCH] Fix a bug with combat at distances >0, >1 --- src/Data/Fight/Generator.elm | 13 +-- src/Logic.elm | 21 +++-- tests/Data/Fight/GeneratorTest.elm | 126 +++++++++++++++++++++++------ 3 files changed, 122 insertions(+), 38 deletions(-) diff --git a/src/Data/Fight/Generator.elm b/src/Data/Fight/Generator.elm index 0e3332e..3245f13 100644 --- a/src/Data/Fight/Generator.elm +++ b/src/Data/Fight/Generator.elm @@ -1,5 +1,6 @@ module Data.Fight.Generator exposing ( Fight + , attack_ , enemyOpponentGenerator , generator , playerOpponent @@ -154,9 +155,7 @@ subtractAp who action ongoing = subtractDistance : Int -> OngoingFight -> OngoingFight subtractDistance n ongoing = - -- TODO TODO TODO TODO check everywhere that distance is 1, not 0 - -- THEN go read through the melee combat and make sure no extra penalties are happening - -- THEN still in melee combat, make sure ranges are taken into account: super sledge can hit from 2 hexes away but knives / unarmed needs 1 hex, etc. + -- TODO TODO TODO TODO still in melee combat, make sure ranges are taken into account: super sledge can hit from 2 hexes away but knives / unarmed needs 1 hex, etc. -- THEN you can probably go for ranged combat. { ongoing | distanceHexes = max 1 <| ongoing.distanceHexes - n } @@ -948,7 +947,7 @@ moveForward who ongoing = let maxPossibleMove : Int maxPossibleMove = - min ongoing.distanceHexes (opponentAp who ongoing) + min (ongoing.distanceHexes - 1) (opponentAp who ongoing) action : Fight.Action action = @@ -1118,8 +1117,12 @@ attack_ who ongoing attackStyle baseApCost = chance : Int chance = chanceToHit who ongoing attackStyle + + weaponRange : Int + weaponRange = + Logic.weaponRange opponent.equippedWeapon attackStyle in - if ongoing.distanceHexes /= 0 then + if ongoing.distanceHexes > weaponRange then rejectCommand who Attack_NotCloseEnough ongoing else if opponentAp who ongoing < apCost_ then diff --git a/src/Logic.elm b/src/Logic.elm index c5e1a9d..2a87ebd 100644 --- a/src/Logic.elm +++ b/src/Logic.elm @@ -39,6 +39,7 @@ module Logic exposing , unarmedAttackStats , unarmedBaseCriticalChance , unarmedRange + , weaponRange , xpGained ) @@ -578,6 +579,17 @@ rangedChanceToHit r = |> clamp 0 95 +weaponRange : Maybe Item.Kind -> AttackStyle -> Int +weaponRange equippedWeapon attackStyle = + case equippedWeapon of + Nothing -> + -- Nothing equipped, means this is an unarmed attack + 1 + + Just weapon -> + Item.range attackStyle weapon + + meleeChanceToHit : { r | attackerAddedSkillPercentages : SeqDict Skill Int @@ -589,14 +601,7 @@ meleeChanceToHit : } -> Int meleeChanceToHit r = - let - weaponRange : Int - weaponRange = - r.equippedWeapon - |> Maybe.map (Item.range r.attackStyle) - |> Maybe.withDefault 1 - in - if r.distanceHexes > weaponRange then + if r.distanceHexes > weaponRange r.equippedWeapon r.attackStyle then -- Wanted to attack at a range the weapon can't do 0 diff --git a/tests/Data/Fight/GeneratorTest.elm b/tests/Data/Fight/GeneratorTest.elm index 34df636..7c2f4a3 100644 --- a/tests/Data/Fight/GeneratorTest.elm +++ b/tests/Data/Fight/GeneratorTest.elm @@ -1,37 +1,35 @@ -module Data.Fight.GeneratorTest exposing (damageNeverNegative) +module Data.Fight.GeneratorTest exposing (suite) -import Data.Fight as Fight -import Data.Fight.Generator +import Data.Fight as Fight exposing (Action, Opponent, Who) +import Data.Fight.AttackStyle as AttackStyle +import Data.Fight.Generator exposing (Fight) +import Data.FightStrategy exposing (..) +import Data.Item as Item +import Data.Special as Special +import Dict import Expect -import Fuzz +import Fuzz exposing (Fuzzer) +import Logic import Random +import SeqDict +import SeqSet import Test exposing (Test) import TestHelpers exposing (..) +import Time + + +suite : Test +suite = + Test.describe "Data.Fight.Generator" + [ damageNeverNegative + , meleeAttackAtDistance2WithRange2 + ] damageNeverNegative : Test damageNeverNegative = - Test.fuzz3 - opponentFuzzer - opponentFuzzer - (Fuzz.map2 Tuple.pair - posixFuzzer - randomSeedFuzzer - ) - "Damage in fight should never be negative" - <| - \attacker target ( currentTime, seed ) -> - let - ( fight, _ ) = - Random.step - (Data.Fight.Generator.generator - { attacker = attacker - , target = target - , currentTime = currentTime - } - ) - seed - in + Test.fuzz randomFightFuzzer "Damage in fight should never be negative" <| + \fight -> fight.fightInfo.log |> List.map (Tuple.second >> Fight.attackDamage) |> List.filter (\damage -> damage < 0) @@ -40,5 +38,83 @@ damageNeverNegative = |> Expect.onFail "Expected the list of negative damage actions in a fight to be empty" +meleeAttackAtDistance2WithRange2 : Test +meleeAttackAtDistance2WithRange2 = + let + strategy : FightStrategy + strategy = + If + { condition = Operator { lhs = Distance, op = GT_, rhs = Number 2 } + , then_ = Command MoveForward + , else_ = Command AttackRandomly + } + in + Test.test "Melee combat at distance 2 with weapon with range 2 should work" <| + \() -> + let + opponent = + { type_ = Fight.Player { name = "Opponent", xp = 0 }, hp = 100, maxHp = 100, maxAp = 10, sequence = 5, traits = SeqSet.empty, perks = SeqDict.empty, caps = 50, items = Dict.empty, drops = [], equippedArmor = Nothing, equippedWeapon = Just Item.SuperSledge, equippedAmmo = Nothing, naturalArmorClass = 5, attackStats = Logic.unarmedAttackStats { special = Special.init, unarmedSkill = 50, traits = SeqSet.empty, perks = SeqDict.empty, level = 1, npcExtraBonus = 0 }, addedSkillPercentages = SeqDict.empty, special = Special.init, fightStrategy = strategy } + + result = + Random.step + (Data.Fight.Generator.attack_ + Fight.Attacker + { distanceHexes = 2 + , attacker = opponent + , attackerAp = 10 + , attackerItemsUsed = SeqDict.empty + , target = opponent + , targetAp = 10 + , targetItemsUsed = SeqDict.empty + , reverseLog = [] + , actionsTaken = 0 + } + AttackStyle.MeleeUnaimed + 4 + ) + (Random.initialSeed 3) + |> Tuple.first + in + result.ranCommandSuccessfully + |> Expect.equal True + |> Expect.onFail "Expected the attack to succeed" + + +fightFuzzer : + { attacker : Fuzzer Opponent + , target : Fuzzer Opponent + } + -> Fuzzer Fight +fightFuzzer r = + Fuzz.constant + (\a t currentTime seed -> + Random.step + (Data.Fight.Generator.generator + { attacker = a + , target = t + , currentTime = currentTime + } + ) + seed + ) + |> Fuzz.andMap r.attacker + |> Fuzz.andMap r.target + |> Fuzz.andMap posixFuzzer + |> Fuzz.andMap randomSeedFuzzer + |> Fuzz.map Tuple.first + + +randomFightFuzzer : Fuzzer Fight +randomFightFuzzer = + fightFuzzer + { attacker = opponentFuzzer + , target = opponentFuzzer + } + + -- TODO items in inventory decrease after finished fight where they were used +-- TODO thrown items decrease after throwing +-- TODO ammo decreases after using +-- TODO fallback ammo used +-- TODO unarmed combat after all ammo used