summaryrefslogtreecommitdiff |
diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/battlemap/attack.erl | 279 | ||||
-rw-r--r-- | src/query/character_turn.erl | 146 | ||||
-rw-r--r-- | src/query/character_turn/handle_character_instance_attacking_2.erl | 383 | ||||
-rw-r--r-- | src/struct/weapon.erl | 4 |
4 files changed, 428 insertions, 384 deletions
diff --git a/src/battlemap/attack.erl b/src/battlemap/attack.erl new file mode 100644 index 0000000..7384f78 --- /dev/null +++ b/src/battlemap/attack.erl @@ -0,0 +1,279 @@ +-module(attack). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% TODO: find better names for those types. +-type hits() :: ('misses' | 'grazes' | 'hits'). +-type critical() :: ('critical' | 'basic'). +-type attack_order() :: 'first' | 'second' | 'counter'. +-type attack_order_with_parry() :: {attack_order(), boolean()}. +-type attack_category() :: + ( + attack_order() + | {attack_order(), 'parry'} + ). +-type attack_effect() :: {hits(), critical(), non_neg_integer()}. +-type attack_desc() :: + ( + {attack_category(), attack_effect()} + | 'nothing' + ). + +-export_type +( + [ + hits/0, + critical/0, + attack_category/0, + attack_effect/0, + attack_desc/0 + ] +). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export +( + [ + get_sequence/3, + get_description_of/3, + apply_to_healths/3 + ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec roll_hits + ( + statistics:struct(), + statistics:struct() + ) + -> hits(). +roll_hits (AttackerStatistics, DefenderStatistics) -> + DefenderDodges = statistics:get_dodges(DefenderStatistics), + AttackerAccuracy = statistics:get_accuracy(AttackerStatistics), + MissChance = max(0, (DefenderDodges - AttackerAccuracy)), + case roll:percentage() of + X when (X =< MissChance) -> misses; + X when (X =< (MissChance * 2)) -> grazes; + _ -> hits + end. + +-spec roll_damage + ( + statistics:struct(), + statistics:struct() + ) + -> {critical(), non_neg_integer()}. +roll_damage (AttackerStatistics, _DefenderStatistics) -> + {MinimumDamage, MaximumDamage} = statistics:get_damages(AttackerStatistics), + MaximumRoll = max(1, MaximumDamage - MinimumDamage), + BaseDamage = MinimumDamage + (rand:uniform(MaximumRoll) - 1), + CriticalHitChance = statistics:get_critical_hits(AttackerStatistics), + case roll:percentage() of + X when (X =< CriticalHitChance) -> {critical, (BaseDamage * 2)}; + _ -> {basic, BaseDamage} + end. + +-spec effect_of_attack + ( + statistics:struct(), + statistics:struct() + ) + -> attack_effect(). +effect_of_attack (AttackerStatistics, DefenderStatistics) -> + Hits = roll_hits(AttackerStatistics, DefenderStatistics), + {Critical, Damage} = roll_damage(AttackerStatistics, DefenderStatistics), + case Hits of + misses -> {Hits, Critical, 0}; + grazes -> {Hits, Critical, trunc(Damage / 2)}; + hits -> {Hits, Critical, Damage} + end. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-spec get_description_of + ( + attack_order_with_parry(), + statistics:struct(), + statistics:struct() + ) + -> attack_desc(). +get_description_of +( + {first, DefenderCanParry}, + AttackerStatistics, + DefenderStatistics +) -> + DefenderParryChance = statistics:get_parries(DefenderStatistics), + ParryRoll = + case DefenderCanParry of + true -> roll:percentage(); + _ -> 101 + end, + if + (ParryRoll =< DefenderParryChance) -> + { + {first, parry}, + effect_of_attack(DefenderStatistics, AttackerStatistics) + }; + + true -> + {first, effect_of_attack(AttackerStatistics, DefenderStatistics)} + end; +get_description_of +( + {second, DefenderCanParry}, + AttackerStatistics, + DefenderStatistics +) -> + DefenderParryChance = statistics:get_parries(DefenderStatistics), + ParryRoll = + case DefenderCanParry of + true -> roll:percentage(); + _ -> 101 + end, + AttackerDoubleAttackChange = statistics:get_double_hits(AttackerStatistics), + DoubleAttackRoll = roll:percentage(), + if + (DoubleAttackRoll > AttackerDoubleAttackChange) -> + nothing; + + (ParryRoll =< DefenderParryChance) -> + { + {second, parry}, + effect_of_attack(DefenderStatistics, AttackerStatistics) + }; + + true -> + {second, effect_of_attack(AttackerStatistics, DefenderStatistics)} + end; +get_description_of +( + {counter, AttackerCanParry}, + AttackerStatistics, + DefenderStatistics +) -> + AttackerParryChance = statistics:get_parries(AttackerStatistics), + ParryRoll = + case AttackerCanParry of + true -> roll:percentage(); + _ -> 101 + end, + if + (ParryRoll =< AttackerParryChance) -> + { + {counter, parry}, + effect_of_attack(AttackerStatistics, DefenderStatistics) + }; + + true -> + {counter, effect_of_attack(DefenderStatistics, AttackerStatistics)} + end. + +-spec apply_to_healths + ( + attack_desc(), + non_neg_integer(), + non_neg_integer() + ) + -> {attack_desc(), non_neg_integer(), non_neg_integer()}. +apply_to_healths +( + nothing, + AttackerHealth, + DefenderHealth +) -> + {nothing, AttackerHealth, DefenderHealth}; +apply_to_healths +( + {Attack, Effect}, + AttackerHealth, + DefenderHealth +) +when +( + (Attack == first) + or (Attack == second) + or (Attack == {counter, parry}) +) -> + {_Hits, _Critical, Damage} = Effect, + case AttackerHealth of + 0 -> + {nothing, AttackerHealth, DefenderHealth}; + + _ -> + { + {Attack, Effect}, + AttackerHealth, + max(0, (DefenderHealth - Damage)) + } + end; +apply_to_healths +( + {Attack, Effect}, + AttackerHealth, + DefenderHealth +) +when +( + (Attack == {first, parry}) + or (Attack == {second, parry}) + or (Attack == counter) +) -> + {_Hits, _Critical, Damage} = Effect, + case DefenderHealth of + 0 -> + {nothing, AttackerHealth, DefenderHealth}; + + _ -> + { + {Attack, Effect}, + max(0, (AttackerHealth - Damage)), + DefenderHealth + } + end. + +-spec get_sequence + ( + non_neg_integer(), + weapon:struct(), + weapon:struct() + ) + -> list(attack_order_with_parry()). +get_sequence (AttackRange, AttackerWeapon, DefenderWeapon) -> + {AttackerDefenseRange, AttackerAttackRange} = + weapon:get_ranges(AttackerWeapon), + {DefenderDefenseRange, DefenderAttackRange} = + weapon:get_ranges(DefenderWeapon), + + AttackerCanAttack = (AttackRange =< AttackerAttackRange), + AttackerCanDefend = + (AttackerCanAttack and (AttackRange > AttackerDefenseRange)), + AttackerCanParry = + (AttackerCanDefend and weapon:can_parry(AttackerWeapon)), + + DefenderCanAttack = (AttackRange =< DefenderAttackRange), + DefenderCanDefend = + (DefenderCanAttack and (AttackRange > DefenderDefenseRange)), + DefenderCanParry = + (DefenderCanDefend and weapon:can_parry(DefenderWeapon)), + + First = {first, DefenderCanParry}, + Second = {second, DefenderCanParry}, + Counter = {counter, AttackerCanParry}, + + if + (not AttackerCanAttack) -> + []; + + (not DefenderCanDefend) -> + [First, Second]; + + true -> + [First, Counter, Second] + end. diff --git a/src/query/character_turn.erl b/src/query/character_turn.erl index c920745..b24e038 100644 --- a/src/query/character_turn.erl +++ b/src/query/character_turn.erl @@ -233,7 +233,151 @@ handle_character_instance_switching_weapons (QueryState, Input) -> UpdatedQueryState }. --include("character_turn/handle_character_instance_attacking_2.erl"). +-spec set_new_healths_in_query_state + ( + non_neg_integer(), + non_neg_integer(), + query_state(), + input() + ) + -> query_state(). +set_new_healths_in_query_state +( + RemainingAttackerHealth, + RemainingDefenderHealth, + QueryState, + Input +) -> + BattlemapInstance = QueryState#query_state.battlemap_instance, + ControlledCharacterInstance = QueryState#query_state.character_instance, + CharacterInstances = + battlemap_instance:get_character_instances(BattlemapInstance), + TargettedCharacterInstanceIX = Input#input.target_ix, + TargettedCharacterInstance = + array:get + ( + TargettedCharacterInstanceIX, + CharacterInstances + ), + + QueryState#query_state + { + battlemap_instance = + battlemap_instance:set_character_instances + ( + array:set + ( + TargettedCharacterInstanceIX, + character_instance:set_current_health + ( + RemainingDefenderHealth, + TargettedCharacterInstance + ), + CharacterInstances + ), + BattlemapInstance + ), + character_instance = + character_instance:set_current_health + ( + RemainingAttackerHealth, + ControlledCharacterInstance + ) + }. + +-spec handle_character_instance_attacking + ( + query_state(), + input() + ) + -> {list(attack:attack_desc()), query_state()}. +handle_character_instance_attacking (QueryState, Input) -> + BattlemapInstance = QueryState#query_state.battlemap_instance, + ControlledCharacterInstance = QueryState#query_state.character_instance, + ControlledCharacter = + character_instance:get_character(ControlledCharacterInstance), + ControlledCharacterStatistics = + character:get_statistics(ControlledCharacter), + TargettedCharacterInstance = + array:get + ( + Input#input.target_ix, + battlemap_instance:get_character_instances(BattlemapInstance) + ), + TargettedCharacter = + character_instance:get_character(TargettedCharacterInstance), + TargettedCharacterStatistics = character:get_statistics(TargettedCharacter), + RequiredRange = + movement:steps_between + ( + character_instance:get_location(ControlledCharacterInstance), + character_instance:get_location(TargettedCharacterInstance) + ), + {AttackingWeaponID, _} = character:get_weapon_ids(ControlledCharacter), + AttackingWeapon = weapon:from_id(AttackingWeaponID), + {DefendingWeaponID, _} = character:get_weapon_ids(TargettedCharacter), + DefendingWeapon = weapon:from_id(DefendingWeaponID), + BaseAttackerHealth = + character_instance:get_current_health(ControlledCharacterInstance), + BaseDefenderHealth = + character_instance:get_current_health(TargettedCharacterInstance), + + AttackSequence = + attack:get_sequence(RequiredRange, AttackingWeapon, DefendingWeapon), + + AttackEffects = + lists:map + ( + fun (AttackOrder) -> + attack:get_description_of + ( + AttackOrder, + ControlledCharacterStatistics, + TargettedCharacterStatistics + ) + end, + AttackSequence + ), + + {AttackSummary, RemainingAttackerHealth, RemainingDefenderHealth} = + lists:foldl + ( + fun + ( + AttackEffect, + { + CurrentAttackEffects, + CurrentAttackerHealth, + CurrentDefenderHealth + } + ) -> + {AttackTrueEffect, NewAttackerHealth, NewDefenderHealth} = + attack:apply_to_healths + ( + AttackEffect, + CurrentAttackerHealth, + CurrentDefenderHealth + ), + { + [AttackTrueEffect|CurrentAttackEffects], + NewAttackerHealth, + NewDefenderHealth + } + end, + {[], BaseAttackerHealth, BaseDefenderHealth}, + AttackEffects + ), + + { + AttackSummary, + set_new_healths_in_query_state + ( + RemainingAttackerHealth, + RemainingDefenderHealth, + QueryState, + Input + ) + }. -spec get_type_of_turn (input()) -> list(atom()). get_type_of_turn (Input) -> diff --git a/src/query/character_turn/handle_character_instance_attacking_2.erl b/src/query/character_turn/handle_character_instance_attacking_2.erl deleted file mode 100644 index 6995c4c..0000000 --- a/src/query/character_turn/handle_character_instance_attacking_2.erl +++ /dev/null @@ -1,383 +0,0 @@ -% TODO: put all of that into separate modules. It's kind of a mess here. --type hits() :: ('misses' | 'grazes' | 'hits'). --type critical() :: ('critical' | 'basic'). --type attack_category() :: - ( - 'first' - | 'second' - | 'counter' - | {'first', 'parry'} - | {'second', 'parry'} - ). --type attack_effect() :: {hits(), critical(), non_neg_integer()}. --type attack_desc() :: {attack_category(), attack_effect()}. - --spec roll_hits - ( - statistics:struct(), - statistics:struct() - ) - -> hits(). -roll_hits (AttackerStatistics, DefenderStatistics) -> - DefenderDodges = statistics:get_dodges(DefenderStatistics), - AttackerAccuracy = statistics:get_accuracy(AttackerStatistics), - MissChance = max(0, (DefenderDodges - AttackerAccuracy)), - case roll:percentage() of - X when (X =< MissChance) -> misses; - X when (X =< (MissChance * 2)) -> grazes; - _ -> hits - end. - --spec roll_damage - ( - statistics:struct(), - statistics:struct() - ) - -> {critical(), non_neg_integer()}. -roll_damage (AttackerStatistics, _DefenderStatistics) -> - {MinimumDamage, MaximumDamage} = statistics:get_damages(AttackerStatistics), - MaximumRoll = max(1, MaximumDamage - MinimumDamage), - BaseDamage = MinimumDamage + (rand:uniform(MaximumRoll) - 1), - CriticalHitChance = statistics:get_critical_hits(AttackerStatistics), - case roll:percentage() of - X when (X =< CriticalHitChance) -> {critical, (BaseDamage * 2)}; - _ -> {basic, BaseDamage} - end. - --spec handle_attack - ( - statistics:struct(), - statistics:struct() - ) - -> {hits(), critical(), non_neg_integer()}. -handle_attack (AttackerStatistics, DefenderStatistics) -> - Hits = roll_hits(AttackerStatistics, DefenderStatistics), - {Critical, Damage} = roll_damage(AttackerStatistics, DefenderStatistics), - case Hits of - misses -> {Hits, Critical, 0}; - grazes -> {Hits, Critical, trunc(Damage / 2)}; - hits -> {Hits, Critical, Damage} - end. - --spec handle_attacks - ( - list(attack_category()), - statistics:struct(), - statistics:struct(), - list(attack_desc()) - ) - -> list(attack_desc()). -handle_attacks ([], _AttackerStatistics, _DefenderStatistics, Results) -> - Results; -handle_attacks -( - [first|Next], - AttackerStatistics, - DefenderStatistics, - Results -) -> - AttackResult = handle_attack(AttackerStatistics, DefenderStatistics), - handle_attacks - ( - Next, - AttackerStatistics, - DefenderStatistics, - [{first, AttackResult} | Results] - ); -handle_attacks -( - [second|Next], - AttackerStatistics, - DefenderStatistics, - Results -) -> - SecondHitChance = statistics:get_double_hits(AttackerStatistics), - UpdatedResults = - case roll:percentage() of - X when (X =< SecondHitChance) -> - [ - {second, handle_attack(AttackerStatistics, DefenderStatistics)} - | - Results - ]; - - _ -> - Results - end, - handle_attacks(Next, AttackerStatistics, DefenderStatistics, UpdatedResults); -handle_attacks -( - [{first, parry}|Next], - AttackerStatistics, - DefenderStatistics, - Results -) -> - ParryChance = statistics:get_parries(DefenderStatistics), - AttackResult = - case roll:percentage() of - X when (X =< ParryChance) -> - { - {first, parry}, - handle_attack(DefenderStatistics, AttackerStatistics) - }; - - _ -> - {first, handle_attack(AttackerStatistics, DefenderStatistics)} - end, - handle_attacks - ( - Next, - AttackerStatistics, - DefenderStatistics, - [AttackResult|Results] - ); -handle_attacks -( - [{second, parry}|Next], - AttackerStatistics, - DefenderStatistics, - Results -) -> - SecondHitChance = statistics:get_double_hits(AttackerStatistics), - ParryChance = statistics:get_parries(DefenderStatistics), - AttackResult = - case roll:percentage() of - X when (X =< SecondHitChance) -> - case roll:percentage() of - Y when (Y =< ParryChance) -> - { - {second, parry}, - handle_attack(DefenderStatistics, AttackerStatistics) - }; - - _ -> - { - second, - handle_attack(AttackerStatistics, DefenderStatistics) - } - end; - - _ -> nothing - end, - handle_attacks - ( - Next, - AttackerStatistics, - DefenderStatistics, - case AttackResult of - nothing -> Results; - _ -> [AttackResult|Results] - end - ); -handle_attacks -( - [counter|Next], - AttackerStatistics, - DefenderStatistics, - Results -) -> - handle_attacks - ( - Next, - AttackerStatistics, - DefenderStatistics, - [ - {counter, handle_attack(DefenderStatistics, AttackerStatistics)} - | - Results - ] - ). - --spec apply_attacks_to_healths - ( - list(attack_desc()), - non_neg_integer(), - non_neg_integer(), - list(attack_desc()) - ) - -> {list(attack_desc()), non_neg_integer(), non_neg_integer()}. -apply_attacks_to_healths ([], AttackerHealth, DefenderHealth, ValidEffects) -> - {ValidEffects, AttackerHealth, DefenderHealth}; -apply_attacks_to_healths -( - [{Action, Effect}|Next], - AttackerHealth, - DefenderHealth, - ValidEffects -) -when ((Action == first) or (Action == second)) -> - {_Hit, _Critical, Damage} = Effect, - case (AttackerHealth > 0) of - true -> - apply_attacks_to_healths - ( - Next, - AttackerHealth, - max(0, (DefenderHealth - Damage)), - [{Action, Effect}|ValidEffects] - ); - - false -> - {ValidEffects, AttackerHealth, DefenderHealth} - end; -apply_attacks_to_healths -( - [{Action, Effect}|Next], - AttackerHealth, - DefenderHealth, - ValidEffects -) -when -( - (Action == counter) - or (Action == {first, parry}) - or (Action == {second, parry}) -) -> - {_Hit, _Critical, Damage} = Effect, - case (DefenderHealth > 0) of - true -> - apply_attacks_to_healths - ( - Next, - max(0, (AttackerHealth - Damage)), - DefenderHealth, - [{Action, Effect}|ValidEffects] - ); - - false -> - {ValidEffects, AttackerHealth, DefenderHealth} - end. - --spec set_new_healths_in_query_state - ( - non_neg_integer(), - non_neg_integer(), - query_state(), - input() - ) - -> query_state(). -set_new_healths_in_query_state -( - RemainingAttackerHealth, - RemainingDefenderHealth, - QueryState, - Input -) -> - BattlemapInstance = QueryState#query_state.battlemap_instance, - ControlledCharacterInstance = QueryState#query_state.character_instance, - CharacterInstances = - battlemap_instance:get_character_instances(BattlemapInstance), - TargettedCharacterInstanceIX = Input#input.target_ix, - TargettedCharacterInstance = - array:get - ( - TargettedCharacterInstanceIX, - CharacterInstances - ), - - QueryState#query_state - { - battlemap_instance = - battlemap_instance:set_character_instances - ( - array:set - ( - TargettedCharacterInstanceIX, - character_instance:set_current_health - ( - RemainingDefenderHealth, - TargettedCharacterInstance - ), - CharacterInstances - ), - BattlemapInstance - ), - character_instance = - character_instance:set_current_health - ( - RemainingAttackerHealth, - ControlledCharacterInstance - ) - }. - --spec handle_character_instance_attacking - ( - query_state(), - input() - ) - -> {list(attack_desc()), query_state()}. -handle_character_instance_attacking (QueryState, Input) -> - BattlemapInstance = QueryState#query_state.battlemap_instance, - ControlledCharacterInstance = QueryState#query_state.character_instance, - ControlledCharacter = - character_instance:get_character(ControlledCharacterInstance), - ControlledCharacterStatistics = - character:get_statistics(ControlledCharacter), - TargettedCharacterInstance = - array:get - ( - Input#input.target_ix, - battlemap_instance:get_character_instances(BattlemapInstance) - ), - TargettedCharacter = - character_instance:get_character(TargettedCharacterInstance), - TargettedCharacterStatistics = character:get_statistics(TargettedCharacter), - RequiredRange = - movement:steps_between - ( - character_instance:get_location(ControlledCharacterInstance), - character_instance:get_location(TargettedCharacterInstance) - ), - {AttackingWeaponID, _} = character:get_weapon_ids(ControlledCharacter), - AttackingWeapon = weapon:from_id(AttackingWeaponID), - {_, AttackingWeaponRange} = weapon:get_ranges(AttackingWeapon), - {DefendingWeaponID, _} = character:get_weapon_ids(TargettedCharacter), - DefendingWeapon = weapon:from_id(DefendingWeaponID), - {DefendingWeaponDefRange, DefendingWeaponAtkRange} = - weapon:get_ranges(DefendingWeapon), - - true = (RequiredRange =< AttackingWeaponRange), - - CanDefend = - ( - (RequiredRange > DefendingWeaponDefRange) - and - (RequiredRange =< DefendingWeaponAtkRange) - ), - CanParry = (weapon:get_range_type(DefendingWeapon) == melee), - Actions = - case {CanDefend, CanParry} of - {true, true} -> - [{second, parry}, counter, {first, parry}]; - {true, false} -> - [second, counter, first]; - {false, _} -> - [second, first] - end, - Effects = - handle_attacks - ( - Actions, - ControlledCharacterStatistics, - TargettedCharacterStatistics, - [] - ), - {RemainingEffects, RemainingAttackerHealth, RemainingDefenderHealth} = - apply_attacks_to_healths - ( - Effects, - character_instance:get_current_health(ControlledCharacterInstance), - character_instance:get_current_health(TargettedCharacterInstance), - [] - ), - - { - RemainingEffects, - set_new_healths_in_query_state - ( - RemainingAttackerHealth, - RemainingDefenderHealth, - QueryState, - Input - ) - }. diff --git a/src/struct/weapon.erl b/src/struct/weapon.erl index d96886c..80cb925 100644 --- a/src/struct/weapon.erl +++ b/src/struct/weapon.erl @@ -55,6 +55,7 @@ [ random_id/0, from_id/1, + can_parry/1, apply_to_attributes/2 ] ). @@ -102,6 +103,9 @@ get_ranges (Wp) -> get_damages (Wp) -> damages_of_type(Wp#weapon.range_type, Wp#weapon.damage_mod). +-spec can_parry (struct()) -> boolean(). +can_parry (Wp) -> (Wp#weapon.range_type == melee). + -spec from_id (id()) -> struct(). from_id (0) -> #weapon{ |