Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GH-327] Add Speed #588

Merged
merged 10 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 44 additions & 15 deletions apps/champions/lib/champions/battle/simulator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,51 @@ defmodule Champions.Battle.Simulator do
has no cooldown and it's cast whenever a unit reaches 500 energy. Energy is gained whenever the target attacks.
The primary skill has a cooldown and it's cast when it's available if the ultimate is not.

Skills possess many effects with their own targets. Effects are composed of `Components`, `Modifiers` and
`Executions` (check module docs for more info on each).
Skills possess many mechanics. The only implemented mechanic right now is `ApplyEffectsTo`, which is composed of many effects
and a targeting strategy. Effects are composed of `Components`, `Modifiers` and `Executions` (check module docs for more info on each).

They have different application types (checked are implemented):
### ApplyEffectsTo mechanics

Effects have different application types:
[x] Instant - Applied once, irreversible.
[x] Permanent - Applied once, is stored in the unit so that it can be reversed (with a dispel, for example)
[x] Duration - Applied once and reverted once its duration ends.
[ ] Periodic - Applied every X steps until duration ends.

They also have different targeting strategies:
The different targeting strategies are:
[x] Random
[X] Nearest
[X] Furthest
[ ] Frontline - Heroes in slots 1 and 2
[ ] Backline - Heroes in slots 2 to 4
[x] Nearest
[x] Furthest
[x] Frontline - Heroes in slots 1 and 2
[x] Backline - Heroes in slots 2 to 4
[x] All
[ ] Self
[ ] Factions
[ ] Classes
[ ] Min (STAT)
[ ] Max (STAT)

And different ways in which their amount is interpreted:
[x] Additive
[x] Multiplicative
[x] Additive & based on stat - The amount is a % of one of the caster's stats
[ ] Multiplicative & based on stat?
It can also be chosen how many targets are affected by the effect, and if they are allies or enemies.


### Simultaneous Battles

Two units can attack the same unit at the same time and over-kill it. This is expected behavior that results
from having the battle be simultaneous.
from having the battle be simultaneous. If this weren't the case, the battle would be turn-based, since a unit
would base its actions on the state of the battle at the end of the previous unit's action.

### Speed Stat
Units have a `speed` stat that affects the cooldown of their basic skill. The formula is:
`FINAL_CD = BASE_CD / [1 + MAX(-99, SPEED) / 100];`
For now, speed is only used to calculate the cooldown of newly cast skills, meaning it's not retroactive with
skills already on cooldown.

### History

A "history" is built as the battle progresses. This history is used to animate the battle in the client. The history
is a list of maps, each map representing a step in the battle. Each step has a `step_number` and a list of `actions`.
These are all translated into Protobuf messages, together with the initial state of the battle and the result,
and then sent to the client.
"""
alias Champions.Units
alias GameBackend.Units.Skills.Skill
Expand Down Expand Up @@ -208,7 +225,7 @@ defmodule Champions.Battle.Simulator do
|> put_in(
[:units, unit.id, :basic_skill, :remaining_cooldown],
# We need this + 1 because we're going to reduce the cooldown at the end of the step
unit.basic_skill.base_cooldown + 1
calculate_cooldown(unit.basic_skill, unit) + 1
)
|> update_in([:units, unit.id, :energy], &(&1 + unit.basic_skill.energy_regen))

Expand Down Expand Up @@ -267,6 +284,16 @@ defmodule Champions.Battle.Simulator do
{new_state, new_history}
end

defp calculate_cooldown(skill, unit) do
speed = calculate_unit_stat(unit, :speed) |> Decimal.from_float()

divisor = Decimal.div(Decimal.max(-99, speed), 100) |> Decimal.add(1)

Decimal.div(skill.base_cooldown, divisor)
|> Decimal.round()
|> Decimal.to_integer()
agustinesco marked this conversation as resolved.
Show resolved Hide resolved
end

# Reduces modifier timers and removes expired ones.
# Called when processing a step for a unit.
defp reduce_modifier_timers(modifiers, unit, history) do
Expand Down Expand Up @@ -966,6 +993,7 @@ defmodule Champions.Battle.Simulator do
health: Units.get_health(unit),
attack: Units.get_attack(unit),
defense: Units.get_defense(unit),
speed: Units.get_speed(unit),
energy: 0,
modifiers: %{
additives: [],
Expand Down Expand Up @@ -1104,6 +1132,7 @@ defmodule Champions.Battle.Simulator do
defp string_to_atom("duration"), do: :duration
defp string_to_atom("period"), do: :period
defp string_to_atom("instant"), do: :instant
defp string_to_atom("permanent"), do: :permanent

defp string_to_atom("ATTACK"), do: :ATTACK
defp string_to_atom("DEFENSE"), do: :DEFENSE
Expand Down
32 changes: 21 additions & 11 deletions apps/champions/lib/champions/units.ex
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ defmodule Champions.Units do
@doc """
Get a unit's health stat for battle, including modifiers from items.

Character and ItemTemplate must be preloaded.
Character, Items and ItemTemplates must be preloaded.

## Examples

Expand All @@ -380,7 +380,7 @@ defmodule Champions.Units do
@doc """
Get a unit's attack stat for battle, including modifiers from items.

Character and ItemTemplate must be preloaded.
Character, Items and ItemTemplates must be preloaded.

## Examples

Expand All @@ -393,7 +393,7 @@ defmodule Champions.Units do
@doc """
Get a unit's defense stat for battle, including modifiers from items.

Character and ItemTemplate must be preloaded.
Character, Items and ItemTemplates must be preloaded.

## Examples

Expand All @@ -403,6 +403,20 @@ defmodule Champions.Units do
"""
def get_defense(unit), do: calculate_stat(unit.character.base_defense, unit, "defense")

@doc """
Get a unit's speed stat for battle, including modifiers from items.
Unlike other stats, speed is not affected by the unit's level, tier or rank.

Items and Templates must be preloaded.

## Examples

iex> {:ok, unit} = Champions.Units.get_unit(unit_id)
iex> Champions.Units.get_speed(unit)
100
"""
def get_speed(unit), do: factor_items(Decimal.new(0), unit.items, "speed")

defp calculate_stat(base_stat, unit, stat_name),
do:
base_stat
Expand Down Expand Up @@ -459,17 +473,13 @@ defmodule Champions.Units do
end

defp get_additive_and_multiplicative_modifiers(items, attribute) do
item_modifiers =
Enum.flat_map(items, & &1.template.modifiers)
item_modifiers = Enum.flat_map(items, & &1.template.modifiers)

attribute_modifiers =
Enum.filter(item_modifiers, &(&1.attribute == attribute))
attribute_modifiers = Enum.filter(item_modifiers, &(&1.attribute == attribute))

additive_modifiers =
Enum.filter(attribute_modifiers, &(&1.operation == "Add"))
additive_modifiers = Enum.filter(attribute_modifiers, &(&1.operation == "Add"))

multiplicative_modifiers =
Enum.filter(attribute_modifiers, &(&1.operation == "Multiply"))
multiplicative_modifiers = Enum.filter(attribute_modifiers, &(&1.operation == "Multiply"))

{additive_modifiers, multiplicative_modifiers}
end
Expand Down
Loading
Loading