summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/battle')
-rw-r--r--src/battle/attack.erl279
-rw-r--r--src/battle/battle_turn.erl135
-rw-r--r--src/battle/movement.erl58
-rw-r--r--src/battle/roll.erl32
4 files changed, 504 insertions, 0 deletions
diff --git a/src/battle/attack.erl b/src/battle/attack.erl
new file mode 100644
index 0000000..7384f78
--- /dev/null
+++ b/src/battle/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/battle/battle_turn.erl b/src/battle/battle_turn.erl
new file mode 100644
index 0000000..e295f09
--- /dev/null
+++ b/src/battle/battle_turn.erl
@@ -0,0 +1,135 @@
+-module(battle_turn).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-export
+(
+ [
+ handle_post_play/1
+ ]
+).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec activate_relevant_character_instances
+ (
+ list(non_neg_integer()),
+ array:array(character_instance:struct()),
+ player:id(),
+ (-1 | non_neg_integer())
+ )
+ -> {list(non_neg_integer()), array:array(character_instance:struct())}.
+activate_relevant_character_instances (IXs, CharacterInstances, _Owner, -1) ->
+ {IXs, CharacterInstances};
+activate_relevant_character_instances (IXs, CharacterInstances, Owner, IX) ->
+ CharacterInstance = array:get(IX, CharacterInstances),
+ Character = character_instance:get_character(CharacterInstance),
+ case character:get_owner_id(Character) of
+ OwnerID when (OwnerID == Owner) ->
+ activate_relevant_character_instances
+ (
+ [IX|IXs],
+ array:set
+ (
+ IX,
+ character_instance:set_is_active(true, CharacterInstance),
+ CharacterInstances
+ ),
+ Owner,
+ (IX - 1)
+ );
+
+ _ ->
+ activate_relevant_character_instances
+ (
+ IXs,
+ CharacterInstances,
+ Owner,
+ (IX - 1)
+ )
+ end.
+
+-spec start_next_players_turn (battle:struct()) ->
+ {list(non_neg_integer()), battle:struct()}.
+start_next_players_turn (Battle) ->
+ PlayerIDs = battle:get_player_ids(Battle),
+ PlayerTurn = battle:get_current_player_turn(Battle),
+ CurrentPlayerIX = player_turn:get_player_ix(PlayerTurn),
+ CurrentTurnNumber = player_turn:get_number(PlayerTurn),
+ CharacterInstances = battle:get_character_instances(Battle),
+
+ NextPlayerIX = ((CurrentPlayerIX + 1) rem (array:size(PlayerIDs))),
+ NextPlayerTurn =
+ player_turn:new
+ (
+ case NextPlayerIX of
+ 0 -> (CurrentTurnNumber + 1);
+ _ -> CurrentTurnNumber
+ end,
+ NextPlayerIX
+ ),
+
+ {ActivatedCharacterInstanceIXs, UpdatedCharacterInstances} =
+ activate_relevant_character_instances
+ (
+ [],
+ CharacterInstances,
+ array:get(NextPlayerIX, PlayerIDs),
+ (array:size(CharacterInstances) - 1)
+ ),
+ UpdatedBattle =
+ battle:set_character_instances
+ (
+ UpdatedCharacterInstances,
+ battle:set_current_player_turn
+ (
+ NextPlayerTurn,
+ Battle
+ )
+ ),
+ {ActivatedCharacterInstanceIXs, UpdatedBattle}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-spec handle_post_play (battle:struct()) ->
+ {database_diff:struct(), battle:struct()}.
+handle_post_play (Battle) ->
+ CharacterInstances = battle:get_character_instances(Battle),
+
+ AnActiveCharacterInstanceRemains =
+ array:foldl
+ (
+ fun (_IX, CharacterInstance, Prev) ->
+ (Prev or character_instance:get_is_active(CharacterInstance))
+ end,
+ false,
+ CharacterInstances
+ ),
+
+ case AnActiveCharacterInstanceRemains of
+ true ->
+ io:format("~nThere are still active characters.~n"),
+ {[], Battle};
+
+ false ->
+ io:format("~nThere are no more active characters.~n"),
+ {UpdatedCharacterInstanceIXs, UpdatedBattle} =
+ start_next_players_turn(Battle),
+ {
+ lists:map
+ (
+ fun (IX) ->
+ {set, character_instance, IX, is_active, true}
+ end,
+ UpdatedCharacterInstanceIXs
+ ),
+ UpdatedBattle
+ }
+ end.
diff --git a/src/battle/movement.erl b/src/battle/movement.erl
new file mode 100644
index 0000000..720b60c
--- /dev/null
+++ b/src/battle/movement.erl
@@ -0,0 +1,58 @@
+-module(movement).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-export([cross/4]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec cross
+ (
+ battlemap:struct(),
+ list(location:type()),
+ list(direction:enum()),
+ non_neg_integer(),
+ location:type()
+ )
+ -> {location:type(), non_neg_integer()}.
+cross (_Battlemap, _ForbiddenLocations, [], Cost, Location) ->
+ {Location, Cost};
+cross (Battlemap, ForbiddenLocations, [Step|NextSteps], Cost, Location) ->
+ NextLocation = location:apply_direction(Step, Location),
+ NextTile = battlemap:get_tile_id(NextLocation, Battlemap),
+ NextCost = (Cost + tile:get_cost(NextTile)),
+ IsForbidden =
+ lists:foldl
+ (
+ fun (ForbiddenLocation, Prev) ->
+ (Prev or (NextLocation == ForbiddenLocation))
+ end,
+ false,
+ ForbiddenLocations
+ ),
+
+ IsForbidden = false,
+
+ cross(Battlemap, ForbiddenLocations, NextSteps, NextCost, NextLocation).
+
+-spec cross
+ (
+ battlemap:struct(),
+ array:array(location:type()),
+ list(direction:enum()),
+ location:type()
+ )
+ -> {location:type(), non_neg_integer()}.
+cross (Battlemap, ForbiddenLocations, Path, Location) ->
+ cross(Battlemap, ForbiddenLocations, Path, 0, Location).
diff --git a/src/battle/roll.erl b/src/battle/roll.erl
new file mode 100644
index 0000000..074054b
--- /dev/null
+++ b/src/battle/roll.erl
@@ -0,0 +1,32 @@
+-module(roll).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% TYPES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-export
+(
+ [
+ percentage/0,
+ between/2
+ ]
+).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% LOCAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% EXPORTED FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-spec between (non_neg_integer(), non_neg_integer()) -> non_neg_integer().
+between (Min, Max) ->
+ Diff = (Max - Min),
+ (Min + (rand:uniform(Diff + 1) - 1)).
+
+-spec percentage () -> 0..100.
+percentage () ->
+ between(0, 100).