Skip to content

Commit

Permalink
New ingame inventory (#1049)
Browse files Browse the repository at this point in the history
* feat: make the inventory be a list

* feat: modify functions to use the first items from the inventory list

* feat: add logic to wait a certain period of time to pick up an item

* feat: turn the inventory list into a map

* fix: forgot to generate protos

* feat: feat modify logic to use a dictionary instead

* fix: passing the wrong argument to the use item function

* Add inventory_full? check and default case for store_item cond

* update item pickup time

* changes:
- fix: infinite health item
- feat: decreased pick up time

* Add new pick_up_time_elapsed map to proto messages

* Add logic to handle pick-up time per player instead of sharing it

* Delete unused functions

* Upgrade arena version

---------

Co-authored-by: tvillegas98 <tvillegas@fi.uba.ar>
Co-authored-by: Manuel Camejo <mcamejo@fi.uba.ar>
  • Loading branch information
3 people authored Jan 17, 2025
1 parent 539b57a commit 55b82ea
Show file tree
Hide file tree
Showing 13 changed files with 426 additions and 241 deletions.
10 changes: 7 additions & 3 deletions apps/arena/lib/arena/entities.ex
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ defmodule Arena.Entities do
forced_movement: false,
power_ups: 0,
power_up_damage_modifier: config.game.power_up_damage_modifier,
inventory: nil,
inventory: %{},
damage_immunity: false,
pull_immunity: false,
effects: [],
Expand Down Expand Up @@ -246,7 +246,10 @@ defmodule Arena.Entities do
name: config.name,
effect: config.effect,
mechanics: config.mechanics,
pull_immunity: true
pick_up_time_elapsed: %{},
pick_up_time_initial_timestamp: %{},
pull_immunity: true,
pick_up_time: nil
}
}
end
Expand Down Expand Up @@ -483,7 +486,8 @@ defmodule Arena.Entities do
def maybe_add_custom_info(entity) when entity.category == :item do
{:item,
%Arena.Serialization.Item{
name: get_in(entity, [:aditional_info, :name])
name: get_in(entity, [:aditional_info, :name]),
pick_up_time_elapsed: get_in(entity, [:aditional_info, :pick_up_time_elapsed])
}}
end

Expand Down
1 change: 1 addition & 0 deletions apps/arena/lib/arena/game/item.ex
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ defmodule Arena.Game.Item do

def do_mechanic(game_state, entity, %{type: "heal"} = item_params) do
player = Player.add_health(entity, item_params.amount)

put_in(game_state, [:players, player.id], player)
end

Expand Down
27 changes: 22 additions & 5 deletions apps/arena/lib/arena/game/player.ex
Original file line number Diff line number Diff line change
Expand Up @@ -360,15 +360,29 @@ defmodule Arena.Game.Player do
end

def store_item(player, item) do
put_in(player, [:aditional_info, :inventory], item)
inventory = player.aditional_info.inventory

cond do
not Map.has_key?(inventory, 1) ->
put_in(player, [:aditional_info, :inventory, 1], item)

not Map.has_key?(inventory, 2) ->
put_in(player, [:aditional_info, :inventory, 2], item)

not Map.has_key?(inventory, 3) ->
put_in(player, [:aditional_info, :inventory, 3], item)

true ->
player
end
end

def inventory_full?(player) do
player.aditional_info.inventory != nil
Enum.count(player.aditional_info.inventory) == 3
end

def use_item(player, game_state) do
case player.aditional_info.inventory do
def use_item(player, item_position, game_state) do
case Map.get(player.aditional_info.inventory, item_position) do
nil ->
game_state

Expand All @@ -378,7 +392,10 @@ defmodule Arena.Game.Player do
|> maybe_update_player_item_effects_expires_at(player, item.effect)

Item.do_mechanics(game_state, player, item.mechanics)
|> put_in([:players, player.id, :aditional_info, :inventory], nil)
|> put_in(
[:players, player.id, :aditional_info, :inventory],
Map.delete(player.aditional_info.inventory, item_position)
)
end
end

Expand Down
4 changes: 2 additions & 2 deletions apps/arena/lib/arena/game_socket_handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,8 @@ defmodule Arena.GameSocketHandler do
%{action_type: {:attack, %{skill: skill, parameters: params}}, timestamp: timestamp} ->
GameUpdater.attack(state.game_pid, state.player_id, skill, params, timestamp)

%{action_type: {:use_item, _}, timestamp: timestamp} ->
GameUpdater.use_item(state.game_pid, state.player_id, timestamp)
%{action_type: {:use_item, %{item_position: item_position}}, timestamp: timestamp} ->
GameUpdater.use_item(state.game_pid, state.player_id, item_position, timestamp)

_ ->
nil
Expand Down
104 changes: 72 additions & 32 deletions apps/arena/lib/arena/game_updater.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ defmodule Arena.GameUpdater do
alias Phoenix.PubSub
alias Arena.Game.Trap

@standing_time 1900

##########################
# API
##########################
Expand All @@ -35,8 +37,8 @@ defmodule Arena.GameUpdater do
GenServer.cast(game_pid, {:attack, player_id, skill, skill_params, timestamp})
end

def use_item(game_pid, player_id, timestamp) do
GenServer.cast(game_pid, {:use_item, player_id, timestamp})
def use_item(game_pid, player_id, item_position, timestamp) do
GenServer.cast(game_pid, {:use_item, player_id, item_position, timestamp})
end

def select_bounty(game_pid, player_id, bounty_quest_id) do
Expand Down Expand Up @@ -159,10 +161,10 @@ defmodule Arena.GameUpdater do
{:noreply, %{state | game_state: game_state}}
end

def handle_cast({:use_item, player_id, _timestamp}, state) do
def handle_cast({:use_item, player_id, item_position, _timestamp}, state) do
game_state =
get_in(state, [:game_state, :players, player_id])
|> Player.use_item(state.game_state)
|> Player.use_item(item_position, state.game_state)

{:noreply, %{state | game_state: game_state}}
end
Expand Down Expand Up @@ -246,9 +248,9 @@ defmodule Arena.GameUpdater do
|> Effect.apply_effect_mechanic_to_entities()
# Players
|> move_players()
|> update_pickup_time_for_items()
|> reduce_players_cooldowns(delta_time)
|> recover_mana()
|> resolve_players_collisions_with_items()
|> resolve_projectiles_effects_on_collisions()
|> apply_zone_damage_to_players(state.game_config.game)
|> update_visible_players(state.game_config)
Expand All @@ -273,6 +275,8 @@ defmodule Arena.GameUpdater do
# Deathmatch
|> add_players_to_respawn_queue(state.game_config)
|> respawn_players(state.game_config)
# Items
|> remove_pickup_time_for_items()

{:ok, state_diff} = diff(state.last_broadcasted_game_state, game_state)

Expand Down Expand Up @@ -1208,24 +1212,6 @@ defmodule Arena.GameUpdater do
%{game_state | projectiles: moved_projectiles}
end

defp resolve_players_collisions_with_items(game_state) do
{players, items} =
Enum.reduce(game_state.players, {game_state.players, game_state.items}, fn {_player_id, player},
{players_acc, items_acc} ->
case find_collided_item(player.collides_with, items_acc) do
nil ->
{players_acc, items_acc}

item ->
process_item(player, item, players_acc, items_acc)
end
end)

game_state
|> Map.put(:players, players)
|> Map.put(:items, items)
end

# This method will decide what to do when a projectile has collided with something in the map
# - If collided with something with the same owner skip that collision
# - If collided with external wall or obstacle explode projectile
Expand Down Expand Up @@ -1691,15 +1677,6 @@ defmodule Arena.GameUpdater do
end
end

defp process_item(player, item, players_acc, items_acc) do
if Player.inventory_full?(player) do
{players_acc, items_acc}
else
player = Player.store_item(player, item.aditional_info)
{Map.put(players_acc, player.id, player), Map.delete(items_acc, item.id)}
end
end

defp random_position_in_map(object_radius, external_wall, obstacles, initial_position, available_radius) do
integer_radius = trunc(available_radius - object_radius)
x = Enum.random(-integer_radius..integer_radius) / 1.0 + initial_position.x
Expand Down Expand Up @@ -2009,6 +1986,69 @@ defmodule Arena.GameUpdater do

defp respawn_players(game_state, _game_config), do: game_state

defp update_pickup_time_for_items(game_state) do
{players, items} =
Enum.reduce(game_state.players, {game_state.players, game_state.items}, fn {player_id, player},
{players_acc, items_acc} ->
case find_collided_item(player.collides_with, items_acc) do
nil ->
{players_acc, items_acc}

item ->
now = System.monotonic_time(:millisecond)

cond do
not Map.has_key?(item.aditional_info.pick_up_time_elapsed, player_id) ->
item =
put_in(item, [:aditional_info, :pick_up_time_elapsed, player_id], 0)
|> put_in([:aditional_info, :pick_up_time_initial_timestamp, player_id], now)

{players_acc, Map.put(items_acc, item.id, item)}

item.aditional_info.pick_up_time_elapsed[player_id] < @standing_time ->
elapsed_time = min(now - item.aditional_info.pick_up_time_initial_timestamp[player_id], @standing_time)

item = put_in(item, [:aditional_info, :pick_up_time_elapsed, player_id], elapsed_time)

{players_acc, Map.put(items_acc, item.id, item)}

not Player.inventory_full?(player) ->
player = Player.store_item(player, item.aditional_info)
{Map.put(players_acc, player.id, player), Map.delete(items_acc, item.id)}

true ->
{players_acc, items_acc}
end
end
end)

game_state
|> Map.put(:players, players)
|> Map.put(:items, items)
end

defp remove_pickup_time_for_items(game_state) do
items =
Enum.reduce(game_state.items, %{}, fn {item_id, item}, acc ->
players_colliding = Physics.check_collisions(item, game_state.players)

item =
put_in(
item,
[:aditional_info, :pick_up_time_elapsed],
Map.take(item.aditional_info.pick_up_time_elapsed, players_colliding)
)
|> put_in(
[:aditional_info, :pick_up_time_initial_timestamp],
Map.take(item.aditional_info.pick_up_time_initial_timestamp, players_colliding)
)

Map.put(acc, item_id, item)
end)

put_in(game_state, [:items], items)
end

##########################
# End Helpers
##########################
Expand Down
51 changes: 38 additions & 13 deletions apps/arena/lib/arena/serialization/messages.pb.ex
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,15 @@ defmodule Arena.Serialization.Player.CooldownsEntry do
field(:value, 2, type: :uint64)
end

defmodule Arena.Serialization.Player.InventoryEntry do
@moduledoc false

use Protobuf, map: true, syntax: :proto3, protoc_gen_elixir_version: "0.13.0"

field(:key, 1, type: :uint32)
field(:value, 2, type: Arena.Serialization.Item)
end

defmodule Arena.Serialization.Player do
@moduledoc false

Expand All @@ -551,23 +560,23 @@ defmodule Arena.Serialization.Player do
field(:character_name, 8, proto3_optional: true, type: :string, json_name: "characterName")
field(:power_ups, 9, proto3_optional: true, type: :uint64, json_name: "powerUps")
field(:effects, 10, repeated: true, type: Arena.Serialization.Effect)
field(:inventory, 11, type: Arena.Serialization.Item)
field(:cooldowns, 12, repeated: true, type: Arena.Serialization.Player.CooldownsEntry, map: true)
field(:visible_players, 13, repeated: true, type: :uint64, json_name: "visiblePlayers")
field(:on_bush, 14, proto3_optional: true, type: :bool, json_name: "onBush")
field(:forced_movement, 15, proto3_optional: true, type: :bool, json_name: "forcedMovement")
field(:bounty_completed, 16, proto3_optional: true, type: :bool, json_name: "bountyCompleted")
field(:mana, 17, proto3_optional: true, type: :uint64)

field(:current_basic_animation, 18,
field(:cooldowns, 11, repeated: true, type: Arena.Serialization.Player.CooldownsEntry, map: true)
field(:visible_players, 12, repeated: true, type: :uint64, json_name: "visiblePlayers")
field(:on_bush, 13, proto3_optional: true, type: :bool, json_name: "onBush")
field(:forced_movement, 14, proto3_optional: true, type: :bool, json_name: "forcedMovement")
field(:bounty_completed, 15, proto3_optional: true, type: :bool, json_name: "bountyCompleted")
field(:mana, 16, proto3_optional: true, type: :uint64)

field(:current_basic_animation, 17,
proto3_optional: true,
type: :uint32,
json_name: "currentBasicAnimation"
)

field(:match_position, 19, proto3_optional: true, type: :uint32, json_name: "matchPosition")
field(:team, 20, proto3_optional: true, type: :uint32)
field(:max_health, 21, proto3_optional: true, type: :uint64, json_name: "maxHealth")
field(:match_position, 18, proto3_optional: true, type: :uint32, json_name: "matchPosition")
field(:team, 19, proto3_optional: true, type: :uint32)
field(:max_health, 20, proto3_optional: true, type: :uint64, json_name: "maxHealth")
field(:inventory, 21, repeated: true, type: Arena.Serialization.Player.InventoryEntry, map: true)
end

defmodule Arena.Serialization.Effect do
Expand All @@ -580,12 +589,28 @@ defmodule Arena.Serialization.Effect do
field(:id, 3, type: :uint64)
end

defmodule Arena.Serialization.Item.PickUpTimeElapsedEntry do
@moduledoc false

use Protobuf, map: true, syntax: :proto3, protoc_gen_elixir_version: "0.13.0"

field(:key, 1, type: :uint32)
field(:value, 2, type: :uint32)
end

defmodule Arena.Serialization.Item do
@moduledoc false

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

field(:name, 2, proto3_optional: true, type: :string)

field(:pick_up_time_elapsed, 3,
repeated: true,
type: Arena.Serialization.Item.PickUpTimeElapsedEntry,
json_name: "pickUpTimeElapsed",
map: true
)
end

defmodule Arena.Serialization.Projectile do
Expand Down Expand Up @@ -703,7 +728,7 @@ defmodule Arena.Serialization.UseItem do

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

field(:item, 1, type: :uint64)
field(:item_position, 1, type: :uint64, json_name: "itemPosition")
end

defmodule Arena.Serialization.SelectBounty do
Expand Down
2 changes: 1 addition & 1 deletion apps/arena/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule Arena.MixProject do
def project do
[
app: :arena,
version: "0.14.1",
version: "0.15.1",
build_path: "../../_build",
config_path: "../../config/config.exs",
deps_path: "../../deps",
Expand Down
Loading

0 comments on commit 55b82ea

Please sign in to comment.