diff --git a/apps/arena/lib/arena/game_socket_handler.ex b/apps/arena/lib/arena/game_socket_handler.ex index 847773f84..f35844d23 100644 --- a/apps/arena/lib/arena/game_socket_handler.ex +++ b/apps/arena/lib/arena/game_socket_handler.ex @@ -206,7 +206,9 @@ defmodule Arena.GameSocketHandler do targetting_angle: mechanic[:angle], targetting_range: mechanic[:range], targetting_offset: mechanic[:offset] || mechanic[:projectile_offset], - is_combo: skill.is_combo? + is_combo: skill.is_combo?, + attack_type: cast_attack_type(skill.attack_type), + skill_type: cast_skill_type(skill.type) } {key, Map.merge(skill, extra_params)} @@ -272,4 +274,11 @@ defmodule Arena.GameSocketHandler do # This is to override jwt validation for human clients in loadtests. defp maybe_override_jwt(_client_id, "true", req), do: :cowboy_req.binding(:client_id, req) defp maybe_override_jwt(client_id, _override_jwt?, _req), do: client_id + + defp cast_attack_type("melee"), do: :MELEE + defp cast_attack_type("ranged"), do: :RANGED + + defp cast_skill_type("basic"), do: :BASIC + defp cast_skill_type("ultimate"), do: :ULTIMATE + defp cast_skill_type("dash"), do: :DASH end diff --git a/apps/arena/lib/arena/serialization/messages.pb.ex b/apps/arena/lib/arena/serialization/messages.pb.ex index 3ca613394..62c335161 100644 --- a/apps/arena/lib/arena/serialization/messages.pb.ex +++ b/apps/arena/lib/arena/serialization/messages.pb.ex @@ -9,6 +9,25 @@ defmodule Arena.Serialization.GameMode do field(:QUICK_GAME, 3) end +defmodule Arena.Serialization.AttackType do + @moduledoc false + + use Protobuf, enum: true, syntax: :proto3, protoc_gen_elixir_version: "0.13.0" + + field(:MELEE, 0) + field(:RANGED, 1) +end + +defmodule Arena.Serialization.SkillType do + @moduledoc false + + use Protobuf, enum: true, syntax: :proto3, protoc_gen_elixir_version: "0.13.0" + + field(:BASIC, 0) + field(:ULTIMATE, 1) + field(:DASH, 2) +end + defmodule Arena.Serialization.GameStatus do @moduledoc false @@ -319,6 +338,14 @@ defmodule Arena.Serialization.ConfigSkill do field(:targetting_offset, 8, type: :float, json_name: "targettingOffset") field(:mana_cost, 9, type: :uint64, json_name: "manaCost") field(:is_combo, 10, type: :bool, json_name: "isCombo") + + field(:attack_type, 11, + type: Arena.Serialization.AttackType, + json_name: "attackType", + enum: true + ) + + field(:skill_type, 12, type: Arena.Serialization.SkillType, json_name: "skillType", enum: true) end defmodule Arena.Serialization.GameState.PlayersEntry do diff --git a/apps/arena_load_test/lib/arena_load_test/serialization/messages.pb.ex b/apps/arena_load_test/lib/arena_load_test/serialization/messages.pb.ex index 170f01513..eae79e1ad 100644 --- a/apps/arena_load_test/lib/arena_load_test/serialization/messages.pb.ex +++ b/apps/arena_load_test/lib/arena_load_test/serialization/messages.pb.ex @@ -9,6 +9,25 @@ defmodule ArenaLoadTest.Serialization.GameMode do field(:QUICK_GAME, 3) end +defmodule ArenaLoadTest.Serialization.AttackType do + @moduledoc false + + use Protobuf, enum: true, syntax: :proto3, protoc_gen_elixir_version: "0.13.0" + + field(:MELEE, 0) + field(:RANGED, 1) +end + +defmodule ArenaLoadTest.Serialization.SkillType do + @moduledoc false + + use Protobuf, enum: true, syntax: :proto3, protoc_gen_elixir_version: "0.13.0" + + field(:BASIC, 0) + field(:ULTIMATE, 1) + field(:DASH, 2) +end + defmodule ArenaLoadTest.Serialization.GameStatus do @moduledoc false @@ -336,6 +355,18 @@ defmodule ArenaLoadTest.Serialization.ConfigSkill do field(:targetting_offset, 8, type: :float, json_name: "targettingOffset") field(:mana_cost, 9, type: :uint64, json_name: "manaCost") field(:is_combo, 10, type: :bool, json_name: "isCombo") + + field(:attack_type, 11, + type: ArenaLoadTest.Serialization.AttackType, + json_name: "attackType", + enum: true + ) + + field(:skill_type, 12, + type: ArenaLoadTest.Serialization.SkillType, + json_name: "skillType", + enum: true + ) end defmodule ArenaLoadTest.Serialization.GameState.PlayersEntry do diff --git a/apps/bot_manager/lib/bot_state_machine.ex b/apps/bot_manager/lib/bot_state_machine.ex index 75460bca1..c80c965a3 100644 --- a/apps/bot_manager/lib/bot_state_machine.ex +++ b/apps/bot_manager/lib/bot_state_machine.ex @@ -11,7 +11,10 @@ defmodule BotManager.BotStateMachine do @skill_1_key "1" @skill_2_key "2" @dash_skill_key "3" - @vision_range 1200 + + @run_away_vision 400 + @ranged_vision 1200 + @melee_vision 300 @min_distance_to_switch 10 def decide_action(%{bots_enabled?: false, bot_state_machine: bot_state_machine}) do @@ -25,7 +28,8 @@ defmodule BotManager.BotStateMachine do game_state: game_state, bot_player: bot_player, bot_state_machine: bot_state_machine, - attack_blocked: attack_blocked + attack_blocked: attack_blocked, + config: config }) do bot_state_machine = if is_nil(bot_state_machine.previous_position) do @@ -55,7 +59,8 @@ defmodule BotManager.BotStateMachine do bot_player: bot_player, bot_state_machine: bot_state_machine, game_state: game_state, - attack_blocked: attack_blocked + attack_blocked: attack_blocked, + config: config }) :running_away -> @@ -76,44 +81,65 @@ defmodule BotManager.BotStateMachine do def use_skill(%{ game_state: game_state, bot_player: bot_player, - bot_state_machine: bot_state_machine + bot_state_machine: bot_state_machine, + config: config }) do - players_with_distances = map_directions_to_players(game_state, bot_player, @vision_range) - - if Enum.empty?(players_with_distances) do - move(bot_player, bot_state_machine) - else - cond do - bot_state_machine.progress_for_ultimate_skill >= bot_state_machine.cap_for_ultimate_skill -> - bot_state_machine = - Map.put( - bot_state_machine, - :progress_for_ultimate_skill, - bot_state_machine.progress_for_ultimate_skill - bot_state_machine.cap_for_ultimate_skill - ) - |> Map.put(:state, :attacking) - + {:player, aditional_info} = bot_player.aditional_info + + skills = BotManager.Utils.list_character_skills_from_config(aditional_info.character_name, config.characters) + + cond do + bot_state_machine.progress_for_ultimate_skill >= bot_state_machine.cap_for_ultimate_skill -> + bot_state_machine = + Map.put( + bot_state_machine, + :progress_for_ultimate_skill, + bot_state_machine.progress_for_ultimate_skill - bot_state_machine.cap_for_ultimate_skill + ) + |> Map.put(:state, :attacking) + + players_with_distances = + map_directions_to_players( + game_state, + bot_player, + if(skills.ultimate.attack_type == :MELEE, do: @melee_vision, else: @ranged_vision) + ) + + if Enum.empty?(players_with_distances) do + move(bot_player, bot_state_machine) + else direction = maybe_aim_to_a_player(bot_player, players_with_distances) %{action: {:use_skill, @skill_2_key, direction}, bot_state_machine: bot_state_machine} - - bot_state_machine.progress_for_basic_skill >= bot_state_machine.cap_for_basic_skill -> - bot_state_machine = - Map.put( - bot_state_machine, - :progress_for_basic_skill, - bot_state_machine.progress_for_basic_skill - bot_state_machine.cap_for_basic_skill - ) - |> Map.put(:progress_for_ultimate_skill, bot_state_machine.progress_for_ultimate_skill + 1) - |> Map.put(:state, :attacking) - + end + + bot_state_machine.progress_for_basic_skill >= bot_state_machine.cap_for_basic_skill -> + bot_state_machine = + Map.put( + bot_state_machine, + :progress_for_basic_skill, + bot_state_machine.progress_for_basic_skill - bot_state_machine.cap_for_basic_skill + ) + |> Map.put(:progress_for_ultimate_skill, bot_state_machine.progress_for_ultimate_skill + 1) + |> Map.put(:state, :attacking) + + players_with_distances = + map_directions_to_players( + game_state, + bot_player, + if(skills.basic.attack_type == :MELEE, do: @melee_vision, else: @ranged_vision) + ) + + if Enum.empty?(players_with_distances) do + move(bot_player, bot_state_machine) + else direction = maybe_aim_to_a_player(bot_player, players_with_distances) %{action: {:use_skill, @skill_1_key, direction}, bot_state_machine: bot_state_machine} + end - true -> - move(bot_player, bot_state_machine) - end + true -> + move(bot_player, bot_state_machine) end end @@ -191,7 +217,7 @@ defmodule BotManager.BotStateMachine do end defp run_away(bot_player, game_state, bot_state_machine) do - players_with_distances = map_directions_to_players(game_state, bot_player, @vision_range) + players_with_distances = map_directions_to_players(game_state, bot_player, @run_away_vision) if Enum.empty?(players_with_distances) do move(bot_player, bot_state_machine) diff --git a/apps/bot_manager/lib/protobuf/messages.pb.ex b/apps/bot_manager/lib/protobuf/messages.pb.ex index 991ec1bd7..83117a101 100644 --- a/apps/bot_manager/lib/protobuf/messages.pb.ex +++ b/apps/bot_manager/lib/protobuf/messages.pb.ex @@ -9,6 +9,25 @@ defmodule BotManager.Protobuf.GameMode do field(:QUICK_GAME, 3) end +defmodule BotManager.Protobuf.AttackType do + @moduledoc false + + use Protobuf, enum: true, syntax: :proto3, protoc_gen_elixir_version: "0.13.0" + + field(:MELEE, 0) + field(:RANGED, 1) +end + +defmodule BotManager.Protobuf.SkillType do + @moduledoc false + + use Protobuf, enum: true, syntax: :proto3, protoc_gen_elixir_version: "0.13.0" + + field(:BASIC, 0) + field(:ULTIMATE, 1) + field(:DASH, 2) +end + defmodule BotManager.Protobuf.GameStatus do @moduledoc false @@ -319,6 +338,14 @@ defmodule BotManager.Protobuf.ConfigSkill do field(:targetting_offset, 8, type: :float, json_name: "targettingOffset") field(:mana_cost, 9, type: :uint64, json_name: "manaCost") field(:is_combo, 10, type: :bool, json_name: "isCombo") + + field(:attack_type, 11, + type: BotManager.Protobuf.AttackType, + json_name: "attackType", + enum: true + ) + + field(:skill_type, 12, type: BotManager.Protobuf.SkillType, json_name: "skillType", enum: true) end defmodule BotManager.Protobuf.GameState.PlayersEntry do diff --git a/apps/bot_manager/lib/utils.ex b/apps/bot_manager/lib/utils.ex index 5e6427fe0..e91ea6a2e 100644 --- a/apps/bot_manager/lib/utils.ex +++ b/apps/bot_manager/lib/utils.ex @@ -6,4 +6,17 @@ defmodule BotManager.Utils do def player_alive?(%{aditional_info: {:player, %{health: health}}}), do: health > 0 def player_alive?(_), do: :not_a_player + + def list_character_skills_from_config(character_name, characters) do + character = Enum.find(characters, fn character -> character.name == character_name end) + {_id, basic} = Enum.find(character.skills, fn {_id, skill} -> skill.skill_type == :BASIC end) + {_id, ultimate} = Enum.find(character.skills, fn {_id, skill} -> skill.skill_type == :ULTIMATE end) + {_id, dash} = Enum.find(character.skills, fn {_id, skill} -> skill.skill_type == :DASH end) + + %{ + basic: basic, + ultimate: ultimate, + dash: dash + } + end end diff --git a/apps/game_backend/lib/game_backend/units/skills/skill.ex b/apps/game_backend/lib/game_backend/units/skills/skill.ex index 29a0dab2e..db3872315 100644 --- a/apps/game_backend/lib/game_backend/units/skills/skill.ex +++ b/apps/game_backend/lib/game_backend/units/skills/skill.ex @@ -29,6 +29,7 @@ defmodule GameBackend.Units.Skills.Skill do field(:stamina_cost, :integer) field(:mana_cost, :integer) field(:type, Ecto.Enum, values: [:basic, :dash, :ultimate]) + field(:attack_type, Ecto.Enum, values: [:melee, :ranged]) belongs_to(:buff, Buff) belongs_to(:next_skill, __MODULE__) @@ -62,7 +63,8 @@ defmodule GameBackend.Units.Skills.Skill do :stamina_cost, :mana_cost, :type, - :version_id + :version_id, + :attack_type ] @doc false diff --git a/apps/game_backend/priv/repo/migrations/20250106212249_add_attack_type_to_skill.exs b/apps/game_backend/priv/repo/migrations/20250106212249_add_attack_type_to_skill.exs new file mode 100644 index 000000000..1acb59889 --- /dev/null +++ b/apps/game_backend/priv/repo/migrations/20250106212249_add_attack_type_to_skill.exs @@ -0,0 +1,9 @@ +defmodule GameBackend.Repo.Migrations.AddAttackTypeToSkill do + use Ecto.Migration + + def change do + alter table(:skills) do + add :attack_type, :string + end + end +end diff --git a/apps/game_client/assets/js/protobuf/messages_pb.js b/apps/game_client/assets/js/protobuf/messages_pb.js index eed46ceda..41973e513 100644 --- a/apps/game_client/assets/js/protobuf/messages_pb.js +++ b/apps/game_client/assets/js/protobuf/messages_pb.js @@ -23,6 +23,7 @@ var global = goog.exportSymbol('proto.Attack', null, global); goog.exportSymbol('proto.AttackParameters', null, global); +goog.exportSymbol('proto.AttackType', null, global); goog.exportSymbol('proto.BountyInfo', null, global); goog.exportSymbol('proto.BountySelected', null, global); goog.exportSymbol('proto.Bush', null, global); @@ -71,6 +72,7 @@ goog.exportSymbol('proto.PowerUpstatus', null, global); goog.exportSymbol('proto.Projectile', null, global); goog.exportSymbol('proto.ProjectileStatus', null, global); goog.exportSymbol('proto.SelectBounty', null, global); +goog.exportSymbol('proto.SkillType', null, global); goog.exportSymbol('proto.ToggleBots', null, global); goog.exportSymbol('proto.ToggleZone', null, global); goog.exportSymbol('proto.Trap', null, global); @@ -4641,7 +4643,9 @@ targettingRange: jspb.Message.getFloatingPointFieldWithDefault(msg, 6, 0.0), staminaCost: jspb.Message.getFieldWithDefault(msg, 7, 0), targettingOffset: jspb.Message.getFloatingPointFieldWithDefault(msg, 8, 0.0), manaCost: jspb.Message.getFieldWithDefault(msg, 9, 0), -isCombo: jspb.Message.getBooleanFieldWithDefault(msg, 10, false) +isCombo: jspb.Message.getBooleanFieldWithDefault(msg, 10, false), +attackType: jspb.Message.getFieldWithDefault(msg, 11, 0), +skillType: jspb.Message.getFieldWithDefault(msg, 12, 0) }; if (includeInstance) { @@ -4718,6 +4722,14 @@ proto.ConfigSkill.deserializeBinaryFromReader = function(msg, reader) { var value = /** @type {boolean} */ (reader.readBool()); msg.setIsCombo(value); break; + case 11: + var value = /** @type {!proto.AttackType} */ (reader.readEnum()); + msg.setAttackType(value); + break; + case 12: + var value = /** @type {!proto.SkillType} */ (reader.readEnum()); + msg.setSkillType(value); + break; default: reader.skipField(); break; @@ -4817,6 +4829,20 @@ proto.ConfigSkill.serializeBinaryToWriter = function(message, writer) { f ); } + f = message.getAttackType(); + if (f !== 0.0) { + writer.writeEnum( + 11, + f + ); + } + f = message.getSkillType(); + if (f !== 0.0) { + writer.writeEnum( + 12, + f + ); + } }; @@ -5000,6 +5026,42 @@ proto.ConfigSkill.prototype.setIsCombo = function(value) { }; +/** + * optional AttackType attack_type = 11; + * @return {!proto.AttackType} + */ +proto.ConfigSkill.prototype.getAttackType = function() { + return /** @type {!proto.AttackType} */ (jspb.Message.getFieldWithDefault(this, 11, 0)); +}; + + +/** + * @param {!proto.AttackType} value + * @return {!proto.ConfigSkill} returns this + */ +proto.ConfigSkill.prototype.setAttackType = function(value) { + return jspb.Message.setProto3EnumField(this, 11, value); +}; + + +/** + * optional SkillType skill_type = 12; + * @return {!proto.SkillType} + */ +proto.ConfigSkill.prototype.getSkillType = function() { + return /** @type {!proto.SkillType} */ (jspb.Message.getFieldWithDefault(this, 12, 0)); +}; + + +/** + * @param {!proto.SkillType} value + * @return {!proto.ConfigSkill} returns this + */ +proto.ConfigSkill.prototype.setSkillType = function(value) { + return jspb.Message.setProto3EnumField(this, 12, value); +}; + + /** * List of repeated fields within this message type. @@ -12523,6 +12585,23 @@ proto.GameMode = { QUICK_GAME: 3 }; +/** + * @enum {number} + */ +proto.AttackType = { + MELEE: 0, + RANGED: 1 +}; + +/** + * @enum {number} + */ +proto.SkillType = { + BASIC: 0, + ULTIMATE: 1, + DASH: 2 +}; + /** * @enum {number} */ diff --git a/apps/game_client/lib/game_client/protobuf/messages.pb.ex b/apps/game_client/lib/game_client/protobuf/messages.pb.ex index a1ba590d0..68207aa73 100644 --- a/apps/game_client/lib/game_client/protobuf/messages.pb.ex +++ b/apps/game_client/lib/game_client/protobuf/messages.pb.ex @@ -9,6 +9,25 @@ defmodule GameClient.Protobuf.GameMode do field(:QUICK_GAME, 3) end +defmodule GameClient.Protobuf.AttackType do + @moduledoc false + + use Protobuf, enum: true, syntax: :proto3, protoc_gen_elixir_version: "0.13.0" + + field(:MELEE, 0) + field(:RANGED, 1) +end + +defmodule GameClient.Protobuf.SkillType do + @moduledoc false + + use Protobuf, enum: true, syntax: :proto3, protoc_gen_elixir_version: "0.13.0" + + field(:BASIC, 0) + field(:ULTIMATE, 1) + field(:DASH, 2) +end + defmodule GameClient.Protobuf.GameStatus do @moduledoc false @@ -319,6 +338,14 @@ defmodule GameClient.Protobuf.ConfigSkill do field(:targetting_offset, 8, type: :float, json_name: "targettingOffset") field(:mana_cost, 9, type: :uint64, json_name: "manaCost") field(:is_combo, 10, type: :bool, json_name: "isCombo") + + field(:attack_type, 11, + type: GameClient.Protobuf.AttackType, + json_name: "attackType", + enum: true + ) + + field(:skill_type, 12, type: GameClient.Protobuf.SkillType, json_name: "skillType", enum: true) end defmodule GameClient.Protobuf.GameState.PlayersEntry do diff --git a/apps/serialization/messages.proto b/apps/serialization/messages.proto index fcaba0ff8..7e81179b0 100644 --- a/apps/serialization/messages.proto +++ b/apps/serialization/messages.proto @@ -123,6 +123,19 @@ message ConfigSkill { float targetting_offset = 8; uint64 mana_cost = 9; bool is_combo = 10; + AttackType attack_type = 11; + SkillType skill_type = 12; +} + +enum AttackType { + MELEE = 0; + RANGED = 1; +} + +enum SkillType{ + BASIC = 0; + ULTIMATE = 1; + DASH = 2; } diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index 1569cdf5a..a47ea94bc 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -585,6 +585,7 @@ skills = [ %{ "name" => "muflus_crush", "type" => "basic", + "attack_type" => "melee", "cooldown_mechanism" => "stamina", "execution_duration_ms" => 450, "activation_delay_ms" => 150, @@ -607,6 +608,7 @@ skills = [ %{ "name" => "muflus_leap", "type" => "ultimate", + "attack_type" => "ranged", "cooldown_mechanism" => "time", "cooldown_ms" => 8000, "execution_duration_ms" => 800, @@ -635,6 +637,7 @@ skills = [ %{ "name" => "muflus_dash", "type" => "dash", + "attack_type" => "melee", "cooldown_mechanism" => "time", "cooldown_ms" => 4500, "execution_duration_ms" => 330, @@ -656,6 +659,7 @@ skills = [ %{ "name" => "h4ck_slingshot", "type" => "basic", + "attack_type" => "ranged", "cooldown_mechanism" => "stamina", "execution_duration_ms" => 250, "activation_delay_ms" => 0, @@ -671,6 +675,7 @@ skills = [ %{ "name" => "h4ck_dash", "type" => "dash", + "attack_type" => "melee", "cooldown_mechanism" => "time", "cooldown_ms" => 5500, "execution_duration_ms" => 250, @@ -692,6 +697,7 @@ skills = [ %{ "name" => "h4ck_denial_of_service", "type" => "ultimate", + "attack_type" => "ranged", "cooldown_mechanism" => "time", "cooldown_ms" => 9000, "execution_duration_ms" => 200, @@ -719,6 +725,7 @@ skills = [ %{ "name" => "uma_avenge", "type" => "basic", + "attack_type" => "melee", "cooldown_mechanism" => "stamina", "execution_duration_ms" => 500, "activation_delay_ms" => 0, @@ -743,6 +750,7 @@ skills = [ %{ "name" => "uma_veil_radiance", "type" => "ultimate", + "attack_type" => "melee", "cooldown_mechanism" => "time", "cooldown_ms" => 9000, "execution_duration_ms" => 300, @@ -766,6 +774,7 @@ skills = [ %{ "name" => "uma_sneak", "type" => "dash", + "attack_type" => "melee", "cooldown_mechanism" => "time", "cooldown_ms" => 5000, "execution_duration_ms" => 250, @@ -787,6 +796,7 @@ skills = [ %{ "name" => "valt_singularity", "type" => "ultimate", + "attack_type" => "ranged", "cooldown_mechanism" => "time", "cooldown_ms" => 9000, "execution_duration_ms" => 500, @@ -802,6 +812,7 @@ skills = [ %{ "name" => "valt_warp", "type" => "dash", + "attack_type" => "ranged", "cooldown_mechanism" => "time", "cooldown_ms" => 6000, "execution_duration_ms" => 450, @@ -825,6 +836,7 @@ skills = [ %{ "name" => "valt_antimatter", "type" => "basic", + "attack_type" => "ranged", "cooldown_mechanism" => "stamina", "execution_duration_ms" => 450, "activation_delay_ms" => 150, @@ -840,6 +852,7 @@ skills = [ %{ "name" => "kenzu_quickslash", "type" => "basic", + "attack_type" => "melee", "cooldown_mechanism" => "stamina", "reset_combo_ms" => 0, "is_combo?" => true, @@ -857,6 +870,7 @@ skills = [ %{ "name" => "kenzu_quickslash_second", "type" => "basic", + "attack_type" => "melee", "cooldown_mechanism" => "stamina", "reset_combo_ms" => 800, "is_combo?" => true, @@ -874,6 +888,7 @@ skills = [ %{ "name" => "kenzu_quickslash_third", "type" => "basic", + "attack_type" => "melee", "cooldown_mechanism" => "stamina", "reset_combo_ms" => 800, "is_combo?" => true, @@ -891,6 +906,7 @@ skills = [ %{ "name" => "kenzu_whirlwind", "type" => "ultimate", + "attack_type" => "melee", "cooldown_mechanism" => "time", "cooldown_ms" => 9000, "execution_duration_ms" => 5000, @@ -916,6 +932,7 @@ skills = [ %{ "name" => "kenzu_pounce", "type" => "dash", + "attack_type" => "melee", "cooldown_mechanism" => "time", "cooldown_ms" => 5000, "execution_duration_ms" => 350, @@ -938,6 +955,7 @@ skills = [ %{ "name" => "otix_carbonthrow", "type" => "basic", + "attack_type" => "ranged", "cooldown_mechanism" => "stamina", "execution_duration_ms" => 450, "activation_delay_ms" => 150, @@ -955,6 +973,7 @@ skills = [ %{ "name" => "otix_magma_rush", "type" => "dash", + "attack_type" => "melee", "cooldown_mechanism" => "time", "cooldown_ms" => 5500, "execution_duration_ms" => 750, @@ -972,6 +991,7 @@ skills = [ %{ "name" => "otix_inferno", "type" => "ultimate", + "attack_type" => "melee", "cooldown_mechanism" => "time", "cooldown_ms" => 10000, "execution_duration_ms" => 1000, @@ -989,6 +1009,7 @@ skills = [ %{ "name" => "shinko_toxic_onion", "type" => "basic", + "attack_type" => "ranged", "cooldown_mechanism" => "stamina", "execution_duration_ms" => 450, "activation_delay_ms" => 150, @@ -1006,6 +1027,7 @@ skills = [ %{ "name" => "shinko_spore_dash", "type" => "dash", + "attack_type" => "melee", "cooldown_mechanism" => "time", "cooldown_ms" => 5500, "execution_duration_ms" => 250, @@ -1023,6 +1045,7 @@ skills = [ %{ "name" => "shinko_PEB", "type" => "ultimate", + "attack_type" => "melee", "cooldown_mechanism" => "time", "cooldown_ms" => 10000, "execution_duration_ms" => 525, @@ -1040,6 +1063,7 @@ skills = [ %{ "name" => "uren_basic", "type" => "basic", + "attack_type" => "ranged", "cooldown_mechanism" => "stamina", "execution_duration_ms" => 450, "activation_delay_ms" => 150, @@ -1055,6 +1079,7 @@ skills = [ %{ "name" => "uren_ultimate", "type" => "ultimate", + "attack_type" => "ranged", "cooldown_mechanism" => "time", "cooldown_ms" => 10000, "execution_duration_ms" => 1000, @@ -1070,6 +1095,7 @@ skills = [ %{ "name" => "uren_dash", "type" => "dash", + "attack_type" => "melee", "cooldown_mechanism" => "time", "cooldown_ms" => 4000, "execution_duration_ms" => 250,