Skip to content

Commit

Permalink
Integrate battles and levels (#468)
Browse files Browse the repository at this point in the history
* Integrate battles and levels

* Fix battle and tests

* Fix wrong stat name in gateway.proto

* Add seed log

* Update apps/gateway/test/champions_test.exs

Co-authored-by: Nicolas Continanza <[email protected]>

---------

Co-authored-by: Nicolas Continanza <[email protected]>
  • Loading branch information
lotuuu and ncontinanza authored Apr 15, 2024
1 parent 333b349 commit 037665e
Show file tree
Hide file tree
Showing 10 changed files with 55 additions and 140 deletions.
41 changes: 10 additions & 31 deletions apps/champions/lib/champions/battle.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ defmodule Champions.Battle do
Fight outcomes are decided randomly, favoring the team with the higher aggregate level.
"""

alias Champions.Battle.Simulator
alias GameBackend.Campaigns
alias GameBackend.Campaigns.SuperCampaignProgress
alias GameBackend.Units
Expand All @@ -22,14 +23,16 @@ defmodule Champions.Battle do
{:level_valid, true} <- {:level_valid, current_level_id == level_id} do
units = Units.get_selected_units(user_id)

if battle(units, level.units) == :team_1 do
case Champions.Campaigns.advance_level(user_id, level.campaign.super_campaign_id) do
case Simulator.run_battle(units, level.units) do
%{result: "team_1"} = result ->
# TODO: add rewards to response [CHoM-191]
{:ok, _changes} -> :win
_error -> {:error, :failed_to_advance}
end
else
:loss
case Champions.Campaigns.advance_level(user_id, level.campaign.super_campaign_id) do
{:ok, _changes} -> result
_error -> {:error, :failed_to_advance}
end

result ->
result
end
else
{:user_exists, false} -> {:error, :user_not_found}
Expand All @@ -38,28 +41,4 @@ defmodule Champions.Battle do
{:level_valid, false} -> {:error, :level_invalid}
end
end

@doc """
Run a battle between two teams. The outcome is decided randomly, favoring the team
with the higher aggregate level of their selected units. Returns `:team_1` or `:team_2`.
"""
def battle(team_1, team_2) do
team_1_agg_level =
Enum.reduce(team_1, 0, fn unit, acc ->
unit.level + item_level_agg(unit.items) + acc
end)

team_2_agg_level =
Enum.reduce(team_2, 0, fn unit, acc ->
unit.level + item_level_agg(unit.items) + acc
end)

total_level = team_1_agg_level + team_2_agg_level

if Enum.random(1..total_level) <= team_1_agg_level, do: :team_1, else: :team_2
end

defp item_level_agg(items) do
Enum.reduce(items, 0, fn item, acc -> item.level + acc end)
end
end
12 changes: 12 additions & 0 deletions apps/champions/lib/champions/battle/simulator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ defmodule Champions.Battle.Simulator do
seed = options[:seed] || @default_seed

:rand.seed(:default, seed)
Logger.info("Running battle with seed: #{seed}")

team_1 = Enum.into(team_1, %{}, fn unit -> create_unit_map(unit, 1) end)
team_2 = Enum.into(team_2, %{}, fn unit -> create_unit_map(unit, 2) end)
Expand Down Expand Up @@ -584,6 +585,17 @@ defmodule Champions.Battle.Simulator do
{new_target, new_history}
end

defp process_execution(
_,
target,
caster,
history,
_skill_id
) do
Logger.warning("#{format_unit_name(caster)} tried to apply an unknown execution to #{format_unit_name(caster)}")
{target, history}
end

# Calculate the current amount of the given attribute that the unit has, based on its modifiers.
defp calculate_unit_stat(unit, attribute) do
overrides = Enum.filter(unit.modifiers.overrides, &(&1.attribute == Atom.to_string(attribute)))
Expand Down
4 changes: 2 additions & 2 deletions apps/champions/priv/skills.json
Original file line number Diff line number Diff line change
Expand Up @@ -853,7 +853,7 @@
"components": [],
"modifiers": [
{
"attribute": "armor",
"attribute": "defense",
"modifier_operation": "Multiply",
"magnitude_calc_type": "Float",
"float_magnitude": 0.85
Expand All @@ -868,7 +868,7 @@
}
],
"target_strategy": {
"highest": "Armor"
"highest": "defense"
},
"target_count": 2,
"target_allies": false
Expand Down
5 changes: 4 additions & 1 deletion apps/game_backend/lib/game_backend/campaigns.ex
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@ defmodule GameBackend.Campaigns do
Returns `{:error, :not_found}` if no level is found.
"""
def get_level(level_id) do
level = Repo.get(Level, level_id) |> Repo.preload([:campaign, :currency_rewards, units: :items, units: :character])
level =
Repo.get(Level, level_id)
|> Repo.preload([:campaign, :currency_rewards, units: [:items, character: [:ultimate_skill, :basic_skill]]])

if level, do: {:ok, level}, else: {:error, :not_found}
end

Expand Down
2 changes: 1 addition & 1 deletion apps/game_backend/lib/game_backend/units.ex
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ defmodule GameBackend.Units do
do:
from(unit in user_units_query(user_id), where: unit.selected)
|> Repo.all()
|> Repo.preload([:character, :user, :items])
|> Repo.preload([:user, :items, character: [:basic_skill, :ultimate_skill]])

@doc """
Get a user's unit associated to the given character.
Expand Down
53 changes: 10 additions & 43 deletions apps/gateway/lib/gateway/champions_socket_handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ defmodule Gateway.ChampionsSocketHandler do
GetBoxes,
GetBox,
Summon,
GetUserSuperCampaignProgresses,
BattleTest
GetUserSuperCampaignProgresses
}

@behaviour :cowboy_websocket
Expand Down Expand Up @@ -115,8 +114,15 @@ defmodule Gateway.ChampionsSocketHandler do

defp handle(%FightLevel{user_id: user_id, level_id: level_id}) do
case Battle.fight_level(user_id, level_id) do
{:error, reason} -> prepare_response({:error, reason}, nil)
battle_result -> prepare_response(%{result: Atom.to_string(battle_result)}, :battle_result)
{:error, reason} ->
prepare_response({:error, reason}, nil)

battle_result ->
battle_result
|> update_in([:steps], fn steps ->
Enum.map(steps, &prepare_step/1)
end)
|> prepare_response(:battle_result)
end
end

Expand Down Expand Up @@ -208,45 +214,6 @@ defmodule Gateway.ChampionsSocketHandler do
prepare_response(%{super_campaign_progresses: super_campaign_progresses}, :super_campaign_progresses)
end

# Temporary endpoint to test the sending of battle replays to the client
defp handle(%BattleTest{user_id: _user_id}) do
team1 =
Enum.map(1..6, fn slot ->
GameBackend.Units.insert_unit(%{
character_id: GameBackend.Units.Characters.get_character_by_name("Muflus").id,
level: 1,
tier: 1,
rank: 1,
selected: true,
user_id: nil,
slot: slot
})
|> elem(1)
|> GameBackend.Repo.preload(character: [:basic_skill, :ultimate_skill])
end)

team2 =
Enum.map(1..6, fn slot ->
GameBackend.Units.insert_unit(%{
character_id: GameBackend.Units.Characters.get_character_by_name("Muflus").id,
level: 1,
tier: 1,
rank: 1,
selected: true,
user_id: nil,
slot: slot
})
|> elem(1)
|> GameBackend.Repo.preload(character: [:basic_skill, :ultimate_skill])
end)

Champions.Battle.Simulator.run_battle(team1, team2)
|> update_in([:steps], fn steps ->
Enum.map(steps, &prepare_step/1)
end)
|> prepare_response(:battle_replay)
end

defp handle(unknown_request),
do: Logger.warning("[Gateway.ChampionsSocketHandler] Received unknown request #{unknown_request}")

Expand Down
34 changes: 4 additions & 30 deletions apps/gateway/lib/gateway/serialization/gateway.pb.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ defmodule Gateway.Serialization.Stat do

field(:HEALTH, 0)
field(:ENERGY, 1)
field(:DAMAGE, 2)
field(:ATTACK, 2)
field(:DEFENSE, 3)
field(:DAMAGE_REDUCTION, 4)
field(:SPEED, 5)
end

defmodule Gateway.Serialization.WebSocketRequest do
Expand Down Expand Up @@ -109,20 +111,6 @@ defmodule Gateway.Serialization.WebSocketRequest do
json_name: "getUserSuperCampaignProgresses",
oneof: 0
)

field(:battle_test, 24,
type: Gateway.Serialization.BattleTest,
json_name: "battleTest",
oneof: 0
)
end

defmodule Gateway.Serialization.BattleTest do
@moduledoc false

use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0"

field(:user_id, 1, type: :string, json_name: "userId")
end

defmodule Gateway.Serialization.GetUser do
Expand Down Expand Up @@ -366,12 +354,6 @@ defmodule Gateway.Serialization.WebSocketResponse do
json_name: "superCampaignProgresses",
oneof: 0
)

field(:battle_replay, 16,
type: Gateway.Serialization.BattleReplay,
json_name: "battleReplay",
oneof: 0
)
end

defmodule Gateway.Serialization.User do
Expand Down Expand Up @@ -561,14 +543,6 @@ defmodule Gateway.Serialization.CurrencyReward do
field(:amount, 3, type: :uint64)
end

defmodule Gateway.Serialization.BattleResult do
@moduledoc false

use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0"

field(:result, 1, type: :string)
end

defmodule Gateway.Serialization.AfkRewards do
@moduledoc false

Expand Down Expand Up @@ -652,7 +626,7 @@ defmodule Gateway.Serialization.UserAndUnit do
field(:unit, 2, type: Gateway.Serialization.Unit)
end

defmodule Gateway.Serialization.BattleReplay do
defmodule Gateway.Serialization.BattleResult do
@moduledoc false

use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0"
Expand Down
12 changes: 6 additions & 6 deletions apps/gateway/test/champions_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -339,11 +339,11 @@ defmodule Gateway.Test.Champions do
fetch_last_message(socket_tester)

assert_receive %WebSocketResponse{
response_type: {:battle_result, _ = battle_result}
response_type: {:battle_result, battle_result}
}

# Battle result should be either win or loss
assert battle_result.result == "win" or battle_result.result == "loss"
# Battle result should be either team_1, team_2, draw or timeout
assert battle_result.result in ["team_1", "team_2", "draw", "timeout"]

# TODO: check rewards [#CHoM-341]
end
Expand Down Expand Up @@ -372,7 +372,7 @@ defmodule Gateway.Test.Champions do
response_type: {:battle_result, _ = battle_result}
}

assert battle_result.result == "win"
assert battle_result.result == "team_1"

{:ok, advanced_user} = Users.get_user(user.id)

Expand Down Expand Up @@ -447,7 +447,7 @@ defmodule Gateway.Test.Champions do
response_type: {:battle_result, _ = battle_result}
}

assert battle_result.result == "win"
assert battle_result.result == "team_1"

# Get advanced user
SocketTester.get_user(socket_tester, user.id)
Expand Down Expand Up @@ -528,7 +528,7 @@ defmodule Gateway.Test.Champions do
response_type: {:battle_result, _ = battle_result}
}

assert battle_result.result == "win"
assert battle_result.result == "team_1"

# Get new user
SocketTester.get_user(socket_tester, user.id)
Expand Down
13 changes: 1 addition & 12 deletions apps/gateway/test/support/socket_tester.ex
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ defmodule Gateway.SocketTester do
GetBox,
GetBoxes,
Summon,
GetUserSuperCampaignProgresses,
BattleTest
GetUserSuperCampaignProgresses
}

def start_link() do
Expand Down Expand Up @@ -273,16 +272,6 @@ defmodule Gateway.SocketTester do
})}
)

def battle_test(pid, user_id),
do:
WebSockex.send_frame(
pid,
{:binary,
WebSocketRequest.encode(%WebSocketRequest{
request_type: {:battle_test, %BattleTest{user_id: user_id}}
})}
)

def handle_frame({:binary, message}, _state) do
message = WebSocketResponse.decode(message)
message |> inspect(pretty: true) |> Logger.info()
Expand Down
Loading

0 comments on commit 037665e

Please sign in to comment.