From 9b73f3efa534fa4dfb8ff832550c1914342ddbb9 Mon Sep 17 00:00:00 2001 From: nsensfel Date: Mon, 5 Mar 2018 17:15:26 +0100 Subject: Improving the way attacks are represented. --- src/battle/attack.erl | 308 -------------------------------------------------- src/struct/attack.erl | 303 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 303 insertions(+), 308 deletions(-) delete mode 100644 src/battle/attack.erl create mode 100644 src/struct/attack.erl (limited to 'src') diff --git a/src/battle/attack.erl b/src/battle/attack.erl deleted file mode 100644 index f1ef048..0000000 --- a/src/battle/attack.erl +++ /dev/null @@ -1,308 +0,0 @@ --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' - ). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --export_type -( - [ - hits/0, - critical/0, - attack_category/0, - attack_effect/0, - attack_desc/0, - attack_order_with_parry/0 - ] -). - --export -( - [ - get_sequence/3, - get_description_of/3, - apply_to_healths/3 - ] -). - --export -( - [ - encode/1 - ] -). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% 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. - --spec encode (attack_desc()) -> binary(). -% This shouldn't be a possibility. Types in this module are a mess... -encode ({AttackCategory, AttackEffect}) -> - jiffy:encode - ( - { - [ - <<"attack">>, - list_to_binary - ( - io_lib:format - ( - "~p", - [{AttackCategory, AttackEffect}] - ) - ) - ] - } - ). diff --git a/src/struct/attack.erl b/src/struct/attack.erl new file mode 100644 index 0000000..b27ff48 --- /dev/null +++ b/src/struct/attack.erl @@ -0,0 +1,303 @@ +-module(attack). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-type order() :: ('first' | 'second' | 'counter'). +-type precision() :: ('misses' | 'grazes' | 'hits'). + +-record +( + attack, + { + order :: order(), + precision :: precision(), + is_critical :: boolean(), + is_parry :: boolean(), + damage :: non_neg_integer() + } +). + +-opaque struct() :: #attack{}. +-type maybe_struct() :: ('nothing' | struct()). +-opaque step() :: {order(), boolean()}. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-export_type([struct/0, maybe_struct/0, step/0]). + +-export +( + [ + get_sequence/3, + get_description_of/3, + apply_to_healths/3 + ] +). + +-export +( + [ + encode/1 + ] +). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec roll_precision + ( + statistics:struct(), + statistics:struct() + ) + -> precision(). +roll_precision (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() + ) + -> {non_neg_integer(), boolean()}. +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) -> {(BaseDamage * 2), true}; + _ -> {BaseDamage, false} + end. + +-spec roll_parry (statistics:struct()) -> boolean(). +roll_parry (DefenderStatistics) -> + DefenderParryChance = statistics:get_parries(DefenderStatistics), + (roll:percentage() =< DefenderParryChance). + +-spec effect_of_attack + ( + order(), + statistics:struct(), + statistics:struct(), + boolean() + ) + -> struct(). +effect_of_attack (Order, AttackerStatistics, DefenderStatistics, CanParry) -> + ParryIsSuccessful = (CanParry and roll_parry(DefenderStatistics)), + {ActualAtkStatistics, ActualDefStatistics} = + case ParryIsSuccessful of + true -> {DefenderStatistics, AttackerStatistics}; + false -> {AttackerStatistics, DefenderStatistics} + end, + + Precision = roll_precision(ActualAtkStatistics, ActualDefStatistics), + {Damage, IsCritical} = roll_damage(ActualAtkStatistics, ActualDefStatistics), + ActualDamage = + case Precision of + misses -> 0; + grazes -> trunc(Damage / 2); + hits -> Damage + end, + + #attack + { + order = Order, + precision = Precision, + is_critical = IsCritical, + is_parry = ParryIsSuccessful, + damage = ActualDamage + }. + +-spec encode_order (order()) -> binary(). +encode_order (first) -> <<"f">>; +encode_order (counter) -> <<"c">>; +encode_order (second) -> <<"s">>. + +-spec encode_precision (precision()) -> binary(). +encode_precision (hits) -> <<"h">>; +encode_precision (grazes) -> <<"g">>; +encode_precision (misses) -> <<"m">>. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-spec get_description_of + ( + step(), + statistics:struct(), + statistics:struct() + ) + -> maybe_struct(). +get_description_of +( + {first, CanParry}, + AttackerStatistics, + DefenderStatistics +) -> + effect_of_attack(first, AttackerStatistics, DefenderStatistics, CanParry); +get_description_of +( + {second, CanParry}, + AttackerStatistics, + DefenderStatistics +) -> + AttackerDoubleAttackChange = statistics:get_double_hits(AttackerStatistics), + + case roll:percentage() of + X when (X =< AttackerDoubleAttackChange) -> + effect_of_attack + ( + second, + AttackerStatistics, + DefenderStatistics, + CanParry + ); + + _ -> + nothing + end; +get_description_of +( + {counter, CanParry}, + AttackerStatistics, + DefenderStatistics +) -> + effect_of_attack(counter, DefenderStatistics, AttackerStatistics, CanParry). + +-spec apply_to_healths + ( + maybe_struct(), + non_neg_integer(), + non_neg_integer() + ) + -> {maybe_struct(), non_neg_integer(), non_neg_integer()}. +apply_to_healths +( + nothing, + AttackerHealth, + DefenderHealth +) -> + {nothing, AttackerHealth, DefenderHealth}; +apply_to_healths +( + Attack, + AttackerHealth, + DefenderHealth +) +when +( + (Attack#attack.order == first) + or (Attack#attack.order == second) + or ((Attack#attack.order == counter) and Attack#attack.is_parry) +) -> + Damage = Attack#attack.damage, + + case AttackerHealth of + 0 -> + {nothing, AttackerHealth, DefenderHealth}; + + _ -> + { + Attack, + AttackerHealth, + max(0, (DefenderHealth - Damage)) + } + end; +apply_to_healths +( + {Attack, Effect}, + AttackerHealth, + DefenderHealth +) +when +( + (Attack#attack.order == counter) + or + ( + (Attack#attack.is_parry) + and ((Attack#attack.order == first) or (Attack#attack.order == second)) + ) +) -> + {_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(step()). +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. + +-spec encode (struct()) -> binary(). +% This shouldn't be a possibility. Types in this module are a mess... +encode (Attack) -> + Order = Attack#attack.order, + Precision = Attack#attack.precision, + IsCritical = Attack#attack.is_critical, + IsParry = Attack#attack.is_parry, + Damage = Attack#attack.damage, + + jiffy:encode + ( + { + [ + {<<"ord">>, encode_order(Order)}, + {<<"pre">>, encode_precision(Precision)}, + {<<"cri">>, IsCritical}, + {<<"par">>, IsParry}, + {<<"dmg">>, Damage} + ] + } + ). -- cgit v1.2.3-70-g09d2