Skip to content

Commit

Permalink
Fix a bug with combat at distances >0, >1
Browse files Browse the repository at this point in the history
  • Loading branch information
Janiczek committed Oct 6, 2024
1 parent 986c396 commit 470bced
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 38 deletions.
13 changes: 8 additions & 5 deletions src/Data/Fight/Generator.elm
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module Data.Fight.Generator exposing
( Fight
, attack_
, enemyOpponentGenerator
, generator
, playerOpponent
Expand Down Expand Up @@ -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 }

Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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
Expand Down
21 changes: 13 additions & 8 deletions src/Logic.elm
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ module Logic exposing
, unarmedAttackStats
, unarmedBaseCriticalChance
, unarmedRange
, weaponRange
, xpGained
)

Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down
126 changes: 101 additions & 25 deletions tests/Data/Fight/GeneratorTest.elm
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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

0 comments on commit 470bced

Please sign in to comment.