Skip to content

Commit

Permalink
Merge branch 'main' into gh-616-add-client-config-json
Browse files Browse the repository at this point in the history
  • Loading branch information
tvillegas98 committed May 17, 2024
2 parents 30f38a3 + feb69d2 commit 3f360a0
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 23 deletions.
6 changes: 5 additions & 1 deletion apps/arena/lib/arena/game/player.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ defmodule Arena.Game.Player do
Module for interacting with Player entity
"""

alias Arena.GameTracker
alias Arena.Utils
alias Arena.Game.Effect
alias Arena.Game.Skill
Expand Down Expand Up @@ -352,11 +353,14 @@ defmodule Arena.Game.Player do
case heal_interval? and damage_interval? and use_skill_interval? do
true ->
heal_amount = floor(player.aditional_info.max_health * 0.1)
new_health = min(player.aditional_info.health + heal_amount, player.aditional_info.max_health)

GameTracker.push_event(self(), {:heal, player.id, new_health - player.aditional_info.health})

Map.update!(player, :aditional_info, fn info ->
%{
info
| health: min(info.health + heal_amount, info.max_health),
| health: new_health,
last_natural_healing_update: now
}
end)
Expand Down
86 changes: 74 additions & 12 deletions apps/arena/lib/arena/game_tracker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,16 @@ defmodule Arena.GameTracker do
GenServer.cast(__MODULE__, {:finish_tracking, match_pid, winner_id})
end

## TODO: define events structs or pattern
## https://github.com/lambdaclass/mirra_backend/issues/601
@type player_id :: pos_integer()
@type player :: %{id: player_id(), character_name: binary()}
@type event ::
{:kill, player(), player()}
| {:damage_taken, player_id(), non_neg_integer()}
| {:damage_done, player_id(), non_neg_integer()}
| {:heal, player_id(), non_neg_integer()}
| {:kill_by_zone, player_id()}

@spec push_event(pid(), event()) :: :ok
def push_event(match_pid, event) do
GenServer.cast(__MODULE__, {:push_event, match_pid, event})
end
Expand All @@ -46,14 +54,19 @@ defmodule Arena.GameTracker do
controller: if(Enum.member?(human_clients, player_to_client[player.id]), do: :human, else: :bot),
character: player.aditional_info.character_name,
kills: [],
death: nil
death: nil,
damage_taken: 0,
damage_done: 0,
health_healed: 0,
killed_by_bot: false
}

{player.id, player_data}
end)

match_state = %{
match_id: match_id,
start_at: System.monotonic_time(:millisecond),
players: players,
player_to_client: player_to_client,
position_on_death: map_size(players)
Expand All @@ -68,28 +81,61 @@ defmodule Arena.GameTracker do
match_data = get_in(state, [:matches, match_pid])
results = generate_results(match_data, winner_id)
payload = Jason.encode!(%{results: results})
## TODO: Handle errors and retry sending
## https://github.com/lambdaclass/mirra_backend/issues/601
send_request("/arena/match/#{match_data.match_id}", payload)

matches = Map.delete(state.matches, match_pid)
{:noreply, %{state | matches: matches}}
end

def handle_cast({:push_event, match_pid, event}, state) do
state = update_in(state, [:matches, match_pid], fn data -> update_data(data, event) end)
case get_in(state, [:matches, match_pid]) do
nil ->
{:noreply, state}

match_data ->
updated_data = update_data(match_data, event)
state = put_in(state, [:matches, match_pid], updated_data)
{:noreply, state}
end
end

@impl true
def handle_info({:retry_request, path, payload, backoff}, state) do
send_request(path, payload, backoff)
{:noreply, state}
end

defp update_data(data, {:kill, killer, victim}) do
data
|> update_in([:players, killer.id, :kills], fn kills -> kills ++ [victim.character_name] end)
|> put_in([:players, victim.id, :death], killer.character_name)
|> put_in([:players, victim.id, :killed_by_bot], get_in(data, [:players, killer.id, :controller]) == :bot)
|> put_in([:players, victim.id, :position], data.position_on_death)
|> put_in([:position_on_death], data.position_on_death - 1)
end

defp update_data(data, {:kill_by_zone, victim_id}) do
data
|> put_in([:players, victim_id, :death], "zone")
|> put_in([:players, victim_id, :killed_by_bot], false)
|> put_in([:players, victim_id, :position], data.position_on_death)
|> put_in([:position_on_death], data.position_on_death - 1)
end

defp update_data(data, {:damage_taken, player_id, amount}) do
update_in(data, [:players, player_id, :damage_taken], fn damage_taken -> damage_taken + amount end)
end

defp update_data(data, {:damage_done, player_id, amount}) do
update_in(data, [:players, player_id, :damage_done], fn damage_done -> damage_done + amount end)
end

defp update_data(data, {:heal, player_id, amount}) do
update_in(data, [:players, player_id, :health_healed], fn health_healed -> health_healed + amount end)
end

defp generate_results(match_data, winner_id) do
duration = System.monotonic_time(:millisecond) - match_data.start_at

Enum.filter(match_data.players, fn {_player_id, player_data} -> player_data.controller == :human end)
|> Enum.map(fn {_player_id, player_data} ->
%{
Expand All @@ -103,16 +149,32 @@ defmodule Arena.GameTracker do
## https://github.com/lambdaclass/mirra_backend/issues/601
deaths: if(player_data.death == nil, do: 0, else: 1),
character: player_data.character,
position: player_data.position
position: if(player_data.id == winner_id, do: 1, else: player_data.position),
damage_taken: player_data.damage_taken,
damage_done: player_data.damage_done,
health_healed: player_data.health_healed,
killed_by: player_data.death,
killed_by_bot: player_data.killed_by_bot,
duration_ms: duration
}
end)
end

defp send_request(path, payload) do
defp send_request(path, payload, backoff \\ 0) do
gateway_url = Application.get_env(:arena, :gateway_url)

Finch.build(:post, "#{gateway_url}#{path}", [{"content-type", "application/json"}], payload)
## We might want to change this to `Finch.async_request/2`, but let's measure the impact first
|> Finch.request(Arena.Finch)
result =
Finch.build(:post, "#{gateway_url}#{path}", [{"content-type", "application/json"}], payload)
## We might want to change this to `Finch.async_request/2`, but let's measure the impact first
|> Finch.request(Arena.Finch)

case result do
{:ok, %Finch.Response{status: 201}} ->
:ok

## TODO: need to figure out user not found to prevent retrying in that case
_else_error ->
Process.send_after(self(), {:retry_request, path, payload, backoff + 1}, backoff * 500)
end
end
end
22 changes: 15 additions & 7 deletions apps/arena/lib/arena/game_updater.ex
Original file line number Diff line number Diff line change
Expand Up @@ -340,8 +340,6 @@ defmodule Arena.GameUpdater do
) do
entry = %{killer_id: killer_id, victim_id: victim_id}
victim = Map.get(game_state.players, victim_id)
killer = Map.get(game_state.players, killer_id)

amount_of_power_ups = get_amount_of_power_ups(victim, game_config.power_ups.power_ups_per_kill)

game_state =
Expand All @@ -353,11 +351,17 @@ defmodule Arena.GameUpdater do

broadcast_player_dead(state.game_state.game_id, victim_id)

GameTracker.push_event(
self(),
{:kill, %{id: killer.id, character_name: killer.aditional_info.character_name},
%{id: victim.id, character_name: victim.aditional_info.character_name}}
)
case Map.get(game_state.players, killer_id) do
nil ->
GameTracker.push_event(self(), {:kill_by_zone, victim.id})

killer ->
GameTracker.push_event(
self(),
{:kill, %{id: killer.id, character_name: killer.aditional_info.character_name},
%{id: victim.id, character_name: victim.aditional_info.character_name}}
)
end

{:noreply, %{state | game_state: game_state}}
end
Expand All @@ -372,6 +376,8 @@ defmodule Arena.GameUpdater do
end

def handle_info({:damage_done, player_id, damage}, state) do
GameTracker.push_event(self(), {:damage_done, player_id, damage})

state =
update_in(state, [:game_state, :damage_done, player_id], fn
nil -> damage
Expand All @@ -382,6 +388,8 @@ defmodule Arena.GameUpdater do
end

def handle_info({:damage_taken, player_id, damage}, state) do
GameTracker.push_event(self(), {:damage_taken, player_id, damage})

state =
update_in(state, [:game_state, :damage_taken, player_id], fn
nil -> damage
Expand Down
22 changes: 19 additions & 3 deletions apps/game_backend/lib/game_backend/matches/arena_match_result.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ defmodule GameBackend.Matches.ArenaMatchResult do
field(:character, :string)
field(:match_id, Ecto.UUID)
field(:position, :integer)
field(:damage_done, :integer)
field(:damage_taken, :integer)
field(:health_healed, :integer)
field(:killed_by, :string)
field(:killed_by_bot, :boolean)
field(:duration_ms, :integer)
timestamps()

belongs_to(:user, GameBackend.Users.GoogleUser)
Expand All @@ -25,21 +31,31 @@ defmodule GameBackend.Matches.ArenaMatchResult do
:character,
:match_id,
:user_id,
:position
:position,
:damage_done,
:damage_taken,
:health_healed,
:killed_by_bot,
:duration_ms
]

@permitted [] ++ @required
@permitted [:killed_by] ++ @required

def changeset(match_result, attrs) do
match_result
|> cast(attrs, @permitted)
|> validate_required(@required)
|> validate_number(:kills, greater_than_or_equal_to: 0)
|> validate_number(:deaths, greater_than_or_equal_to: 0)
|> validate_number(:damage_done, greater_than_or_equal_to: 0)
|> validate_number(:damage_taken, greater_than_or_equal_to: 0)
|> validate_number(:health_healed, greater_than_or_equal_to: 0)
|> validate_number(:duration_ms, greater_than_or_equal_to: 0)
|> validate_inclusion(:result, ["win", "loss", "abandon"])
## TODO: This enums should actually be read from config
## https://github.com/lambdaclass/mirra_backend/issues/601
|> validate_inclusion(:character, ["h4ck", "muflus", "uma"])
|> validate_inclusion(:character, ["h4ck", "muflus", "uma", "valtimer", "otix"])
|> validate_inclusion(:killed_by, ["h4ck", "muflus", "uma", "valtimer", "otix", "zone"])
|> foreign_key_constraint(:user_id)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
defmodule GameBackend.Repo.Migrations.AddMoreMatchResultsData do
use Ecto.Migration

def change do
alter table(:arena_match_results) do
add :damage_done, :integer
add :damage_taken, :integer
add :health_healed, :integer
add :killed_by, :string
add :killed_by_bot, :boolean
add :duration_ms, :integer
end

## This is to allow us to later set `null: false` on all the new columns
## without having to set a default value when adding the column
## Having a default value in the DB could lead to us having a hidden bug or wrong values if
## it where to be used by accident
query = """
UPDATE arena_match_results
SET duration_ms = 600000, damage_done = 0, damage_taken = 0, health_healed = 0, killed_by_bot = false
WHERE duration_ms IS NULL
"""
execute(query, "")

alter table(:arena_match_results) do
modify :duration_ms, :integer, from: :integer, null: false
modify :damage_done, :integer, from: :integer, null: false
modify :damage_taken, :integer, from: :integer, null: false
modify :health_healed, :integer, from: :integer, null: false
modify :killed_by_bot, :boolean, from: :boolean, null: false
end
end
end

0 comments on commit 3f360a0

Please sign in to comment.