Skip to content

Commit

Permalink
Merge branch 'main' into campaign-rewards
Browse files Browse the repository at this point in the history
  • Loading branch information
lotuuu committed Mar 8, 2024
2 parents 7887271 + f6c5eb9 commit 8939a59
Show file tree
Hide file tree
Showing 13 changed files with 258 additions and 17 deletions.
4 changes: 2 additions & 2 deletions apps/arena/priv/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -277,8 +277,8 @@
{
"name": "antimatter",
"cooldown_ms": 2000,
"execution_duration_ms": 300,
"activation_delay_ms": 0,
"execution_duration_ms": 800,
"activation_delay_ms": 500,
"is_passive": false,
"autoaim": true,
"stamina_cost": 1,
Expand Down
164 changes: 164 additions & 0 deletions apps/champions/lib/champions/units.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ defmodule Champions.Units do
alias Ecto.Multi
alias GameBackend.Transaction
alias GameBackend.Units
alias GameBackend.Units.Characters.Character
alias GameBackend.Units.Unit
alias GameBackend.Users.Currencies
alias GameBackend.Users.Currencies.CurrencyCost

Expand All @@ -23,6 +25,10 @@ defmodule Champions.Units do
@illumination3 8
@awakened 9

@epic 3
@rare 2
@common 1

def get_rank(:star1), do: @star1
def get_rank(:star2), do: @star2
def get_rank(:star3), do: @star3
Expand All @@ -33,6 +39,10 @@ defmodule Champions.Units do
def get_rank(:illumination3), do: @illumination3
def get_rank(:awakened), do: @awakened

def get_quality(:epic), do: @epic
def get_quality(:rare), do: @rare
def get_quality(:common), do: @common

@doc """
Marks a unit as selected for a user. Units cannot be selected to the same slot.
Expand Down Expand Up @@ -207,6 +217,160 @@ defmodule Champions.Units do
%CurrencyCost{currency_id: Currencies.get_currency_by_name!("Gems").id, amount: 50}
]

##########
# Fusion #
##########

@doc """
Consume a list of units that meet specific rank and character requirements based on the target
unit's rank in order to increase it.
Returns `{:ok, unit}` or `{:error, reason}`.
"""
def fuse(user_id, unit_id, consumed_units_ids) do
with {:unit, {:ok, unit}} <- {:unit, Units.get_unit(unit_id)},
{:unit_owned, true} <- {:unit_owned, unit.user_id == user_id},
{:can_rank_up, true} <- {:can_rank_up, can_rank_up(unit)},
{:unit_not_in_consumed_units, true} <-
{:unit_not_in_consumed_units, unit_id not in consumed_units_ids},
consumed_units <- Units.get_units_by_ids(consumed_units_ids),
{:consumed_units_owned, true} <-
{:consumed_units_owned, Enum.all?(consumed_units, &(&1.user_id == user_id))},
{:consumed_units_count, true} <-
{:consumed_units_count, Enum.count(consumed_units) == Enum.count(consumed_units_ids)},
{:consumed_units_valid, true} <-
{:consumed_units_valid, meets_fuse_requirements?(unit, consumed_units)} do
result =
Multi.new()
|> Multi.run(:unit, fn _, _ -> Units.add_rank(unit) end)
|> Multi.run(:deleted_units, fn _, _ -> delete_consumed_units(consumed_units_ids) end)
|> Transaction.run()

case result do
{:error, reason} ->
{:error, reason}

{:error, _, _, _} ->
{:error, :transaction}

{:ok, %{unit: unit}} ->
{:ok, unit}
end
else
{:unit, {:error, :not_found}} ->
{:error, :not_found}

{:unit_owned, false} ->
{:error, :not_owned}

{:can_rank_up, false} ->
{:error, :cant_rank_up}

{:unit_not_in_consumed_units, false} ->
{:error, :consumed_units_invalid}

{:consumed_units_count, false} ->
{:error, :consumed_units_not_found}

{:consumed_units_valid, false} ->
{:error, :consumed_units_invalid}
end
end

defp delete_consumed_units(unit_ids) do
{amount_deleted, _return} = Units.delete_units(unit_ids)

if Enum.count(unit_ids) == amount_deleted, do: {:ok, amount_deleted}, else: {:error, "failed"}
end

defp meets_fuse_requirements?(unit, unit_list) do
{same_character_amount, same_character_rank} = same_character_requirements(unit)
{same_faction_amount, same_faction_rank} = same_faction_requirements(unit)

with {:ok, removed_same_character} <-
remove_same_character(unit, unit_list, same_character_amount, same_character_rank),
{:ok, removed_same_faction} <-
remove_same_faction(
unit,
removed_same_character,
same_faction_amount,
same_faction_rank
) do
# If we got here with an empty list, then the units are valid
if Enum.empty?(removed_same_faction), do: true, else: false
else
:error -> false
end
end

defp same_character_requirements(%Unit{rank: @star4}), do: {2, @star4}
defp same_character_requirements(%Unit{rank: @star5}), do: {1, @star5}
defp same_character_requirements(%Unit{rank: @illumination1}), do: {1, @star5}
defp same_character_requirements(%Unit{rank: @illumination2}), do: {1, @star5}
defp same_character_requirements(%Unit{rank: @illumination3}), do: {3, @star5}

defp same_faction_requirements(%Unit{rank: @star4}), do: {4, @star4}
defp same_faction_requirements(%Unit{rank: @star5}), do: {4, @star5}
defp same_faction_requirements(%Unit{rank: @illumination1}), do: {1, @illumination1}
defp same_faction_requirements(%Unit{rank: @illumination2}), do: {2, @illumination2}
defp same_faction_requirements(%Unit{rank: @illumination3}), do: {2, @illumination2}

defp remove_same_character(unit, unit_list, amount, rank) do
Enum.reduce_while(1..amount, unit_list, fn _, list ->
same_character =
Enum.find(
list,
&(&1.character_id == unit.character_id and &1.rank == rank)
)

if is_nil(same_character) do
# Not enough of same character
{:halt, :error}
else
{:cont, List.delete(list, same_character)}
end
end)
|> case do
:error -> :error
list -> {:ok, list}
end
end

defp remove_same_faction(unit, unit_list, amount, rank) do
Enum.reduce_while(1..amount, unit_list, fn _, list ->
same_character =
Enum.find(
list,
&(&1.character.faction == unit.character.faction and &1.rank == rank)
)

if is_nil(same_character) do
# Not enough of same faction
{:halt, :error}
else
{:cont, List.delete(list, same_character)}
end
end)
|> case do
:error -> :error
list -> {:ok, list}
end
end

@doc """
Returns whether a unit can rank up, based on its current rank and its character's quality.
"""
def can_rank_up(%Unit{rank: rank, character: %Character{quality: @epic}}), do: rank < @awakened

def can_rank_up(%Unit{rank: rank, character: %Character{quality: @rare}}),
do: rank < @illumination2

def can_rank_up(_unit), do: false

##########
# Battle #
##########

@doc """
Get a unit's max health stat for battle. Buffs from items and similar belong here.
Expand Down
1 change: 1 addition & 0 deletions apps/champions/lib/champions/users.ex
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ defmodule Champions.Users do
character_id: Enum.random(characters).id,
user_id: user.id,
level: Enum.random(1..5),
rank: Champions.Units.get_rank(:star5),
tier: 1,
selected: true,
slot: index
Expand Down
29 changes: 27 additions & 2 deletions apps/game_backend/lib/game_backend/units.ex
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ defmodule GameBackend.Units do
"""
def get_units(), do: Repo.all(Unit) |> Repo.preload([:character, :user, :items])

@doc """
Gets all units from ids in a list.
"""
def get_units_by_ids(unit_ids) when is_list(unit_ids),
do: Repo.all(from(u in Unit, where: u.id in ^unit_ids, preload: [:character, :user, :items]))

@doc """
Gets all units for a user.
"""
Expand Down Expand Up @@ -130,6 +136,11 @@ defmodule GameBackend.Units do
def delete_unit(%Unit{} = unit), do: Repo.delete(unit)
def delete_unit(id), do: Repo.get(Unit, id) |> delete_unit()

@doc """
Deletes all units in a given list.
"""
def delete_units(unit_ids), do: Repo.delete_all(from(u in Unit, where: u.id in ^unit_ids))

@doc """
Sets all of the user's units' selected value to false.
"""
Expand Down Expand Up @@ -168,7 +179,7 @@ defmodule GameBackend.Units do
do: Repo.exists?(from(u in Unit, where: u.id == ^unit_id and u.user_id == ^user_id))

@doc """
Increment an unit's level (not to be confused with units' `level` association).
Increment a unit's level (not to be confused with units' `level` association).
## Examples
Expand All @@ -182,7 +193,7 @@ defmodule GameBackend.Units do
end

@doc """
Increment an unit's tier.
Increment a unit's tier.
## Examples
Expand All @@ -194,4 +205,18 @@ defmodule GameBackend.Units do
|> Unit.update_changeset(%{tier: unit.tier + tier})
|> Repo.update()
end

@doc """
Increment a unit's rank.
## Examples
iex> add_rank(%Unit{rank: 41}, 1)
{:ok, %Unit{rank: 42}}
"""
def add_rank(unit, rank \\ 1) do
unit
|> Unit.update_changeset(%{rank: unit.rank + rank})
|> Repo.update()
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ defmodule GameBackend.Units.Characters.Character do
field(:active, :boolean, default: true)
field(:name, :string)
field(:faction, :string)
field(:rarity, :string)
field(:quality, :integer)

field(:base_health, :integer)
field(:base_attack, :integer)
Expand All @@ -33,7 +33,7 @@ defmodule GameBackend.Units.Characters.Character do
:name,
:active,
:faction,
:rarity,
:quality,
:basic_skill_id,
:ultimate_skill_id,
:base_health,
Expand Down
4 changes: 2 additions & 2 deletions apps/game_backend/lib/game_backend/units/unit.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ defmodule GameBackend.Units.Unit do
:user_id,
:campaign_level_id
])
|> validate_required([:level, :tier, :selected, :character_id])
|> validate_required([:level, :selected, :character_id])
end

@doc """
Changeset for when updating a unit.
"""
def update_changeset(unit, attrs),
do: cast(unit, attrs, [:selected, :slot, :rank, :level, :tier])
do: cast(unit, attrs, [:selected, :slot, :level, :tier, :rank])
end
23 changes: 22 additions & 1 deletion apps/game_backend/lib/game_backend/users/currencies.ex
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ defmodule GameBackend.Users.Currencies do
where: uc.user_id == ^user_id and uc.currency_id == ^currency_id,
select: uc.amount
)
)
) || 0

@doc """
Adds (or substracts) the given amount of currency to a user.
Expand Down Expand Up @@ -152,4 +152,25 @@ defmodule GameBackend.Users.Currencies do
{:error, "failed"}
end
end

@doc """
Gets how much a user has of a given currency by its name.
"""
def get_amount_of_currency_by_name(user_id, currency_name) do
Repo.one(
from(uc in UserCurrency,
join: currency in assoc(uc, :currency),
where: uc.user_id == ^user_id and currency.name == ^currency_name,
select: uc.amount
)
) || 0
end

@doc """
Add amount of currency to user by its name.
"""
def add_currency_by_name!(user_id, currency_name, amount),
do:
user_id
|> add_currency(get_currency_by_name!(currency_name).id, amount)
end
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ defmodule GameBackend.Repo.Migrations.CreateInitialTables do
add :active, :boolean, null: false
add :name, :string, null: false
add :faction, :string, null: false
add :rarity, :string
add :quality, :integer
timestamps()
end

Expand Down
12 changes: 12 additions & 0 deletions apps/gateway/lib/gateway/champions_socket_handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ defmodule Gateway.ChampionsSocketHandler do
UnselectUnit,
LevelUpUnit,
TierUpUnit,
FuseUnit,
EquipItem,
UnequipItem,
GetItem,
Expand Down Expand Up @@ -122,6 +123,17 @@ defmodule Gateway.ChampionsSocketHandler do
end
end

defp handle(%FuseUnit{
user_id: user_id,
unit_id: unit_id,
consumed_units_ids: consumed_units_ids
}) do
case Units.fuse(user_id, unit_id, consumed_units_ids) do
{:ok, result} -> prepare_response(result, :unit)
{:error, reason} -> prepare_response({:error, reason}, nil)
end
end

defp handle(%EquipItem{user_id: user_id, item_id: item_id, unit_id: unit_id}),
do: Items.equip_item(user_id, item_id, unit_id) |> prepare_response(:item)

Expand Down
2 changes: 1 addition & 1 deletion apps/gateway/lib/gateway/serialization/gateway.pb.ex
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ defmodule Gateway.Serialization.Character do
field(:active, 1, type: :bool)
field(:name, 2, type: :string)
field(:faction, 3, type: :string)
field(:rarity, 4, type: :string)
field(:quality, 4, type: :uint32)
end

defmodule Gateway.Serialization.Item do
Expand Down
Loading

0 comments on commit 8939a59

Please sign in to comment.