From 6d10ab47421852f2375520a078754492cc890263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Sanchez?= Date: Mon, 4 Nov 2024 15:12:05 -0300 Subject: [PATCH] Add shinko (#973) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Apply skill effects to target entities when applying damage to them * Add shinko to the characters list * Add shinko skills and params in seeds * Add underscore to discard unused variable in seeds * Fix Shinko ulti params and make his pool effects affect enemies even when they escape it * Make projectiles able to apply an effect on enemies and fix a function taking wrong variable (old effect) * Fix Shinko basic attack params in seeds * Add new boolean to effects to differentiate effects inside pool only vs effects still valid outside them * Fix credo * Fix credo moving code to new function * feat: add checkbox (#975) --------- Co-authored-by: Tomás Villegas <67444519+tvillegas98@users.noreply.github.com> --- apps/arena/lib/arena/game/effect.ex | 48 +++-- apps/arena/lib/arena/game/skill.ex | 29 ++- .../lib/arena_load_test/socket_handler.ex | 2 +- .../components/custom_components.ex | 1 + .../lib/game_backend/curse_of_mirra/effect.ex | 4 + .../matches/arena_match_result.ex | 4 +- .../controllers/page_html/home.html.heex | 2 +- priv/repo/seeds.exs | 174 +++++++++++++++++- 8 files changed, 240 insertions(+), 24 deletions(-) diff --git a/apps/arena/lib/arena/game/effect.ex b/apps/arena/lib/arena/game/effect.ex index 7e005212b..1898ac253 100644 --- a/apps/arena/lib/arena/game/effect.ex +++ b/apps/arena/lib/arena/game/effect.ex @@ -50,12 +50,14 @@ defmodule Arena.Game.Effect do cond do Map.has_key?(game_state.players, entity_id) -> update_in(game_state, [:players, entity_id, :aditional_info, :effects], fn current_effects -> - Enum.reject(current_effects, fn effect -> effect.owner_id == owner_id end) + # Here we remove all effects from owner if they only apply there (pools for now). + Enum.reject(current_effects, fn effect -> effect.owner_id == owner_id and effect.disabled_outside_pool end) end) Map.has_key?(game_state.crates, entity_id) -> update_in(game_state, [:crates, entity_id, :aditional_info, :effects], fn current_effects -> - Enum.reject(current_effects, fn effect -> effect.owner_id == owner_id end) + # Here we remove all effects from owner if they only apply there (pools for now). + Enum.reject(current_effects, fn effect -> effect.owner_id == owner_id and effect.disabled_outside_pool end) end) true -> @@ -177,7 +179,7 @@ defmodule Arena.Game.Effect do update_in(player, [:aditional_info, :effects], fn effects -> Enum.map(effects, fn current_effect -> if current_effect.id == effect.id do - update_effect_mechanic_value_in_effect(effect, mechanic, :last_application_time, now) + update_effect_mechanic_value_in_effect(current_effect, mechanic, :last_application_time, now) else current_effect end @@ -229,20 +231,19 @@ defmodule Arena.Game.Effect do end defp do_effect_mechanics(game_state, entity, effect, %{name: "damage"} = damage_params) do - # TODO not all effects may come from pools entities, maybe we should update this when we implement other skills that - # applies this effect - Map.get(game_state.pools, effect.owner_id) + Map.get(effect, :player_owner_id, get_entity_player_owner_id(game_state, effect.owner_id)) |> case do nil -> entity - pool -> - pool_owner = Map.get(game_state.players, pool.aditional_info.owner_id) - real_damage = Player.calculate_real_damage(pool_owner, damage_params.damage) + owner_id -> + owner = Map.get(game_state.players, owner_id) + real_damage = Player.calculate_real_damage(owner, damage_params.damage) - send(self(), {:damage_done, pool_owner.id, real_damage}) + send(self(), {:damage_done, owner_id, real_damage}) - Entities.take_damage(entity, real_damage, pool_owner.id) + Entities.take_damage(entity, real_damage, owner_id) + |> add_player_owner_of_effect_to_entity(effect.id, owner_id) end end @@ -279,6 +280,31 @@ defmodule Arena.Game.Effect do entity end + defp get_entity_player_owner_id(game_state, owner_id) do + # Append here entities that apply effects. For now are pools and projectiles. + damage_entity = + Map.merge(game_state.projectiles, game_state.pools) + |> Map.get(owner_id) + + if is_nil(damage_entity) do + nil + else + damage_entity.aditional_info.owner_id + end + end + + defp add_player_owner_of_effect_to_entity(entity, effect_id, owner_id) do + update_in(entity, [:aditional_info, :effects], fn effects -> + Enum.map(effects, fn current_effect -> + if current_effect.id == effect_id do + Map.put(current_effect, :player_owner_id, owner_id) + else + current_effect + end + end) + end) + end + defp add_effect_to_entity(game_state, entity, effect, owner_id, start_action_removal_in_ms) do now = System.monotonic_time(:millisecond) diff --git a/apps/arena/lib/arena/game/skill.ex b/apps/arena/lib/arena/game/skill.ex index a49d2331b..984907ec8 100644 --- a/apps/arena/lib/arena/game/skill.ex +++ b/apps/arena/lib/arena/game/skill.ex @@ -27,7 +27,13 @@ defmodule Arena.Game.Skill do entity_player_owner = get_entity_player_owner(game_state, entity) - deal_damage_to_game_entities(game_state, entity_player_owner, circular_damage_area, circle_hit.damage) + apply_damage_and_effects_to_entities( + game_state, + entity_player_owner, + circular_damage_area, + circle_hit.damage, + circle_hit.effect + ) |> maybe_move_player(entity, circle_hit[:move_by]) end @@ -48,7 +54,7 @@ defmodule Arena.Game.Skill do cone_area = Entities.make_polygon(entity.id, triangle_points) entity_player_owner = get_entity_player_owner(game_state, entity) - deal_damage_to_game_entities(game_state, entity_player_owner, cone_area, cone_hit.damage) + apply_damage_and_effects_to_entities(game_state, entity_player_owner, cone_area, cone_hit.damage) |> maybe_move_player(entity, cone_hit[:move_by]) end @@ -318,7 +324,7 @@ defmodule Arena.Game.Skill do entity_player_owner = get_entity_player_owner(game_state, entity) - deal_damage_to_game_entities(game_state, entity_player_owner, polygon_damage_area, polygon_hit.damage) + apply_damage_and_effects_to_entities(game_state, entity_player_owner, polygon_damage_area, polygon_hit.damage) end def handle_skill_effects(game_state, player, effect, execution_duration_ms) do @@ -436,14 +442,27 @@ defmodule Arena.Game.Skill do direction end - defp deal_damage_to_game_entities(game_state, player, skill_entity, damage) do + defp apply_damage_and_effects_to_entities(game_state, player, skill_entity, damage, effect \\ nil) do # Players alive_players = Player.alive_players(game_state.players) |> Map.filter(fn {_, alive_player} -> alive_player.id != player.id end) + collided_players = Physics.check_collisions(skill_entity, alive_players) + + # Apply effects to players + game_state = + if is_nil(effect) do + game_state + else + Enum.reduce(collided_players, game_state, fn collided_player_id, game_state -> + collided_player = Map.get(game_state.players, collided_player_id) + Effect.put_effect_to_entity(game_state, collided_player, skill_entity.id, effect) + end) + end + players = - Physics.check_collisions(skill_entity, alive_players) + collided_players |> Enum.reduce(game_state.players, fn player_id, players_acc -> real_damage = Player.calculate_real_damage(player, damage) diff --git a/apps/arena_load_test/lib/arena_load_test/socket_handler.ex b/apps/arena_load_test/lib/arena_load_test/socket_handler.ex index 5d2ac34cd..2908df784 100644 --- a/apps/arena_load_test/lib/arena_load_test/socket_handler.ex +++ b/apps/arena_load_test/lib/arena_load_test/socket_handler.ex @@ -71,7 +71,7 @@ defmodule ArenaLoadTest.SocketHandler do # This is enough for now. Will request bots from the bots app in future iterations. # https://github.com/lambdaclass/mirra_backend/issues/410 defp get_random_active_character() do - ["muflus", "h4ck", "uma", "valtimer", "kenzu", "otix"] + ["muflus", "h4ck", "uma", "valtimer", "kenzu", "otix", "shinko"] |> Enum.random() end diff --git a/apps/configurator/lib/configurator_web/components/custom_components.ex b/apps/configurator/lib/configurator_web/components/custom_components.ex index 1cb5784e5..d2c2062a5 100644 --- a/apps/configurator/lib/configurator_web/components/custom_components.ex +++ b/apps/configurator/lib/configurator_web/components/custom_components.ex @@ -59,6 +59,7 @@ defmodule ConfiguratorWeb.CustomComponents do <.input field={effect_f[:duration_ms]} type="number" label="Effect duration" /> <.input field={effect_f[:remove_on_action]} type="checkbox" label="Remove effect on action" /> <.input field={effect_f[:one_time_application]} type="checkbox" label="Apply effect once" /> + <.input field={effect_f[:disabled_outside_pool]} type="checkbox" label="Disabled outside pool" /> <.input field={effect_f[:allow_multiple_effects]} type="checkbox" label="Allow more that one effect instance" /> <.input field={effect_f[:consume_projectile]} type="checkbox" label="Consume projectile" /> <.inputs_for :let={mechanics_form} field={effect_f[:effect_mechanics]}> diff --git a/apps/game_backend/lib/game_backend/curse_of_mirra/effect.ex b/apps/game_backend/lib/game_backend/curse_of_mirra/effect.ex index 151cc58ad..fb41ad288 100644 --- a/apps/game_backend/lib/game_backend/curse_of_mirra/effect.ex +++ b/apps/game_backend/lib/game_backend/curse_of_mirra/effect.ex @@ -13,6 +13,7 @@ defmodule GameBackend.CurseOfMirra.Effect do :allow_multiple_effects, :consume_projectile, :effect_mechanics, + :disabled_outside_pool, :name ]} embedded_schema do @@ -22,6 +23,7 @@ defmodule GameBackend.CurseOfMirra.Effect do field(:one_time_application, :boolean) field(:allow_multiple_effects, :boolean) field(:consume_projectile, :boolean) + field(:disabled_outside_pool, :boolean) embeds_many(:effect_mechanics, EffectMechanic) end @@ -33,12 +35,14 @@ defmodule GameBackend.CurseOfMirra.Effect do :one_time_application, :allow_multiple_effects, :consume_projectile, + :disabled_outside_pool, :name ]) |> validate_required([ :remove_on_action, :one_time_application, :allow_multiple_effects, + :disabled_outside_pool, :name ]) |> cast_embed(:effect_mechanics) diff --git a/apps/game_backend/lib/game_backend/matches/arena_match_result.ex b/apps/game_backend/lib/game_backend/matches/arena_match_result.ex index b272304c2..605680bab 100644 --- a/apps/game_backend/lib/game_backend/matches/arena_match_result.ex +++ b/apps/game_backend/lib/game_backend/matches/arena_match_result.ex @@ -54,8 +54,8 @@ defmodule GameBackend.Matches.ArenaMatchResult do |> 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", "valtimer", "kenzu", "otix"]) - |> validate_inclusion(:killed_by, ["h4ck", "muflus", "uma", "valtimer", "kenzu", "otix", "zone"]) + |> validate_inclusion(:character, ["h4ck", "muflus", "uma", "valtimer", "kenzu", "otix", "shinko"]) + |> validate_inclusion(:killed_by, ["h4ck", "muflus", "uma", "valtimer", "kenzu", "otix", "shinko", "zone"]) |> foreign_key_constraint(:user_id) end end diff --git a/apps/game_client/lib/game_client_web/controllers/page_html/home.html.heex b/apps/game_client/lib/game_client_web/controllers/page_html/home.html.heex index 254f84ca3..476a5dc66 100644 --- a/apps/game_client/lib/game_client_web/controllers/page_html/home.html.heex +++ b/apps/game_client/lib/game_client_web/controllers/page_html/home.html.heex @@ -1,6 +1,6 @@ <%= if assigns[:current_user_id] do %> <.form :let={f} for={%{}} action={~p"/"}> - <.input field={f[:character]} name="character" label="Select a Character" type="select" options={["muflus", "h4ck", "uma", "valtimer", "kenzu", "otix"]} value=""/> + <.input field={f[:character]} name="character" label="Select a Character" type="select" options={["muflus", "h4ck", "uma", "valtimer", "kenzu", "otix", "shinko"]} value=""/> <.input field={f[:user_id]} name="user_id" type="hidden" value={assigns[:user_id]}/> <.button type="submit" name="game_mode" value="battle-royale">Play Battle Royal <.button type="submit" name="game_mode" value="pair">Play Pair diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index 3c4cbb686..c43484839 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -240,6 +240,7 @@ singularity_effect = %{ remove_on_action: false, one_time_application: true, allow_multiple_effects: true, + disabled_outside_pool: true, effect_mechanics: [ %{ name: "pull", @@ -262,6 +263,7 @@ denial_of_service = remove_on_action: false, one_time_application: true, allow_multiple_effects: true, + disabled_outside_pool: true, effect_mechanics: [ %{ name: "damage", @@ -279,6 +281,7 @@ invisible_effect = remove_on_action: true, one_time_application: false, allow_multiple_effects: true, + disabled_outside_pool: true, effect_mechanics: [ %{ name: "invisible", @@ -301,6 +304,7 @@ whirlwind_effect = remove_on_action: false, one_time_application: false, allow_multiple_effects: true, + disabled_outside_pool: true, effect_mechanics: [ %{ name: "defense_change", @@ -318,6 +322,7 @@ buff_singularity_effect = one_time_application: true, consume_projectile: true, allow_multiple_effects: true, + disabled_outside_pool: true, effect_mechanics: [ %{ name: "buff_pool", @@ -334,6 +339,7 @@ inferno_effect = %{ remove_on_action: false, one_time_application: true, allow_multiple_effects: true, + disabled_outside_pool: true, effect_mechanics: [ %{ name: "speed_boost", @@ -350,6 +356,46 @@ inferno_effect = %{ ] } +toxic_onion_effect = %{ + name: "toxic_onion", + remove_on_action: false, + duration_ms: 3000, + one_time_application: false, + allow_multiple_effects: true, + disabled_outside_pool: false, + effect_mechanics: [ + %{ + name: "damage", + damage: 20, + effect_delay_ms: 1000, + execute_multiple_times: true + } + ] +} + +peb_effect = %{ + name: "putrid_elixir_bomb", + remove_on_action: false, + duration_ms: 4000, + one_time_application: true, + allow_multiple_effects: true, + disabled_outside_pool: false, + effect_mechanics: [ + %{ + name: "damage", + damage: 10, + effect_delay_ms: 250, + execute_multiple_times: true + }, + %{ + name: "damage", + damage: 50, + effect_delay_ms: 0, + execute_multiple_times: false + } + ] +} + ## Mechanics multi_shoot = %{ "type" => "multi_shoot", @@ -432,6 +478,47 @@ inferno = %{ "effect" => inferno_effect } +toxic_onion_explosion = %{ + "name" => "toxic_onion_explosion", + "type" => "circle_hit", + "damage" => 58, + "range" => 250.0, + "offset" => 0, + "effect" => toxic_onion_effect +} + +toxic_onion = %{ + "type" => "simple_shoot", + "speed" => 1.8, + "duration_ms" => 0, + "remove_on_collision" => false, + "projectile_offset" => 0, + "radius" => 250.0, + "damage" => 0, + "range" => 700, + "on_explode_mechanics" => [ + toxic_onion_explosion + ] +} + +putrid_elixir_bomb = %{ + "name" => "putrid_elixir_bomb", + "type" => "spawn_pool", + "activation_delay" => 250, + "duration_ms" => 8000, + "radius" => 400.0, + "range" => 0.0, + "shape" => "circle", + "vertices" => [], + "effect" => peb_effect +} + +spore_dash = %{ + "type" => "dash", + "speed" => 4.0, + "duration_ms" => 250 +} + otix_carbonthrow_mechanic = %{ "type" => "simple_shoot", "speed" => 1.8, @@ -857,6 +944,54 @@ skills = [ "mechanics" => [ inferno ] + }, + %{ + "name" => "shinko_toxic_onion", + "type" => "basic", + "cooldown_mechanism" => "stamina", + "execution_duration_ms" => 450, + "activation_delay_ms" => 150, + "is_passive" => false, + "autoaim" => true, + "max_autoaim_range" => 1400, + "stamina_cost" => 1, + "can_pick_destination" => true, + "block_movement" => true, + "mechanics" => [ + toxic_onion + ] + }, + %{ + "name" => "shinko_spore_dash", + "type" => "dash", + "cooldown_mechanism" => "time", + "cooldown_ms" => 5500, + "execution_duration_ms" => 250, + "activation_delay_ms" => 0, + "is_passive" => false, + "autoaim" => false, + "max_autoaim_range" => 0, + "can_pick_destination" => false, + "block_movement" => true, + "mechanics" => [ + spore_dash + ] + }, + %{ + "name" => "shinko_PEB", + "type" => "ultimate", + "cooldown_mechanism" => "time", + "cooldown_ms" => 10000, + "execution_duration_ms" => 1000, + "activation_delay_ms" => 0, + "is_passive" => false, + "autoaim" => false, + "max_autoaim_range" => 0, + "can_pick_destination" => false, + "block_movement" => true, + "mechanics" => [ + putrid_elixir_bomb + ] } ] @@ -972,7 +1107,7 @@ valtimer_params = %{ kenzu_params = %{ name: "kenzu", - active: false, + active: true, base_speed: 1, base_size: 100.0, base_health: 400, @@ -994,7 +1129,7 @@ kenzu_params = %{ otix_params = %{ name: "otix", - active: false, + active: true, base_speed: 0.68, base_size: 100.0, base_health: 400, @@ -1009,8 +1144,33 @@ otix_params = %{ version_id: version.id } +shinko_params = %{ + name: "shinko", + active: true, + base_speed: 0.68, + base_size: 100.0, + base_health: 400, + base_stamina: 3, + stamina_interval: 2000, + max_inventory_size: 1, + natural_healing_interval: 1000, + natural_healing_damage_interval: 3500, + basic_skill_id: skills["shinko_toxic_onion"], + ultimate_skill_id: skills["shinko_PEB"], + dash_skill_id: skills["shinko_spore_dash"], + version_id: version.id +} + # Insert characters -[muflus_params, h4ck_params, uma_params, valtimer_params, kenzu_params, otix_params] +[ + muflus_params, + h4ck_params, + uma_params, + valtimer_params, + kenzu_params, + otix_params, + shinko_params +] |> Enum.each(fn char_params -> Map.put(char_params, :game_id, curse_of_mirra_id) |> Map.put(:faction, "none") @@ -1069,6 +1229,7 @@ golden_clock_effect = %{ remove_on_action: false, one_time_application: true, allow_multiple_effects: true, + disabled_outside_pool: false, effect_mechanics: [ %{ name: "reduce_stamina_interval", @@ -1114,6 +1275,7 @@ magic_boots_effect = remove_on_action: false, one_time_application: true, allow_multiple_effects: true, + disabled_outside_pool: true, effect_mechanics: [ %{ name: "speed_boost", @@ -1143,6 +1305,7 @@ mirra_blessing_effect = remove_on_action: false, one_time_application: true, allow_multiple_effects: true, + disabled_outside_pool: true, effect_mechanics: [ %{ name: "damage_immunity", @@ -1171,6 +1334,7 @@ giant_effect = remove_on_action: false, one_time_application: true, allow_multiple_effects: true, + disabled_outside_pool: true, effect_mechanics: [ %{ name: "modify_radius", @@ -1215,6 +1379,7 @@ polymorph_effect = %{ name: "polymorph_effect", duration_ms: 9000, remove_on_action: true, + disabled_outside_pool: false, one_time_application: true, allow_multiple_effects: true, effect_mechanics: [] @@ -1265,12 +1430,13 @@ fake_item_params = %{ {:ok, _fake_item} = GameBackend.Items.create_consumable_item(fake_item_params) -slow_field_effect = %{ +_slow_field_effect = %{ name: "slow_field_effect", duration_ms: 9000, remove_on_action: true, one_time_application: true, allow_multiple_effects: true, + disabled_outside_pool: false, effect_mechanics: [ %{ name: "speed_boost",