///- --package .
combat (test)
test combat: prelude
///- --test static --symbol
:< testing
:< keyed_rng
:| combat_roll =
| ArmorProbe
| BoutSwing
| StatsDraw
| ArrowShot
| ArrowFire
:+ purpose_code combat_roll =
\ r =>
?= r ?|
| ArmorProbe => 4u32
| BoutSwing => 7u32
| StatsDraw => 9u32
| ArrowShot => 11u32
| ArrowFire => 12u32
:== roll p = keyed_rng::roll_key p
:= combat_key (s : u32) (t : u32) (e : u32) (r : combat_roll) : roll combat_roll =
keyed_rng::roll_key (keyed_rng::seed s) (keyed_rng::tick t) (keyed_rng::entity e) r
:= unit_scale () : i32 = 4096
:= uniform_unit \* p {purpose_code}. (key : roll p) (index : u32) : i32 =
conv (keyed_rng::draw (keyed_rng::sub_key key index) % 4096u32)
:= irwin_hall_twelve \* p {purpose_code}. (key : roll p) (attempt : u32) : i32 =
:~ ^ sum (i : u32) (total : i32) : i32 =
? i >= 12u32 ?> total |: sum (i + 1u32) (total + uniform_unit key (attempt * 12u32 + i))
sum 0u32 0i32
:= normal_attempt \* p {purpose_code}. (key : roll p) (n : u32) : i32 =
2048i32 + (irwin_hall_twelve key n - 24576i32) / 4i32
:= clamped_to_unit (v : i32) : i32 = ? v < 0i32 ?> 0i32 |: unit_scale ()
:= in_unit (v : i32) : bool = v >= 0i32 && v <= unit_scale ()
:= truncated_normal_unit \* p {purpose_code}. (key : roll p) : i32 =
:~ ^ attempt (n : u32) : i32 =
:= v = normal_attempt key n
? in_unit v ?> v
|? n >= 7u32 ?> clamped_to_unit v
|: attempt (n + 1u32)
attempt 0u32
:== draw_stats = { total : i32; low : i32; high : i32 }
:= with_draw (v : i32) (d : draw_stats) : draw_stats =
{ total = d::total + v; low = (? v < d::low ?> v |: d::low); high = (? v > d::high ?> v |: d::high) }
:= stats_draw (seed : u32) (i : i32) : i32 = truncated_normal_unit (combat_key seed (conv i) 0u32 StatsDraw)
:= no_draws () : draw_stats = { total = 0i32; low = unit_scale (); high = 0i32 }
:~ ^ draws_from (seed : u32) (count : i32) (i : i32) (d : draw_stats) : draw_stats =
? i >= count ?> d |: draws_from seed count (i + 1i32) (with_draw (stats_draw seed i) d)
:= unit_draw_stats (seed : u32) (count : i32) : i32 * i32 * i32 =
:= d = draws_from seed count 0i32 (no_draws ())
d::total / count, d::low, d::high
:= melee_roll \* p {purpose_code}. (low : i32) (high : i32) (key : roll p) : i32 =
low + (truncated_normal_unit key * (high - low) + 2048i32) / unit_scale ()
:= uniform_between \* p {purpose_code}. (low : i32) (high : i32) (key : roll p) (index : u32) : i32 =
conv (keyed_rng::roll_between (conv low) (conv high) (keyed_rng::sub_key key index))
:= defense_block \* p {purpose_code}. (defense : i32) (key : roll p) : i32 =
uniform_between (defense / 2i32) defense key 200u32
:= armor_block \* p {purpose_code}. (armor : i32) (key : roll p) : i32 =
? armor > 3i32 ?> uniform_between (armor / 2i32) (armor - (armor % 2i32 + 1i32)) key 201u32
|? armor > 0i32 ?> 1i32
|: 0i32
:= after_armor \* p {purpose_code}. (hit : i32) (armor : i32) (key : roll p) : i32 =
:= left = hit - armor_block armor key
? left <= 0i32 ?> 0i32 |: left
:= blocked_hit \* p {purpose_code}. (raw : i32) (defense : i32) (armor : i32) (charged : bool) (key : roll p) : i32 =
:= left = ? charged ?> raw - defense_block defense key |: raw
? left <= 0i32 ?> 0i32 |: after_armor left armor key
:== gear = { armor : i32; defense : i32 }
:= balanced_defense_percent () : i32 = 75i32
:= shield_scaling_per_mille () : i32 = 160i32
:= shielded_defense (shield_skill : i32) (shield_defense : i32) : i32 =
(shield_skill * 25i32 + 223i32)
* shield_defense
* balanced_defense_percent ()
* shield_scaling_per_mille ()
/ 10000000i32
:= knight_sample_armor () : i32 =
:= brass_helmet = 3i32
:= brass_armor = 8i32
:= brass_legs = 5i32
:= leather_boots = 1i32
brass_helmet + brass_armor + brass_legs + leather_boots
:= knight_sample_gear () : gear =
:= dwarven_shield = 26i32
{ armor = knight_sample_armor (); defense = shielded_defense 11i32 dwarven_shield }
:= max_block_charges () : i32 = 2i32
:= block_charge_ms () : i32 = 1000i32
:= creature_hit \* p {purpose_code}. (melee_max : i32) (defender : gear) (shielded : bool) (key : roll p) : i32 =
blocked_hit (melee_roll 0i32 melee_max key) defender::defense defender::armor shielded key
:== ranged_attack = { chance_percent : i32; max_damage : i32; range : i32; every_ms : i32 }
:= hunter_arrow () : ranged_attack = { chance_percent = 50; max_damage = 100; range = 7; every_ms = 2000 }
:= ranged_in_reach (attack : ranged_attack) (distance : i32) (sight_clear : bool) : bool =
distance <= attack::range && sight_clear
:= ranged_fires \* p {purpose_code}. (attack : ranged_attack) (key : roll p) : bool =
uniform_between 1i32 100i32 key 202u32 <= attack::chance_percent
:= charge_left (charges : i32) : i32 = ? charges > 0i32 ?> charges - 1i32 |: 0i32
:= ranged_hit_spending_a_charge \* p {purpose_code}.
(attack : ranged_attack) (defender : gear) (charges : i32) (key : roll p)
: i32 * i32
=
blocked_hit (melee_roll 0i32 attack::max_damage key) defender::defense defender::armor !- key, charge_left charges
:== swinger = { mode : damage::fight_mode; level : i32; attack_value : i32; attack_skill : i32 }
:= swing_floor (s : swinger) : i32 = ? s::attack_value > 0i32 ?> s::level / 5i32 |: 0i32
:= swing_ceiling (s : swinger) : i32 = damage::max_melee_damage s::mode s::attack_value s::attack_skill s::level
:= melee_hit \* p {purpose_code}. (s : swinger) (target : gear) (key : roll p) : i32 =
blocked_hit (melee_roll (swing_floor s) (swing_ceiling s) key) target::defense target::armor !+ key
:== bout = { s : swinger; target : gear; seed : u32; swings : i32 }
:= bout_of (s : swinger) (target : gear) (seed : u32) (swings : i32) : bout =
{ s = s; target = target; seed = seed; swings = swings }
:= bout_hit (b : bout) (i : i32) : i32 = melee_hit b::s b::target (combat_key b::seed (conv i) 1u32 BoutSwing)
:= landed_after (hit : i32) (landed : i32) : i32 = ? hit > 0i32 ?> landed + 1i32 |: landed
:== swing_tally = { landed : i32; total : i32 }
:= tallied_swing (hit : i32) (t : swing_tally) : swing_tally =
{ landed = landed_after hit t::landed; total = t::total + hit }
:~ ^ swings_from (b : bout) (i : i32) (t : swing_tally) : swing_tally =
? i >= b::swings ?> t |: swings_from b (i + 1i32) (tallied_swing (bout_hit b i) t)
:= swing_stats (s : swinger) (target : gear) (seed : u32) (swings : i32) : i32 * i32 =
:= t = swings_from (bout_of s target seed swings) 0i32 { landed = 0i32; total = 0i32 }
t::landed, t::total
:== capped_tally = { health : i32; landed : i32; total : i32 }
:= taken_from (hit : i32) (health : i32) : i32 = ? hit > health ?> health |: hit
:= health_after (max_health : i32) (left : i32) : i32 = ? left <= 0i32 ?> max_health |: left
:= capped_swing (max_health : i32) (hit : i32) (t : capped_tally) : capped_tally =
:= taken = taken_from hit t::health
:= health = health_after max_health (t::health - taken)
{ health = health; landed = landed_after taken t::landed; total = t::total + taken }
:~ ^ capped_from (b : bout) (max_health : i32) (i : i32) (t : capped_tally) : capped_tally =
? i >= b::swings ?> t |: capped_from b max_health (i + 1i32) (capped_swing max_health (bout_hit b i) t)
:= fresh_capped (max_health : i32) : capped_tally = { health = max_health; landed = 0i32; total = 0i32 }
:= capped_hit_stats (s : swinger) (target : gear) (max_health : i32) (seed : u32) (swings : i32) : i32 * i32 =
:= t = capped_from (bout_of s target seed swings) max_health 0i32 (fresh_capped max_health)
t::landed, t::total
:= level_8_axe (mode : damage::fight_mode) (skill : i32) : swinger =
{ mode = mode; level = 8i32; attack_value = 21i32; attack_skill = skill }
:= armor_and_defense_10 () : gear = { armor = 10i32; defense = 10i32 }
:= troll_swings (mode : damage::fight_mode) (skill : i32) (seed : u32) : i32 * i32 =
swing_stats (level_8_axe mode skill) (armor_and_defense_10 ()) seed 2000i32
:= capped_troll_swings (mode : damage::fight_mode) (skill : i32) (health : i32) (seed : u32) : i32 * i32 =
capped_hit_stats (level_8_axe mode skill) (armor_and_defense_10 ()) health seed 2000i32
:= arrow_key (i : i32) : roll combat_roll = combat_key 9u32 (conv i) 2u32 ArrowShot
:= one_if (b : bool) : i32 = ? b ?> 1i32 |: 0i32
:= arrow_outcome (g : gear) (i : i32) : i32 * i32 * i32 =
:= key = arrow_key i
:= hurt, left = ranged_hit_spending_a_charge (hunter_arrow ()) g 2i32 key
one_if (hurt <> creature_hit (hunter_arrow ())::max_damage g !- key), one_if (left = 1i32), hurt
:= summed (a : i32 * i32 * i32) (b : i32 * i32 * i32) : i32 * i32 * i32 =
:= a1, a2, a3 = a
:= b1, b2, b3 = b
a1 + b1, a2 + b2, a3 + b3
:~ ^ arrow_totals (g : gear) (shots : i32) (i : i32) (acc : i32 * i32 * i32) : i32 * i32 * i32 =
? i >= shots ?> acc |: arrow_totals g shots (i + 1i32) (summed acc (arrow_outcome g i))
:= arrow_with_no_charge (g : gear) : i32 =
:= _, left = ranged_hit_spending_a_charge (hunter_arrow ()) g 0i32 (arrow_key 0i32)
left
:= arrow_facts (shots : i32) : i32 * i32 * i32 * bool =
:= g = knight_sample_gear ()
:= differ, spent, total = arrow_totals g shots 0i32 (0i32, 0i32, 0i32)
differ, spent, arrow_with_no_charge g, total > shots * 30i32
:~ ^ fired (arrow : ranged_attack) (i : i32) (count : i32) : i32 =
? i >= 10000i32 ?> count
|: fired arrow (i + 1i32) (? ranged_fires arrow (combat_key 3u32 (conv i) 4u32 ArrowFire) ?> count + 1i32 |: count)
:= reach_of (arrow : ranged_attack) (range : i32) : bool * bool * bool =
ranged_in_reach arrow range !+, ranged_in_reach arrow (range + 1i32) !+, ranged_in_reach arrow 3i32 !-
:= armor_blocks \* p {purpose_code}. (armor : i32) (key : roll p) : bool * i32 * i32 =
:= block = armor_block armor key
(block >= 5i32 && block <= 9i32), armor_block (armor - 7i32) key, armor_block (armor - 10i32) key
:= fires_about_half (arrow : ranged_attack) : bool =
:= shots = fired arrow 0i32 0i32
shots > 4800i32 && shots < 5200i32
hunter_arrows_skip_the_shield_but_spend_a_block_charge_like_canary_distance_spells (test)
test hunter_arrows_skip_the_shield_but_spend_a_block_charge_like_canary_distance_spells: passed
///- --test --symbol
///> c
:= ~shots = 4000i32
arrow_facts shots |> _assert_eq (0i32, shots, 0i32, !+)
__assert_eq { expected = (0, 4000, 0, true) }
hunter_arrows_need_range_7_and_clear_sight_and_fire_half_the_time (test)
test hunter_arrows_need_range_7_and_clear_sight_and_fire_half_the_time: passed
///- --test --symbol
///> c
:= ~range = 7i32
:= near, beyond, close = reach_of (hunter_arrow ()) range
(near, beyond, close, fires_about_half (hunter_arrow ())) |> _assert_eq (!+, !-, !-, !+)
__assert_eq { expected = (true, false, false, true) }
armor_block_follows_canary_bounds (test)
test armor_block_follows_canary_bounds: passed
///- --test --symbol
///> c
:= ~armor = 10i32
armor_blocks armor (combat_key 1u32 2u32 3u32 ArmorProbe) |> _assert_eq (!+, 1i32, 0i32)
__assert_eq { expected = (true, 1, 0) }
truncated_normal_stays_in_unit_range_with_mean_near_half (test)
test truncated_normal_stays_in_unit_range_with_mean_near_half: passed
///- --test --symbol
///> c
:= ~seed = 11u32
unit_draw_stats seed 4000i32 |> _assert_eq (2055i32, 0i32, 4093i32)
__assert_eq { expected = (2055, 0, 4093) }
level_8_knight_rarely_hurts_a_troll (test)
test level_8_knight_rarely_hurts_a_troll: passed
///- --test --symbol
///> c
:= ~seed = 5u32
troll_swings (:< damage ;; Offensive) 11i32 seed |> _assert_eq (435i32, 1393i32)
__assert_eq { expected = (435, 1393) }
axe_skill_30_hurts_a_troll_often (test)
test axe_skill_30_hurts_a_troll_often: passed
///- --test --symbol
///> c
:= ~seed = 5u32
troll_swings (:< damage ;; Offensive) 30i32 seed |> _assert_eq (1680i32, 28222i32)
__assert_eq { expected = (1680, 28222) }
balanced_axe_skill_30_matches_live_troll_hits (test)
test balanced_axe_skill_30_matches_live_troll_hits: passed
///- --test --symbol
///> c
:= ~seed = 5u32
troll_swings (:< damage ;; Balanced) 30i32 seed |> _assert_eq (1435i32, 15398i32)
__assert_eq { expected = (1435, 15398) }
balanced_swing_stats_on_gpu (test)
test balanced_swing_stats_on_gpu: passed
///- --test --symbol
///> wgsl
:= ~seed = 5u32
?= troll_swings (:< damage ;; Balanced) 30i32 seed ?|
| landed, total => ? landed = 1435i32 && total = 15398i32 ?> 0i32 |: 1i32
hits_capped_at_remaining_health_match_live_skeleton_log (test)
test hits_capped_at_remaining_health_match_live_skeleton_log: passed
///- --test --symbol
///> c
:= ~seed = 5u32
capped_troll_swings (:< damage ;; Balanced) 30i32 50i32 seed |> _assert_eq (1435i32, 13451i32)
__assert_eq { expected = (1435, 13451) }