diff --git a/apps/arena/lib/arena/configuration.ex b/apps/arena/lib/arena/configuration.ex
index 42f5115a2..43f8b3cc4 100644
--- a/apps/arena/lib/arena/configuration.ex
+++ b/apps/arena/lib/arena/configuration.ex
@@ -13,12 +13,11 @@ defmodule Arena.Configuration do
|> File.read()
config = Jason.decode!(config_json, [{:keys, :atoms}])
- skills = parse_skills_config(config.skills)
- characters = parse_characters_config(get_characters_config(), skills)
+ characters = parse_characters_config(get_characters_config())
client_config = get_client_config()
game_config = get_game_configuration()
- %{config | skills: skills}
+ config
|> Map.put(:characters, characters)
|> Map.put(:game, game_config)
|> Map.put(:client_config, client_config)
@@ -52,10 +51,16 @@ defmodule Arena.Configuration do
Jason.decode!(payload.body, [{:keys, :atoms}])
end
- defp parse_skills_config(skills_config) do
- Enum.reduce(skills_config, [], fn skill_config, skills ->
- skill = parse_skill_config(skill_config)
- [skill | skills]
+ defp parse_characters_config(characters) do
+ Enum.map(characters, fn character ->
+ character_skills = %{
+ "1" => parse_skill_config(character.basic_skill),
+ "2" => parse_skill_config(character.ultimate_skill),
+ "3" => parse_skill_config(character.dash_skill)
+ }
+
+ Map.put(character, :skills, character_skills)
+ |> Map.drop([:basic_skill, :ultimate_skill, :dash_skill])
end)
end
@@ -89,53 +94,32 @@ defmodule Arena.Configuration do
end)
end
- defp parse_mechanic_config(mechanic) when map_size(mechanic) > 1 do
- raise "Config has mechanic with 2 values: #{inspect(mechanic)}"
+ defp parse_mechanic_config(nil) do
+ nil
end
defp parse_mechanic_config(mechanic) do
- Map.to_list(mechanic)
- |> Enum.map(&parse_mechanic_fields/1)
- |> hd()
- end
-
- defp parse_mechanic_fields({:leap, attrs}) do
- {:leap, %{attrs | on_arrival_mechanic: parse_mechanic_config(attrs.on_arrival_mechanic)}}
- end
-
- defp parse_mechanic_fields({name, %{on_explode_mechanics: on_explode_mechanics} = attrs}) do
- {name,
- %{
- attrs
- | on_explode_mechanics: parse_mechanic_config(on_explode_mechanics)
- }}
- end
-
- defp parse_mechanic_fields(mechanic) do
- mechanic
- end
-
- defp parse_characters_config(characters, config_skills) do
- Enum.map(characters, fn character ->
- character_skills =
- Enum.map(character.skills, fn {skill_key, skill_name} ->
- skill = find_skill!(skill_name, config_skills)
- {:erlang.atom_to_binary(skill_key), skill}
- end)
- |> Map.new()
-
- %{character | skills: character_skills}
- end)
- end
-
- defp find_skill!(skill_name, skills) do
- skill = Enum.find(skills, fn skill -> skill.name == skill_name end)
-
- ## This is a sanity check when loading the config
- if skill == nil do
- raise "Skill #{inspect(skill_name)} does not exist in config"
- else
- skill
- end
+ ## Why do we even need this? Well it happens that some of our fields are represented
+ ## as Decimal. To prevent precision loss this struct its converted to a string of the float
+ ## which should be read back and converted to Decimal
+ ## The not so small problem we have is that our code expects floats so we still need to parse
+ ## the strings, but end up with regular floats
+ %{
+ mechanic
+ | angle_between: maybe_to_float(mechanic.angle_between),
+ move_by: maybe_to_float(mechanic.move_by),
+ radius: maybe_to_float(mechanic.radius),
+ range: maybe_to_float(mechanic.range),
+ speed: maybe_to_float(mechanic.speed),
+ on_arrival_mechanic: parse_mechanic_config(mechanic.on_arrival_mechanic),
+ on_explode_mechanics: parse_mechanics_config(mechanic.on_explode_mechanics)
+ }
+ end
+
+ defp maybe_to_float(nil), do: nil
+
+ defp maybe_to_float(float_string) do
+ {float, ""} = Float.parse(float_string)
+ float
end
end
diff --git a/apps/arena/lib/arena/game/player.ex b/apps/arena/lib/arena/game/player.ex
index 9ea779cce..a5b166e71 100644
--- a/apps/arena/lib/arena/game/player.ex
+++ b/apps/arena/lib/arena/game/player.ex
@@ -246,7 +246,7 @@ defmodule Arena.Game.Player do
# This is a messy solution to get a mechanic result before actually running the mechanic since the client needed the
# position in which the player will spawn when the skill start and not when we actually execute the teleport
# this is also optimistic since we assume the destination will be always available
- defp maybe_add_destination(action, game_state, player, skill_direction, %{mechanics: [{:teleport, teleport}]}) do
+ defp maybe_add_destination(action, game_state, player, skill_direction, %{mechanics: [%{type: "teleport"} = teleport]}) do
target_position = %{
x: player.position.x + skill_direction.x * teleport.range,
y: player.position.y + skill_direction.y * teleport.range
@@ -454,7 +454,7 @@ defmodule Arena.Game.Player do
## so to simplify my life an executive decision was made to take thas as a fact
## When the time comes to have more than one mechanic per skill this function will need to be refactored, good thing
## is that it will crash here so not something we can ignore
- defp calculate_duration(%{mechanics: [{:leap, leap}]}, position, direction, auto_aim?) do
+ defp calculate_duration(%{mechanics: [%{type: "leap"} = leap]}, position, direction, auto_aim?) do
## TODO: Cap target_position to leap.range
direction = Skill.maybe_multiply_by_range(direction, auto_aim?, leap.range)
diff --git a/apps/arena/lib/arena/game/skill.ex b/apps/arena/lib/arena/game/skill.ex
index e83e14cb0..c033dbfd8 100644
--- a/apps/arena/lib/arena/game/skill.ex
+++ b/apps/arena/lib/arena/game/skill.ex
@@ -13,7 +13,12 @@ defmodule Arena.Game.Skill do
end)
end
- def do_mechanic(game_state, entity, {:circle_hit, circle_hit}, %{skill_direction: skill_direction} = _skill_params) do
+ def do_mechanic(
+ game_state,
+ entity,
+ %{type: "circle_hit"} = circle_hit,
+ %{skill_direction: skill_direction} = _skill_params
+ ) do
circle_center_position = get_position_with_offset(entity.position, skill_direction, circle_hit.offset)
circular_damage_area = Entities.make_circular_area(entity.id, circle_center_position, circle_hit.range)
@@ -63,7 +68,12 @@ defmodule Arena.Game.Skill do
|> maybe_move_player(entity, circle_hit[:move_by])
end
- def do_mechanic(game_state, entity, {:cone_hit, cone_hit}, %{skill_direction: skill_direction} = _skill_params) do
+ def do_mechanic(
+ game_state,
+ entity,
+ %{type: "cone_hit"} = cone_hit,
+ %{skill_direction: skill_direction} = _skill_params
+ ) do
triangle_points =
Physics.calculate_triangle_vertices(
entity.position,
@@ -117,9 +127,9 @@ defmodule Arena.Game.Skill do
|> maybe_move_player(entity, cone_hit[:move_by])
end
- def do_mechanic(game_state, entity, {:multi_cone_hit, multi_cone_hit}, skill_params) do
+ def do_mechanic(game_state, entity, %{type: "multi_cone_hit"} = multi_cone_hit, skill_params) do
Enum.each(1..(multi_cone_hit.amount - 1), fn i ->
- mechanic = {:cone_hit, multi_cone_hit}
+ mechanic = %{multi_cone_hit | type: "cone_hit"}
Process.send_after(
self(),
@@ -128,12 +138,12 @@ defmodule Arena.Game.Skill do
)
end)
- do_mechanic(game_state, entity, {:cone_hit, multi_cone_hit}, skill_params)
+ do_mechanic(game_state, entity, %{multi_cone_hit | type: "cone_hit"}, skill_params)
end
- def do_mechanic(game_state, entity, {:multi_circle_hit, multi_circle_hit}, skill_params) do
+ def do_mechanic(game_state, entity, %{type: "multi_circle_hit"} = multi_circle_hit, skill_params) do
Enum.each(1..(multi_circle_hit.amount - 1), fn i ->
- mechanic = {:circle_hit, multi_circle_hit}
+ mechanic = %{multi_circle_hit | type: "circle_hit"}
Process.send_after(
self(),
@@ -142,16 +152,16 @@ defmodule Arena.Game.Skill do
)
end)
- do_mechanic(game_state, entity, {:circle_hit, multi_circle_hit}, skill_params)
+ do_mechanic(game_state, entity, %{multi_circle_hit | type: "circle_hit"}, skill_params)
end
def do_mechanic(
game_state,
entity,
- {:dash, %{speed: speed, duration: duration}},
+ %{type: "dash", speed: speed, duration_ms: duration_ms},
%{skill_direction: skill_direction} = _skill_params
) do
- Process.send_after(self(), {:stop_dash, entity.id, entity.aditional_info.base_speed}, duration)
+ Process.send_after(self(), {:stop_dash, entity.id, entity.aditional_info.base_speed}, duration_ms)
## Modifying base_speed rather than speed because effects will reset the speed on game tick
## by modifying base_speed we ensure that the dash speed is kept as expected
@@ -167,7 +177,7 @@ defmodule Arena.Game.Skill do
%{game_state | players: players}
end
- def do_mechanic(game_state, entity, {:repeated_shot, repeated_shot}, skill_params) do
+ def do_mechanic(game_state, entity, %{type: "repeated_shot"} = repeated_shot, skill_params) do
remaining_amount = repeated_shot.amount - 1
if remaining_amount > 0 do
@@ -175,7 +185,7 @@ defmodule Arena.Game.Skill do
Process.send_after(
self(),
- {:trigger_mechanic, entity.id, {:repeated_shot, repeated_shot}, skill_params},
+ {:trigger_mechanic, entity.id, repeated_shot, skill_params},
repeated_shot.interval_ms
)
end
@@ -205,7 +215,12 @@ defmodule Arena.Game.Skill do
|> put_in([:projectiles, projectile.id], projectile)
end
- def do_mechanic(game_state, entity, {:multi_shoot, multishot}, %{skill_direction: skill_direction} = skill_params) do
+ def do_mechanic(
+ game_state,
+ entity,
+ %{type: "multi_shoot"} = multishot,
+ %{skill_direction: skill_direction} = skill_params
+ ) do
entity_player_owner = get_entity_player_owner(game_state, entity)
calculate_angle_directions(multishot.amount, multishot.angle_between, skill_direction)
@@ -234,7 +249,12 @@ defmodule Arena.Game.Skill do
end)
end
- def do_mechanic(game_state, entity, {:simple_shoot, simple_shoot}, %{skill_direction: skill_direction} = skill_params) do
+ def do_mechanic(
+ game_state,
+ entity,
+ %{type: "simple_shoot"} = simple_shoot,
+ %{skill_direction: skill_direction} = skill_params
+ ) do
last_id = game_state.last_id + 1
entity_player_owner = get_entity_player_owner(game_state, entity)
@@ -259,7 +279,7 @@ defmodule Arena.Game.Skill do
|> put_in([:projectiles, projectile.id], projectile)
end
- def do_mechanic(game_state, entity, {:leap, leap}, %{execution_duration: execution_duration}) do
+ def do_mechanic(game_state, entity, %{type: "leap"} = leap, %{execution_duration: execution_duration}) do
Process.send_after(
self(),
{:stop_leap, entity.id, entity.aditional_info.base_speed, leap.on_arrival_mechanic},
@@ -278,7 +298,7 @@ defmodule Arena.Game.Skill do
put_in(game_state, [:players, player.id], player)
end
- def do_mechanic(game_state, entity, {:teleport, _teleport}, %{skill_destination: skill_destination}) do
+ def do_mechanic(game_state, entity, %{type: "teleport"}, %{skill_destination: skill_destination}) do
entity =
entity
|> Map.put(:aditional_info, entity.aditional_info)
@@ -287,15 +307,10 @@ defmodule Arena.Game.Skill do
put_in(game_state, [:players, entity.id], entity)
end
- def do_mechanic(game_state, player, {:spawn_pool, pool_params}, skill_params) do
- %{
- skill_direction: skill_direction,
- auto_aim?: auto_aim?
- } = skill_params
-
+ def do_mechanic(game_state, player, %{type: "spawn_pool"} = pool_params, skill_params) do
last_id = game_state.last_id + 1
- skill_direction = maybe_multiply_by_range(skill_direction, auto_aim?, pool_params.range)
+ skill_direction = maybe_multiply_by_range(skill_params.skill_direction, skill_params.auto_aim?, pool_params.range)
target_position = %{
x: player.position.x + skill_direction.x,
diff --git a/apps/arena/lib/arena/game_socket_handler.ex b/apps/arena/lib/arena/game_socket_handler.ex
index 78aba7665..f5e689b29 100644
--- a/apps/arena/lib/arena/game_socket_handler.ex
+++ b/apps/arena/lib/arena/game_socket_handler.ex
@@ -188,15 +188,14 @@ defmodule Arena.GameSocketHandler do
defp to_broadcast_skill({key, skill}) do
## TODO: This will break once a skill has more than 1 mechanic, until then
## we can use this "shortcut" and deal with it when the time comes
- [{_mechanic, params}] = skill.mechanics
-
- extra_params =
- %{
- targetting_radius: params[:radius],
- targetting_angle: params[:angle],
- targetting_range: params[:range],
- targetting_offset: params[:offset] || params[:projectile_offset]
- }
+ [mechanic] = skill.mechanics
+
+ extra_params = %{
+ targetting_radius: mechanic[:radius],
+ targetting_angle: mechanic[:angle],
+ targetting_range: mechanic[:range],
+ targetting_offset: mechanic[:offset] || mechanic[:projectile_offset]
+ }
{key, Map.merge(skill, extra_params)}
end
diff --git a/apps/configurator/lib/configurator_web/controllers/character_controller.ex b/apps/configurator/lib/configurator_web/controllers/character_controller.ex
index 1cecacf48..b547ba5b4 100644
--- a/apps/configurator/lib/configurator_web/controllers/character_controller.ex
+++ b/apps/configurator/lib/configurator_web/controllers/character_controller.ex
@@ -11,15 +11,13 @@ defmodule ConfiguratorWeb.CharacterController do
def new(conn, _params) do
changeset = Ecto.Changeset.change(%Character{})
- render(conn, :new, changeset: changeset)
+ skills = get_curse_skills_by_type()
+ render(conn, :new, changeset: changeset, skills: skills)
end
def create(conn, %{"character" => character_params}) do
- # TODO This should be removed once we have the skills relationship, issue: https://github.com/lambdaclass/mirra_backend/issues/717
- skills = Jason.decode!(character_params["skills"])
-
character_params =
- Map.put(character_params, "skills", skills)
+ character_params
|> Map.put("game_id", GameBackend.Utils.get_game_id(:curse_of_mirra))
|> Map.put("faction", "curse")
@@ -30,7 +28,8 @@ defmodule ConfiguratorWeb.CharacterController do
|> redirect(to: ~p"/characters/#{character}")
{:error, %Ecto.Changeset{} = changeset} ->
- render(conn, :new, changeset: changeset)
+ skills = get_curse_skills_by_type()
+ render(conn, :new, changeset: changeset, skills: skills)
end
end
@@ -42,13 +41,11 @@ defmodule ConfiguratorWeb.CharacterController do
def edit(conn, %{"id" => id}) do
character = Characters.get_character(id)
changeset = Ecto.Changeset.change(character)
- render(conn, :edit, character: character, changeset: changeset)
+ skills = get_curse_skills_by_type()
+ render(conn, :edit, character: character, changeset: changeset, skills: skills)
end
def update(conn, %{"id" => id, "character" => character_params}) do
- # TODO This should be removed once we have the skills relationship, issue: https://github.com/lambdaclass/mirra_backend/issues/717
- skills = Jason.decode!(character_params["skills"])
- character_params = Map.put(character_params, "skills", skills)
character = Characters.get_character(id)
case Characters.update_character(character, character_params) do
@@ -58,7 +55,8 @@ defmodule ConfiguratorWeb.CharacterController do
|> redirect(to: ~p"/characters/#{character}")
{:error, %Ecto.Changeset{} = changeset} ->
- render(conn, :edit, character: character, changeset: changeset)
+ skills = get_curse_skills_by_type()
+ render(conn, :edit, character: character, changeset: changeset, skills: skills)
end
end
@@ -70,4 +68,9 @@ defmodule ConfiguratorWeb.CharacterController do
|> put_flash(:success, "Character deleted successfully.")
|> redirect(to: ~p"/characters")
end
+
+ defp get_curse_skills_by_type() do
+ GameBackend.Units.Skills.list_curse_skills()
+ |> Enum.group_by(& &1.type)
+ end
end
diff --git a/apps/configurator/lib/configurator_web/controllers/character_html.ex b/apps/configurator/lib/configurator_web/controllers/character_html.ex
index 24dcbd08f..541722299 100644
--- a/apps/configurator/lib/configurator_web/controllers/character_html.ex
+++ b/apps/configurator/lib/configurator_web/controllers/character_html.ex
@@ -8,6 +8,23 @@ defmodule ConfiguratorWeb.CharacterHTML do
"""
attr :changeset, Ecto.Changeset, required: true
attr :action, :string, required: true
+ attr :skills, :list, required: true
def character_form(assigns)
+
+ attr :field, Phoenix.HTML.FormField, required: true
+ attr :label, :string, required: true
+ attr :skills, :list, required: true
+
+ def skill_select(assigns) do
+ ~H"""
+ <.input
+ field={@field}
+ type="select"
+ label={@label}
+ prompt="Select a skill"
+ options={Enum.map(@skills, &{&1.name, &1.id})}
+ />
+ """
+ end
end
diff --git a/apps/configurator/lib/configurator_web/controllers/character_html/character_form.html.heex b/apps/configurator/lib/configurator_web/controllers/character_html/character_form.html.heex
index 73aa2a08a..216cadf7f 100644
--- a/apps/configurator/lib/configurator_web/controllers/character_html/character_form.html.heex
+++ b/apps/configurator/lib/configurator_web/controllers/character_html/character_form.html.heex
@@ -5,7 +5,7 @@
<.input field={f[:name]} type="text" label="Name" />
<.input field={f[:active]} type="checkbox" label="Active" />
<.input field={f[:base_speed]} type="number" label="Base speed" step="any" />
- <.input field={f[:base_size]} type="number" label="Base size" step="any" />
+ <.input field={f[:base_size]} type="number" label="Base size" step="any" lang="en" />
<.input field={f[:base_health]} type="number" label="Base health" />
<.input field={f[:base_stamina]} type="number" label="Base stamina" />
<.input field={f[:stamina_interval]} type="number" label="Stamina Interval" />
@@ -14,7 +14,9 @@
<.input field={f[:natural_healing_interval]} type="number" label="Natural healing interval" />
<.input field={f[:natural_healing_damage_interval]} type="number" label="Natural healing damage interval" />
- <.input field={f[:skills]} type="text" label="Skills" value={Jason.encode!(f.data.skills)} />
+ <.skill_select field={f[:basic_skill_id]} label="Basic skill" skills={@skills[:basic]} />
+ <.skill_select field={f[:dash_skill_id]} label="Dash skill" skills={@skills[:dash]} />
+ <.skill_select field={f[:ultimate_skill_id]} label="Ultimate skill" skills={@skills[:ultimate]} />
<:actions>
<.button>Save Character
diff --git a/apps/configurator/lib/configurator_web/controllers/character_html/edit.html.heex b/apps/configurator/lib/configurator_web/controllers/character_html/edit.html.heex
index fb271da45..e6c65b2fb 100644
--- a/apps/configurator/lib/configurator_web/controllers/character_html/edit.html.heex
+++ b/apps/configurator/lib/configurator_web/controllers/character_html/edit.html.heex
@@ -3,6 +3,6 @@
<:subtitle>Use this form to manage character records in your database.
-<.character_form changeset={@changeset} action={~p"/characters/#{@character}"} />
+<.character_form changeset={@changeset} action={~p"/characters/#{@character}"} skills={@skills} />
<.back navigate={~p"/characters"}>Back to characters
diff --git a/apps/configurator/lib/configurator_web/controllers/character_html/index.html.heex b/apps/configurator/lib/configurator_web/controllers/character_html/index.html.heex
index 1673b1b74..a4a7857d1 100644
--- a/apps/configurator/lib/configurator_web/controllers/character_html/index.html.heex
+++ b/apps/configurator/lib/configurator_web/controllers/character_html/index.html.heex
@@ -14,7 +14,11 @@
<:col :let={character} label="Base size"><%= character.base_size %>
<:col :let={character} label="Base health"><%= character.base_health %>
<:col :let={character} label="Base stamina"><%= character.base_stamina %>
- <:col :let={character} label="Stamina Interval"><%= character.stamina_interval %>
+ <:col :let={character} label="Basic skill"><%= if character.basic_skill, do: character.basic_skill.name %>
+ <:col :let={character} label="Dash skill"><%= if character.dash_skill, do: character.dash_skill.name %>
+ <:col :let={character} label="Ultimate skill">
+ <%= if character.ultimate_skill, do: character.ultimate_skill.name %>
+
<:action :let={character}>
diff --git a/apps/configurator/lib/configurator_web/controllers/character_html/new.html.heex b/apps/configurator/lib/configurator_web/controllers/character_html/new.html.heex
index 5bbd0af66..ff3d56330 100644
--- a/apps/configurator/lib/configurator_web/controllers/character_html/new.html.heex
+++ b/apps/configurator/lib/configurator_web/controllers/character_html/new.html.heex
@@ -3,6 +3,6 @@
<:subtitle>Use this form to manage character records in your database.
-<.character_form changeset={@changeset} action={~p"/characters"} />
+<.character_form changeset={@changeset} action={~p"/characters"} skills={@skills} />
<.back navigate={~p"/characters"}>Back to characters
diff --git a/apps/configurator/lib/configurator_web/controllers/character_html/show.html.heex b/apps/configurator/lib/configurator_web/controllers/character_html/show.html.heex
index f9e1f01e5..e86add641 100644
--- a/apps/configurator/lib/configurator_web/controllers/character_html/show.html.heex
+++ b/apps/configurator/lib/configurator_web/controllers/character_html/show.html.heex
@@ -1,5 +1,5 @@
<.header>
- Character <%= @character.id %>
+ Character <%= @character.name %>
<:subtitle>This is a character record from your database.
<:actions>
<.link href={~p"/characters/#{@character}/edit"}>
@@ -19,7 +19,9 @@
<:item title="Max inventory size"><%= @character.max_inventory_size %>
<:item title="Natural healing interval"><%= @character.natural_healing_interval %>
<:item title="Natural healing damage interval"><%= @character.natural_healing_damage_interval %>
- <:item title="Skills"><%= Jason.encode!(@character.skills) %>
+ <:item title="Basic skill"><%= if @character.basic_skill, do: @character.basic_skill.name %>
+ <:item title="Dash skill"><%= if @character.dash_skill, do: @character.dash_skill.name %>
+ <:item title="Ultimate skill"><%= if @character.ultimate_skill, do: @character.ultimate_skill.name %>
<.back navigate={~p"/characters"}>Back to characters
diff --git a/apps/configurator/lib/configurator_web/controllers/home_html/home.html.heex b/apps/configurator/lib/configurator_web/controllers/home_html/home.html.heex
index 02fa8d62d..673bb2d38 100644
--- a/apps/configurator/lib/configurator_web/controllers/home_html/home.html.heex
+++ b/apps/configurator/lib/configurator_web/controllers/home_html/home.html.heex
@@ -5,4 +5,5 @@
<.list>
<:item title="Character settings"><.link href={~p"/characters"}>Link
<:item title="Game settings"><.link href={~p"/game_configurations"}>Link
+ <:item title="Skill settings"><.link href={~p"/skills"}>Link
diff --git a/apps/configurator/lib/configurator_web/controllers/skill_controller.ex b/apps/configurator/lib/configurator_web/controllers/skill_controller.ex
new file mode 100644
index 000000000..b3af2327d
--- /dev/null
+++ b/apps/configurator/lib/configurator_web/controllers/skill_controller.ex
@@ -0,0 +1,78 @@
+defmodule ConfiguratorWeb.SkillController do
+ use ConfiguratorWeb, :controller
+
+ alias GameBackend.Units.Skills
+ alias GameBackend.Units.Skills.Mechanic
+ alias GameBackend.Units.Skills.Skill
+ alias GameBackend.Utils
+
+ def index(conn, _params) do
+ skills = Skills.list_curse_skills()
+ render(conn, :index, skills: skills)
+ end
+
+ def new(conn, _params) do
+ changeset = Skills.change_skill(%Skill{mechanics: [%Mechanic{}]})
+ render(conn, :new, changeset: changeset)
+ end
+
+ def create(conn, %{"skill" => skill_params}) do
+ skill_params = Map.put(skill_params, "game_id", Utils.get_game_id(:curse_of_mirra))
+
+ case Skills.insert_skill(skill_params) do
+ {:ok, skill} ->
+ conn
+ |> put_flash(:info, "Skill created successfully.")
+ |> redirect(to: ~p"/skills/#{skill}")
+
+ {:error, %Ecto.Changeset{} = changeset} ->
+ render(conn, :new, changeset: changeset)
+ end
+ end
+
+ def show(conn, %{"id" => id}) do
+ skill = Skills.get_skill!(id)
+ render(conn, :show, skill: skill)
+ end
+
+ def edit(conn, %{"id" => id}) do
+ skill = Skills.get_skill!(id)
+ changeset = Skills.change_skill(skill)
+ render(conn, :edit, skill: skill, changeset: changeset)
+ end
+
+ def update(conn, %{"id" => id, "skill" => skill_params}) do
+ skill = Skills.get_skill!(id)
+
+ case Skills.update_skill(skill, skill_params) do
+ {:ok, skill} ->
+ conn
+ |> put_flash(:info, "Skill updated successfully.")
+ |> redirect(to: ~p"/skills/#{skill}")
+
+ {:error, %Ecto.Changeset{} = changeset} ->
+ render(conn, :edit, skill: skill, changeset: changeset)
+ end
+ end
+
+ def delete(conn, %{"id" => id}) do
+ skill = Skills.get_skill!(id)
+
+ case Skills.delete_skill(skill) do
+ {:error, %{errors: [characters: {_, [constraint: :foreign, constraint_name: "characters_basic_skill_id_fkey"]}]}} ->
+ conn
+ |> put_flash(:error, "Skill being used by a Character.")
+ |> redirect(to: ~p"/skills/#{skill}")
+
+ {:error, _changeset} ->
+ conn
+ |> put_flash(:info, "Something went wrong.")
+ |> redirect(to: ~p"/skills/#{skill}")
+
+ {:ok, _skill} ->
+ conn
+ |> put_flash(:success, "Skill deleted successfully.")
+ |> redirect(to: ~p"/skills")
+ end
+ end
+end
diff --git a/apps/configurator/lib/configurator_web/controllers/skill_html.ex b/apps/configurator/lib/configurator_web/controllers/skill_html.ex
new file mode 100644
index 000000000..b998b3603
--- /dev/null
+++ b/apps/configurator/lib/configurator_web/controllers/skill_html.ex
@@ -0,0 +1,36 @@
+defmodule ConfiguratorWeb.SkillHTML do
+ use ConfiguratorWeb, :html
+ alias GameBackend.Units.Skills.Mechanic
+
+ embed_templates "skill_html/*"
+
+ @doc """
+ Renders a skill form.
+ """
+ attr :changeset, Ecto.Changeset, required: true
+ attr :action, :string, required: true
+
+ def skill_form(assigns)
+
+ @doc """
+ Renders the inputs for a mechanic inside a skill form.
+ """
+ attr :skill_form, Phoenix.HTML.FormField, required: true
+
+ def skill_mechanic_inputs(assigns)
+
+ @doc """
+ Renders the inputs for a nested mechanic inside skill_mechanic_inputs/1.
+ """
+ attr :parent_form, Phoenix.HTML.FormField, required: true
+ attr :parent_field, :atom, required: true
+
+ def nested_mechanic_inputs(assigns)
+
+ @doc """
+ Renders to show a mechanic.
+ """
+ attr :mechanic, Mechanic, required: true
+
+ def mechanic_show(assigns)
+end
diff --git a/apps/configurator/lib/configurator_web/controllers/skill_html/edit.html.heex b/apps/configurator/lib/configurator_web/controllers/skill_html/edit.html.heex
new file mode 100644
index 000000000..dce7511bf
--- /dev/null
+++ b/apps/configurator/lib/configurator_web/controllers/skill_html/edit.html.heex
@@ -0,0 +1,8 @@
+<.header>
+ Edit Skill <%= @skill.name %>
+ <:subtitle>Use this form to manage skill records in your database.
+
+
+<.skill_form changeset={@changeset} action={~p"/skills/#{@skill}"} />
+
+<.back navigate={~p"/skills"}>Back to skills
diff --git a/apps/configurator/lib/configurator_web/controllers/skill_html/index.html.heex b/apps/configurator/lib/configurator_web/controllers/skill_html/index.html.heex
new file mode 100644
index 000000000..e7461f077
--- /dev/null
+++ b/apps/configurator/lib/configurator_web/controllers/skill_html/index.html.heex
@@ -0,0 +1,65 @@
+<.header>
+ Listing Config skills
+ <:actions>
+ <.link href={~p"/skills/new"}>
+ <.button>New Skill
+
+
+
+
+<.table id="skills" rows={@skills} row_click={&JS.navigate(~p"/skills/#{&1}")}>
+ <:col :let={skill} label="Name"><%= skill.name %>
+ <:col :let={skill} label="Activation delay (ms)"><%= skill.activation_delay_ms %>
+ <:col :let={skill} label="Autoaim"><%= skill.autoaim %>
+ <:col :let={skill} label="Block movement"><%= skill.block_movement %>
+ <:col :let={skill} label="Can pick destination"><%= skill.can_pick_destination %>
+ <:col :let={skill} label="Cooldown mechanism"><%= skill.cooldown_mechanism %>
+ <:col :let={skill} label="Cooldown (ms)"><%= skill.cooldown_ms %>
+ <:col :let={skill} label="Execution duration (ms)"><%= skill.execution_duration_ms %>
+ <:col :let={skill} label="Inmune while executing"><%= skill.inmune_while_executing %>
+ <:col :let={skill} label="Is passive"><%= skill.is_passive %>
+ <:col :let={skill} label="Max autoaim range"><%= skill.max_autoaim_range %>
+ <:col :let={skill} label="Stamina cost"><%= skill.stamina_cost %>
+ <:action :let={skill}>
+ <.button type="button" phx-click={show_modal("skill-mechanics-#{skill.id}")}>Mechanics
+ <.modal id={"skill-mechanics-#{skill.id}"}>
+ <.header>
+ Mechanics for the skill
+
+ <%= for mechanic <- skill.mechanics do %>
+ <.mechanic_show mechanic={mechanic} />
+
+ <%= if not is_nil(mechanic.on_arrival_mechanic) do %>
+ <.button type="button" phx-click={show_modal("on-arrival-mechanic-modal")}>Show on arrival mechanic
+ <.modal id="on-arrival-mechanic-modal">
+ <.header>
+ On arrival mechanic
+
+ <.mechanic_show mechanic={mechanic.on_arrival_mechanic} />
+
+ <% end %>
+
+ <.button type="button" phx-click={show_modal("on-explode-mechanics-modal")}>Show on explode mechanics
+ <.modal id="on-explode-mechanics-modal">
+ <.header>
+ On explode mechanics
+
+ <%= for mechanic <- mechanic.on_explode_mechanics do %>
+ <.mechanic_show mechanic={mechanic} />
+ <% end %>
+
+ <% end %>
+
+
+ <:action :let={skill}>
+
+ <.link navigate={~p"/skills/#{skill}"}>Show
+
+ <.link navigate={~p"/skills/#{skill}/edit"}>Edit
+
+ <:action :let={skill}>
+ <.link href={~p"/skills/#{skill}"} method="delete" data-confirm="Are you sure?">
+ Delete
+
+
+
diff --git a/apps/configurator/lib/configurator_web/controllers/skill_html/mechanic_show.html.heex b/apps/configurator/lib/configurator_web/controllers/skill_html/mechanic_show.html.heex
new file mode 100644
index 000000000..514fd4ee9
--- /dev/null
+++ b/apps/configurator/lib/configurator_web/controllers/skill_html/mechanic_show.html.heex
@@ -0,0 +1,16 @@
+<.list>
+ <:item title="Type"><%= @mechanic.type %>
+ <:item title="Name"><%= @mechanic.name %>
+ <:item title="Angle between"><%= @mechanic.angle_between %>
+ <:item title="Damage"><%= @mechanic.damage %>
+ <:item title="Duration (ms)"><%= @mechanic.duration_ms %>
+ <:item title="Interval (ms)"><%= @mechanic.interval_ms %>
+ <:item title="Move by"><%= @mechanic.move_by %>
+ <:item title="Offset"><%= @mechanic.offset %>
+ <:item title="Projectile offset"><%= @mechanic.projectile_offset %>
+ <:item title="Radius"><%= @mechanic.radius %>
+ <:item title="Range"><%= @mechanic.range %>
+ <:item title="Remove on collision"><%= @mechanic.remove_on_collision %>
+ <:item title="Speed"><%= @mechanic.speed %>
+ <:item title="Effects to apply"><%= @mechanic.effects_to_apply %>
+
diff --git a/apps/configurator/lib/configurator_web/controllers/skill_html/nested_mechanic_inputs.html.heex b/apps/configurator/lib/configurator_web/controllers/skill_html/nested_mechanic_inputs.html.heex
new file mode 100644
index 000000000..bd6f1a431
--- /dev/null
+++ b/apps/configurator/lib/configurator_web/controllers/skill_html/nested_mechanic_inputs.html.heex
@@ -0,0 +1,29 @@
+<.inputs_for :let={fp} field={@parent_form[@parent_field]}>
+ <.input
+ field={fp[:type]}
+ type="select"
+ label="Type"
+ prompt="Choose a value"
+ options={Ecto.Enum.values(GameBackend.Units.Skills.Mechanic, :type)}
+ />
+ <.input field={fp[:name]} type="text" label="Name" />
+ <.input field={fp[:amount]} type="number" label="Amount" />
+ <.input field={fp[:angle_between]} type="number" label="Angle between" />
+ <.input field={fp[:damage]} type="number" label="Damage" />
+ <.input field={fp[:duration_ms]} type="number" label="Duration (ms)" />
+ <.input field={fp[:interval_ms]} type="number" label="Interval (ms)" />
+ <.input field={fp[:move_by]} type="number" label="Move by" />
+ <.input field={fp[:offset]} type="number" label="Offset" />
+ <.input field={fp[:projectile_offset]} type="number" label="Projectile offset" />
+ <.input field={fp[:radius]} type="number" label="Radius" />
+ <.input field={fp[:range]} type="number" label="Range" />
+ <.input field={fp[:remove_on_collision]} type="checkbox" label="Remove on collision" />
+ <.input field={fp[:speed]} type="number" label="speed" />
+ <.input
+ field={fp[:effects_to_apply]}
+ type="select"
+ label="Effects to apply"
+ multiple
+ options={["singularity", "denial_of_service", "invisible"]}
+ />
+
diff --git a/apps/configurator/lib/configurator_web/controllers/skill_html/new.html.heex b/apps/configurator/lib/configurator_web/controllers/skill_html/new.html.heex
new file mode 100644
index 000000000..5dd46d2f8
--- /dev/null
+++ b/apps/configurator/lib/configurator_web/controllers/skill_html/new.html.heex
@@ -0,0 +1,8 @@
+<.header>
+ New Skill
+ <:subtitle>Use this form to manage skill records in your database.
+
+
+<.skill_form changeset={@changeset} action={~p"/skills"} />
+
+<.back navigate={~p"/skills"}>Back to skills
diff --git a/apps/configurator/lib/configurator_web/controllers/skill_html/show.html.heex b/apps/configurator/lib/configurator_web/controllers/skill_html/show.html.heex
new file mode 100644
index 000000000..6f329a416
--- /dev/null
+++ b/apps/configurator/lib/configurator_web/controllers/skill_html/show.html.heex
@@ -0,0 +1,53 @@
+<.header>
+ Skill: <%= @skill.name %>
+ <:subtitle>This is a skill record from your database.
+ <:actions>
+ <.link href={~p"/skills/#{@skill}/edit"}>
+ <.button>Edit skill
+
+
+
+
+<.list>
+ <:item title="Name"><%= @skill.name %>
+ <:item title="Activation delay (ms)"><%= @skill.activation_delay_ms %>
+ <:item title="Autoaim"><%= @skill.autoaim %>
+ <:item title="Block movement"><%= @skill.block_movement %>
+ <:item title="Can pick destination"><%= @skill.can_pick_destination %>
+ <:item title="Cooldown mechanism"><%= @skill.cooldown_mechanism %>
+ <:item title="Cooldown (ms)"><%= @skill.cooldown_ms %>
+ <:item title="Execution duration (ms)"><%= @skill.execution_duration_ms %>
+ <:item title="Inmune while executing"><%= @skill.inmune_while_executing %>
+ <:item title="Is passive"><%= @skill.is_passive %>
+ <:item title="Max autoaim range"><%= @skill.max_autoaim_range %>
+ <:item title="Stamina cost"><%= @skill.stamina_cost %>
+
+
+<.header>
+ Mechanics
+ <:subtitle>This are the mechanics for the skill
+
+
+<%= for mechanic <- @skill.mechanics do %>
+ <.mechanic_show mechanic={mechanic} />
+
+ <.button type="button" phx-click={show_modal("on-arrival-mechanic-modal")}>Show on arrival mechanic
+ <.modal id="on-arrival-mechanic-modal">
+ <.header>
+ On arrival mechanic
+
+ <.mechanic_show :if={not is_nil(mechanic.on_arrival_mechanic)} mechanic={mechanic.on_arrival_mechanic} />
+
+
+ <.button type="button" phx-click={show_modal("on-explode-mechanics-modal")}>Show on explode mechanics
+ <.modal id="on-explode-mechanics-modal">
+ <.header>
+ On explode mechanics
+
+ <%= for mechanic <- mechanic.on_explode_mechanics do %>
+ <.mechanic_show mechanic={mechanic} />
+ <% end %>
+
+<% end %>
+
+<.back navigate={~p"/skills"}>Back to skills
diff --git a/apps/configurator/lib/configurator_web/controllers/skill_html/skill_form.html.heex b/apps/configurator/lib/configurator_web/controllers/skill_html/skill_form.html.heex
new file mode 100644
index 000000000..b91b0f991
--- /dev/null
+++ b/apps/configurator/lib/configurator_web/controllers/skill_html/skill_form.html.heex
@@ -0,0 +1,36 @@
+<.simple_form :let={f} for={@changeset} action={@action}>
+ <.error :if={@changeset.action}>
+ Oops, something went wrong! Please check the errors below.
+
+ <.input field={f[:name]} type="text" label="Name" required />
+ <.input
+ field={f[:type]}
+ type="select"
+ label="Type"
+ prompt="Choose a value"
+ options={Ecto.Enum.values(GameBackend.Units.Skills.Skill, :type)}
+ />
+ <.input field={f[:activation_delay_ms]} type="number" label="Activation delay (ms)" />
+ <.input field={f[:autoaim]} type="checkbox" label="Autoaim" />
+ <.input field={f[:block_movement]} type="checkbox" label="Block movement" />
+ <.input field={f[:can_pick_destination]} type="checkbox" label="Can pick destination" />
+ <.input
+ field={f[:cooldown_mechanism]}
+ type="select"
+ label="Cooldown mechanism"
+ prompt="Choose a value"
+ options={Ecto.Enum.values(GameBackend.Units.Skills.Skill, :cooldown_mechanism)}
+ />
+ <.input field={f[:cooldown_ms]} type="number" label="Cooldown (ms)" />
+ <.input field={f[:execution_duration_ms]} type="number" label="Execution duration (ms)" />
+ <.input field={f[:inmune_while_executing]} type="checkbox" label="Inmune while executing" />
+ <.input field={f[:is_passive]} type="checkbox" label="Is passive" />
+ <.input field={f[:max_autoaim_range]} type="number" label="Max autoaim range" />
+ <.input field={f[:stamina_cost]} type="number" label="Stamina cost" />
+
+ <.skill_mechanic_inputs skill_form={f} />
+
+ <:actions>
+ <.button>Save Skill
+
+
diff --git a/apps/configurator/lib/configurator_web/controllers/skill_html/skill_mechanic_inputs.html.heex b/apps/configurator/lib/configurator_web/controllers/skill_html/skill_mechanic_inputs.html.heex
new file mode 100644
index 000000000..deda469a6
--- /dev/null
+++ b/apps/configurator/lib/configurator_web/controllers/skill_html/skill_mechanic_inputs.html.heex
@@ -0,0 +1,53 @@
+<.header>
+ Skill mechanic
+ <:subtitle>Manage the mechanic for the skill
+
+
+<.inputs_for :let={fp} field={@skill_form[:mechanics]}>
+ <.input
+ field={fp[:type]}
+ type="select"
+ label="Type"
+ prompt="Choose a value"
+ required
+ options={Ecto.Enum.values(GameBackend.Units.Skills.Mechanic, :type)}
+ />
+ <.input field={fp[:name]} type="text" label="Name" />
+ <.input field={fp[:amount]} type="number" label="Amount" />
+ <.input field={fp[:angle_between]} type="number" label="Angle between" />
+ <.input field={fp[:damage]} type="number" label="Damage" />
+ <.input field={fp[:duration_ms]} type="number" label="Duration (ms)" />
+ <.input field={fp[:interval_ms]} type="number" label="Interval (ms)" />
+ <.input field={fp[:move_by]} type="number" label="Move by" />
+ <.input field={fp[:offset]} type="number" label="Offset" />
+ <.input field={fp[:projectile_offset]} type="number" label="Projectile offset" />
+ <.input field={fp[:radius]} type="number" label="Radius" />
+ <.input field={fp[:range]} type="number" label="Range" />
+ <.input field={fp[:remove_on_collision]} type="checkbox" label="Remove on collision" />
+ <.input field={fp[:speed]} type="number" label="speed" />
+ <.input
+ field={fp[:effects_to_apply]}
+ type="select"
+ label="Effects to apply"
+ multiple
+ options={["singularity", "denial_of_service", "invisible"]}
+ />
+
+ <.button type="button" phx-click={show_modal("on-arrival-mechanic-modal")}>Edit on arrival mechanic
+ <.modal id="on-arrival-mechanic-modal">
+ <.header>
+ On arrival mechanic
+ <:subtitle>Details to use on mechanic when arriving
+
+ <.nested_mechanic_inputs parent_form={fp} parent_field={:on_arrival_mechanic} />
+
+
+ <.button type="button" phx-click={show_modal("on-explode-mechanics-modal")}>Edit on explode mechanics
+ <.modal id="on-explode-mechanics-modal">
+ <.header>
+ On explode mechanics
+ <:subtitle>Details to use on mechanic when exploding
+
+ <.nested_mechanic_inputs parent_form={fp} parent_field={:on_explode_mechanics} />
+
+
diff --git a/apps/configurator/lib/configurator_web/router.ex b/apps/configurator/lib/configurator_web/router.ex
index a4eaf46e5..03ae4e6bd 100644
--- a/apps/configurator/lib/configurator_web/router.ex
+++ b/apps/configurator/lib/configurator_web/router.ex
@@ -17,6 +17,11 @@ defmodule ConfiguratorWeb.Router do
plug :accepts, ["json"]
end
+ # Other scopes may use custom stacks.
+ # scope "/api", ConfiguratorWeb do
+ # pipe_through :api
+ # end
+
# Enable LiveDashboard in development
if Application.compile_env(:configurator, :dev_routes) do
# If you want to use the LiveDashboard in production, you should put
@@ -51,6 +56,7 @@ defmodule ConfiguratorWeb.Router do
get "/", HomeController, :home
resources "/characters", CharacterController
+ resources "/skills", SkillController
resources "/game_configurations", GameConfigurationController
end
diff --git a/apps/configurator/mix.exs b/apps/configurator/mix.exs
index 10317b64e..975f559e8 100644
--- a/apps/configurator/mix.exs
+++ b/apps/configurator/mix.exs
@@ -55,7 +55,8 @@ defmodule Configurator.MixProject do
{:telemetry_metrics, "~> 0.6"},
{:telemetry_poller, "~> 1.0"},
{:jason, "~> 1.2"},
- {:bandit, "~> 1.2"}
+ {:bandit, "~> 1.2"},
+ {:game_backend, in_umbrella: true}
]
end
diff --git a/apps/configurator/test/configurator_web/controllers/character_controller_test.exs b/apps/configurator/test/configurator_web/controllers/character_controller_test.exs
index 1cce30f3f..43a82c930 100644
--- a/apps/configurator/test/configurator_web/controllers/character_controller_test.exs
+++ b/apps/configurator/test/configurator_web/controllers/character_controller_test.exs
@@ -66,7 +66,7 @@ defmodule ConfiguratorWeb.CharacterControllerTest do
assert redirected_to(conn) == ~p"/characters/#{id}"
conn = get(conn, ~p"/characters/#{id}")
- assert html_response(conn, 200) =~ "Character #{id}"
+ assert html_response(conn, 200) =~ "Character #{@create_attrs[:name]}"
end
test "renders errors when data is invalid", %{conn: conn} do
diff --git a/apps/game_backend/lib/game_backend/units/characters.ex b/apps/game_backend/lib/game_backend/units/characters.ex
index 6f43ac616..36172756e 100644
--- a/apps/game_backend/lib/game_backend/units/characters.ex
+++ b/apps/game_backend/lib/game_backend/units/characters.ex
@@ -88,7 +88,7 @@ defmodule GameBackend.Units.Characters do
iex> get_character(wrong_id)
{:error, :not_found}
"""
- def get_character(id), do: Repo.get(Character, id) |> Repo.preload([:basic_skill, :ultimate_skill])
+ def get_character(id), do: Repo.get(Character, id) |> Repo.preload([:basic_skill, :ultimate_skill, :dash_skill])
@doc """
Get all Characters.
@@ -173,6 +173,17 @@ defmodule GameBackend.Units.Characters do
"""
def get_curse_characters() do
curse_id = GameBackend.Utils.get_game_id(:curse_of_mirra)
- Repo.all(from(c in Character, where: ^curse_id == c.game_id))
+
+ q =
+ from(c in Character,
+ where: ^curse_id == c.game_id,
+ preload: [
+ basic_skill: [mechanics: [:on_arrival_mechanic, :on_explode_mechanics, :parent_mechanic]],
+ ultimate_skill: [mechanics: [:on_arrival_mechanic, :on_explode_mechanics, :parent_mechanic]],
+ dash_skill: [mechanics: [:on_arrival_mechanic, :on_explode_mechanics, :parent_mechanic]]
+ ]
+ )
+
+ Repo.all(q)
end
end
diff --git a/apps/game_backend/lib/game_backend/units/characters/character.ex b/apps/game_backend/lib/game_backend/units/characters/character.ex
index 0d06ec816..3cd8dd342 100644
--- a/apps/game_backend/lib/game_backend/units/characters/character.ex
+++ b/apps/game_backend/lib/game_backend/units/characters/character.ex
@@ -8,22 +8,6 @@ defmodule GameBackend.Units.Characters.Character do
alias GameBackend.Units.Skills.Skill
- @derive {Jason.Encoder,
- only: [
- :active,
- :name,
- :base_attack,
- :base_health,
- :base_defense,
- :base_stamina,
- :stamina_interval,
- :max_inventory_size,
- :natural_healing_interval,
- :natural_healing_damage_interval,
- :base_speed,
- :base_size,
- :skills
- ]}
schema "characters" do
field(:game_id, :integer)
field(:active, :boolean, default: true)
@@ -46,11 +30,9 @@ defmodule GameBackend.Units.Characters.Character do
field(:base_speed, :float)
field(:base_size, :float)
- # TODO This should be removed once we have the skills relationship, issue: https://github.com/lambdaclass/mirra_backend/issues/717
- field(:skills, {:map, :string})
-
belongs_to(:basic_skill, Skill, on_replace: :update)
belongs_to(:ultimate_skill, Skill, on_replace: :update)
+ belongs_to(:dash_skill, Skill, on_replace: :update)
timestamps()
end
@@ -79,8 +61,10 @@ defmodule GameBackend.Units.Characters.Character do
:max_inventory_size,
:natural_healing_interval,
:natural_healing_damage_interval,
- :skills,
- :base_defense
+ :base_defense,
+ :basic_skill_id,
+ :dash_skill_id,
+ :ultimate_skill_id
])
|> cast_assoc(:basic_skill)
|> cast_assoc(:ultimate_skill)
diff --git a/apps/game_backend/lib/game_backend/units/skills.ex b/apps/game_backend/lib/game_backend/units/skills.ex
index 17b7b6544..405454c91 100644
--- a/apps/game_backend/lib/game_backend/units/skills.ex
+++ b/apps/game_backend/lib/game_backend/units/skills.ex
@@ -53,4 +53,65 @@ defmodule GameBackend.Units.Skills do
detail_type in Mechanic.mechanic_types() and mechanic[detail_type] != nil
end)
end
+
+ def list_curse_skills() do
+ curse_id = GameBackend.Utils.get_game_id(:curse_of_mirra)
+
+ q =
+ from(s in Skill,
+ where: ^curse_id == s.game_id,
+ preload: [mechanics: [:on_arrival_mechanic, :on_explode_mechanics]]
+ )
+
+ Repo.all(q)
+ end
+
+ @doc """
+ Gets a single skill.
+
+ Raises `Ecto.NoResultsError` if the Skill does not exist.
+
+ ## Examples
+
+ iex> get_skill!(123)
+ %Skill{}
+
+ iex> get_skill!(456)
+ ** (Ecto.NoResultsError)
+
+ """
+ def get_skill!(id) do
+ Repo.get!(Skill, id)
+ |> Repo.preload(mechanics: [:on_arrival_mechanic, :on_explode_mechanics])
+ end
+
+ @doc """
+ Deletes a skill.
+
+ ## Examples
+
+ iex> delete_skill(skill)
+ {:ok, %Skill{}}
+
+ iex> delete_skill(skill)
+ {:error, %Ecto.Changeset{}}
+
+ """
+ def delete_skill(%Skill{} = skill) do
+ Skill.changeset(skill)
+ |> Repo.delete()
+ end
+
+ @doc """
+ Returns an `%Ecto.Changeset{}` for tracking skill changes.
+
+ ## Examples
+
+ iex> change_skill(skill)
+ %Ecto.Changeset{data: %Skill{}}
+
+ """
+ def change_skill(%Skill{} = skill, attrs \\ %{}) do
+ Skill.changeset(skill, attrs)
+ end
end
diff --git a/apps/game_backend/lib/game_backend/units/skills/mechanic.ex b/apps/game_backend/lib/game_backend/units/skills/mechanic.ex
index 86b643e21..65f813127 100644
--- a/apps/game_backend/lib/game_backend/units/skills/mechanic.ex
+++ b/apps/game_backend/lib/game_backend/units/skills/mechanic.ex
@@ -5,37 +5,80 @@ defmodule GameBackend.Units.Skills.Mechanic do
import Ecto.Changeset
alias GameBackend.Units.Skills.Skill
- alias GameBackend.Units.Skills.Mechanics.{ApplyEffectsTo, PassiveEffect}
+ alias GameBackend.Units.Skills.Mechanics.{ApplyEffectsTo, PassiveEffect, OnCollideEffects}
schema "mechanics" do
+ field(:amount, :integer)
+ field(:angle_between, :decimal)
+ field(:damage, :integer)
+ field(:duration_ms, :integer)
+ field(:effects_to_apply, {:array, :string})
+ field(:interval_ms, :integer)
+ field(:move_by, :decimal)
+ field(:name, :string)
+ field(:offset, :integer)
+ field(:projectile_offset, :integer)
+ field(:radius, :decimal)
+ field(:range, :decimal)
+ field(:remove_on_collision, :boolean, default: false)
+ field(:speed, :decimal)
+ field(:activation_delay, :integer)
field(:trigger_delay, :integer)
- belongs_to(:skill, Skill)
+ field(:type, Ecto.Enum,
+ values: [:circle_hit, :spawn_pool, :leap, :multi_shoot, :dash, :multi_circle_hit, :teleport, :simple_shoot]
+ )
+
+ belongs_to(:skill, Skill)
belongs_to(:apply_effects_to, ApplyEffectsTo)
- # Not yet implemented, added to define how different Mechanic types will be handled
+ has_many(:on_explode_mechanics, __MODULE__, foreign_key: :parent_mechanic_id)
belongs_to(:passive_effects, PassiveEffect)
+ belongs_to(:on_arrival_mechanic, __MODULE__)
+ belongs_to(:parent_mechanic, __MODULE__, foreign_key: :parent_mechanic_id)
+ embeds_one(:on_collide_effects, OnCollideEffects)
end
+ def mechanic_types(), do: [:apply_effects_to, :passive_effects]
+
@doc false
def changeset(mechanic, attrs \\ %{}) do
mechanic
- |> cast(attrs, [:trigger_delay, :skill_id])
+ |> cast(attrs, [
+ :trigger_delay,
+ :on_arrival_mechanic_id,
+ :parent_mechanic_id,
+ :skill_id,
+ :type,
+ :amount,
+ :angle_between,
+ :damage,
+ :duration_ms,
+ :effects_to_apply,
+ :interval_ms,
+ :move_by,
+ :name,
+ :offset,
+ :projectile_offset,
+ :radius,
+ :range,
+ :remove_on_collision,
+ :activation_delay,
+ :speed
+ ])
|> cast_assoc(:apply_effects_to)
|> cast_assoc(:passive_effects)
- |> validate_only_one_type()
- |> validate_required([:trigger_delay])
+ |> cast_assoc(:parent_mechanic, with: &assoc_changeset/2)
+ |> cast_assoc(:on_arrival_mechanic, with: &assoc_changeset/2)
+ |> cast_assoc(:on_explode_mechanics, with: &assoc_changeset/2)
+ |> cast_embed(:on_collide_effects)
end
- defp validate_only_one_type(changeset) do
- if Enum.count(mechanic_types(), fn type -> Map.has_key?(changeset.changes, type) end) == 1,
- do: changeset,
- else:
- add_error(
- changeset,
- hd(mechanic_types()),
- "Exactly 1 of these fields must be present: #{inspect(mechanic_types())}"
- )
- end
+ defp assoc_changeset(struct, params) do
+ changeset = changeset(struct, params)
- def mechanic_types(), do: [:apply_effects_to, :passive_effects]
+ case get_field(changeset, :type) do
+ nil -> %{changeset | action: :ignore}
+ _ -> changeset
+ end
+ end
end
diff --git a/apps/game_backend/lib/game_backend/units/skills/mechanics/on_collide_effects.ex b/apps/game_backend/lib/game_backend/units/skills/mechanics/on_collide_effects.ex
new file mode 100644
index 000000000..7e7979cc6
--- /dev/null
+++ b/apps/game_backend/lib/game_backend/units/skills/mechanics/on_collide_effects.ex
@@ -0,0 +1,21 @@
+defmodule GameBackend.Units.Skills.Mechanics.OnCollideEffects do
+ @moduledoc """
+ A schema that defines the strategy for how a mechanic will choose its targets.
+ """
+
+ use GameBackend.Schema
+ import Ecto.Changeset
+
+ @derive Jason.Encoder
+ @primary_key false
+ embedded_schema do
+ field(:apply_effect_to_entity_type, {:array, :string})
+ field(:effects, {:array, :string})
+ end
+
+ @doc false
+ def changeset(targeting_strategy, attrs \\ %{}) do
+ targeting_strategy
+ |> cast(attrs, [:apply_effect_to_entity_type, :effects])
+ 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 cb6aea8e9..377e801c1 100644
--- a/apps/game_backend/lib/game_backend/units/skills/skill.ex
+++ b/apps/game_backend/lib/game_backend/units/skills/skill.ex
@@ -9,12 +9,26 @@ defmodule GameBackend.Units.Skills.Skill do
schema "skills" do
field(:name, :string)
- has_many(:mechanics, Mechanic, on_replace: :delete)
+ field(:game_id, :integer)
field(:cooldown, :integer)
field(:energy_regen, :integer)
field(:animation_duration, :integer)
+ field(:activation_delay_ms, :integer)
+ field(:autoaim, :boolean, default: false)
+ field(:block_movement, :boolean, default: false)
+ field(:can_pick_destination, :boolean, default: false)
+ field(:cooldown_mechanism, Ecto.Enum, values: [:stamina, :time])
+ field(:cooldown_ms, :integer)
+ field(:execution_duration_ms, :integer)
+ field(:inmune_while_executing, :boolean, default: false)
+ field(:is_passive, :boolean, default: false)
+ field(:max_autoaim_range, :integer)
+ field(:stamina_cost, :integer)
+ field(:effects_to_apply, {:array, :string})
+ field(:type, Ecto.Enum, values: [:basic, :dash, :ultimate])
belongs_to(:buff, Buff)
+ has_many(:mechanics, Mechanic, on_replace: :delete)
timestamps()
end
@@ -22,7 +36,29 @@ defmodule GameBackend.Units.Skills.Skill do
@doc false
def changeset(skill, attrs \\ %{}) do
skill
- |> cast(attrs, [:name, :cooldown, :energy_regen, :animation_duration, :buff_id])
+ |> cast(attrs, [
+ :name,
+ :game_id,
+ :cooldown,
+ :energy_regen,
+ :animation_duration,
+ :buff_id,
+ :activation_delay_ms,
+ :autoaim,
+ :block_movement,
+ :can_pick_destination,
+ :cooldown_mechanism,
+ :cooldown_ms,
+ :execution_duration_ms,
+ :inmune_while_executing,
+ :is_passive,
+ :max_autoaim_range,
+ :stamina_cost,
+ :effects_to_apply,
+ :type
+ ])
|> cast_assoc(:mechanics)
+ |> unique_constraint([:game_id, :name])
+ |> foreign_key_constraint(:characters, name: "characters_basic_skill_id_fkey")
end
end
diff --git a/apps/game_backend/priv/repo/migrations/20240626175150_add_config_skills_and_mechanics.exs b/apps/game_backend/priv/repo/migrations/20240626175150_add_config_skills_and_mechanics.exs
new file mode 100644
index 000000000..b1dc83314
--- /dev/null
+++ b/apps/game_backend/priv/repo/migrations/20240626175150_add_config_skills_and_mechanics.exs
@@ -0,0 +1,55 @@
+defmodule Configurator.Repo.Migrations.AddConfigSkillsAndMechanics do
+ use Ecto.Migration
+
+ def change do
+ alter table(:skills) do
+ add :game_id, :integer
+ add :activation_delay_ms, :integer
+ add :autoaim, :boolean, default: false, null: false
+ add :block_movement, :boolean, default: false, null: false
+ add :can_pick_destination, :boolean, default: false, null: false
+ add :cooldown_mechanism, :string
+ add :cooldown_ms, :integer
+ add :execution_duration_ms, :integer
+ add :inmune_while_executing, :boolean, default: false, null: false
+ add :is_passive, :boolean, default: false, null: false
+ add :max_autoaim_range, :integer
+ add :stamina_cost, :integer
+ add :type, :string
+ add :effects_to_apply, {:array, :string}
+ end
+
+ create unique_index(:skills, [:game_id, :name])
+
+ alter table(:mechanics) do
+ add :type, :string
+ add :amount, :integer
+ add :angle_between, :decimal
+ add :damage, :integer
+ add :duration_ms, :integer
+ add :effects_to_apply, {:array, :string}
+ add :interval_ms, :integer
+ add :move_by, :decimal
+ add :name, :string
+ add :offset, :integer
+ add :projectile_offset, :integer
+ add :radius, :decimal
+ add :range, :decimal
+ add :remove_on_collision, :boolean, default: false, null: false
+ add :speed, :decimal
+ add :activation_delay, :integer
+ add :on_arrival_mechanic_id, references(:mechanics, on_delete: :nothing)
+ add :parent_mechanic_id, references(:mechanics, on_delete: :nothing)
+ add :on_collide_effects, :map
+ modify :skill_id, references(:skills, on_delete: :nilify_all), from: references(:skills)
+ end
+
+ create index(:mechanics, [:on_arrival_mechanic_id])
+ create index(:mechanics, [:parent_mechanic_id])
+
+ alter table :characters do
+ remove :skills
+ add :dash_skill_id, references(:skills, on_delete: :delete_all)
+ end
+ end
+end
diff --git a/apps/gateway/lib/gateway/controllers/curse_of_mirra/configuration_controller.ex b/apps/gateway/lib/gateway/controllers/curse_of_mirra/configuration_controller.ex
index 55c5da43a..afba0f1a7 100644
--- a/apps/gateway/lib/gateway/controllers/curse_of_mirra/configuration_controller.ex
+++ b/apps/gateway/lib/gateway/controllers/curse_of_mirra/configuration_controller.ex
@@ -4,11 +4,12 @@ defmodule Gateway.Controllers.CurseOfMirra.ConfigurationController do
"""
use Gateway, :controller
alias GameBackend.Configuration
+ alias GameBackend.Units.Characters
action_fallback Gateway.Controllers.FallbackController
def get_characters_configuration(conn, _params) do
- case GameBackend.Units.Characters.get_curse_characters() do
+ case Characters.get_curse_characters() do
[] ->
{:error, :not_found}
@@ -16,7 +17,7 @@ defmodule Gateway.Controllers.CurseOfMirra.ConfigurationController do
send_resp(
conn,
200,
- Jason.encode!(characters)
+ Jason.encode!(encode_characters(characters))
)
end
end
@@ -25,4 +26,69 @@ defmodule Gateway.Controllers.CurseOfMirra.ConfigurationController do
game_configuration = Configuration.get_latest_game_configuration()
send_resp(conn, 200, Jason.encode!(game_configuration))
end
+
+ defp encode_characters(characters) when is_list(characters) do
+ Enum.map(characters, fn character ->
+ character
+ |> Map.put(:basic_skill, encode_skill(character.basic_skill))
+ |> Map.put(:ultimate_skill, encode_skill(character.ultimate_skill))
+ |> Map.put(:dash_skill, encode_skill(character.dash_skill))
+ |> ecto_struct_to_map()
+ end)
+ end
+
+ defp encode_skill(skill) do
+ skill
+ |> Map.put(:mechanics, encode_mechanics(skill.mechanics))
+ |> ecto_struct_to_map()
+ |> Map.drop([:buff])
+ end
+
+ defp encode_mechanics(mechanics) when is_list(mechanics) do
+ Enum.map(mechanics, &encode_mechanic/1)
+ end
+
+ defp encode_mechanic(nil) do
+ nil
+ end
+
+ defp encode_mechanic(mechanic) do
+ # This is done to avoid the infinite nested mechanics loop
+ # Once we enter on a mechanic, we don't want to go deeper (yet).
+ on_explode_mechanics =
+ Enum.map(mechanic.on_explode_mechanics, fn explode_mechanic ->
+ explode_mechanic
+ |> Map.put(:on_arrival_mechanic, nil)
+ |> Map.put(:on_explode_mechanics, [])
+ |> Map.put(:parent_mechanic, nil)
+ end)
+
+ on_arrival_mechanic =
+ if mechanic.on_arrival_mechanic do
+ mechanic.on_arrival_mechanic
+ |> Map.put(:on_arrival_mechanic, nil)
+ |> Map.put(:on_explode_mechanics, [])
+ |> Map.put(:parent_mechanic, nil)
+ else
+ nil
+ end
+
+ mechanic
+ |> Map.put(:on_arrival_mechanic, mechanic.on_arrival_mechanic_id && encode_mechanic(on_arrival_mechanic))
+ |> Map.put(:on_explode_mechanics, encode_mechanics(on_explode_mechanics))
+ |> ecto_struct_to_map()
+ |> Map.drop([:skill, :apply_effects_to, :passive_effects])
+ end
+
+ defp ecto_struct_to_map(ecto_struct) when is_struct(ecto_struct) do
+ ecto_struct
+ |> Map.from_struct()
+ |> Map.drop([
+ :__meta__,
+ :__struct__,
+ :inserted_at,
+ :updated_at,
+ :id
+ ])
+ end
end
diff --git a/config/test.exs b/config/test.exs
index 6b15c48bb..c1c297883 100644
--- a/config/test.exs
+++ b/config/test.exs
@@ -16,6 +16,20 @@ config :logger, level: :warning
# Initialize plugs at runtime for faster test compilation
config :phoenix, :plug_init_mode, :runtime
+config :joken,
+ default_signer: [
+ signer_alg: "Ed25519",
+ key_openssh: """
+ -----BEGIN OPENSSH PRIVATE KEY-----
+ b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+ QyNTUxOQAAACDVgskcQdNGPgcP9UJIwA6AB1FUnvCyO19dChVY3EFuZQAAAKDVn3NU1Z9z
+ VAAAAAtzc2gtZWQyNTUxOQAAACDVgskcQdNGPgcP9UJIwA6AB1FUnvCyO19dChVY3EFuZQ
+ AAAECOw1cqNcGfb/U3HgERb+cujt5dvVM+QzIWMMEWeaua5NWCyRxB00Y+Bw/1QkjADoAH
+ UVSe8LI7X10KFVjcQW5lAAAAF2FyZW5hQGdhdGV3YXkubWlycmEuZGV2AQIDBAUG
+ -----END OPENSSH PRIVATE KEY-----
+ """
+ ]
+
############################
# App configuration: arena #
############################
diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs
index 6a17edb86..2d19c9ade 100644
--- a/priv/repo/seeds.exs
+++ b/priv/repo/seeds.exs
@@ -1,3 +1,4 @@
+alias GameBackend.Units.Skills
alias GameBackend.{Gacha, Repo, Users, Utils}
alias GameBackend.Campaigns.Rewards.AfkRewardRate
alias GameBackend.Users.{KalineTreeLevel, Upgrade}
@@ -229,6 +230,323 @@ Champions.Config.import_dungeon_levels_config()
%{email: "admin@configurator.com", password: "letmepass1234"}
|> Configurator.Accounts.register_user()
+## Skills
+skills = [
+ %{
+ "name" => "muflus_crush",
+ "type" => "basic",
+ "cooldown_mechanism" => "stamina",
+ "execution_duration_ms" => 450,
+ "activation_delay_ms" => 150,
+ "is_passive" => false,
+ "autoaim" => true,
+ "max_autoaim_range" => 700,
+ "stamina_cost" => 1,
+ "can_pick_destination" => false,
+ "block_movement" => true,
+ "mechanics" => [
+ %{
+ "type" => "circle_hit",
+ "damage" => 64,
+ "range" => 350.0,
+ "offset" => 400
+ }
+ ],
+ "effects_to_apply" => []
+ },
+ %{
+ "name" => "muflus_leap",
+ "type" => "ultimate",
+ "cooldown_mechanism" => "time",
+ "cooldown_ms" => 8000,
+ "execution_duration_ms" => 800,
+ "activation_delay_ms" => 200,
+ "is_passive" => false,
+ "autoaim" => true,
+ "max_autoaim_range" => 1300,
+ "can_pick_destination" => true,
+ "block_movement" => true,
+ "mechanics" => [
+ %{
+ "type" => "leap",
+ "range" => 1300.0,
+ "speed" => 1.7,
+ "radius" => 600,
+ "on_arrival_mechanic" => %{
+ "type" => "circle_hit",
+ "damage" => 92,
+ "range" => 600.0,
+ "offset" => 0
+ }
+ }
+ ]
+ },
+ %{
+ "name" => "muflus_dash",
+ "type" => "dash",
+ "cooldown_mechanism" => "time",
+ "cooldown_ms" => 4500,
+ "execution_duration_ms" => 330,
+ "activation_delay_ms" => 0,
+ "is_passive" => false,
+ "autoaim" => false,
+ "max_autoaim_range" => 0,
+ "can_pick_destination" => false,
+ "block_movement" => true,
+ "mechanics" => [
+ %{
+ "type" => "dash",
+ "speed" => 3.3,
+ "duration_ms" => 330
+ }
+ ]
+ },
+ %{
+ "name" => "h4ck_slingshot",
+ "type" => "basic",
+ "cooldown_mechanism" => "stamina",
+ "execution_duration_ms" => 250,
+ "activation_delay_ms" => 0,
+ "is_passive" => false,
+ "autoaim" => true,
+ "max_autoaim_range" => 1300,
+ "stamina_cost" => 1,
+ "can_pick_destination" => false,
+ "block_movement" => true,
+ "mechanics" => [
+ %{
+ "type" => "multi_shoot",
+ "angle_between" => 22.0,
+ "amount" => 3,
+ "speed" => 1.1,
+ "duration_ms" => 1000,
+ "remove_on_collision" => true,
+ "projectile_offset" => 100,
+ "damage" => 44,
+ "radius" => 40.0
+ }
+ ],
+ "effects_to_apply" => []
+ },
+ %{
+ "name" => "h4ck_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" => [
+ %{
+ "type" => "dash",
+ "speed" => 4.0,
+ "duration_ms" => 250
+ }
+ ]
+ },
+ %{
+ "name" => "h4ck_denial_of_service",
+ "type" => "ultimate",
+ "cooldown_mechanism" => "time",
+ "cooldown_ms" => 9000,
+ "execution_duration_ms" => 200,
+ "activation_delay_ms" => 300,
+ "is_passive" => false,
+ "autoaim" => true,
+ "max_autoaim_range" => 1200,
+ "can_pick_destination" => true,
+ "block_movement" => true,
+ "mechanics" => [
+ %{
+ "type" => "spawn_pool",
+ "name" => "denial_of_service",
+ "activation_delay" => 250,
+ "duration_ms" => 2500,
+ "radius" => 500.0,
+ "range" => 1200.0,
+ "effects_to_apply" => [
+ "denial_of_service"
+ ]
+ }
+ ],
+ "effects_to_apply" => []
+ },
+ %{
+ "name" => "uma_avenge",
+ "type" => "basic",
+ "cooldown_mechanism" => "stamina",
+ "execution_duration_ms" => 500,
+ "activation_delay_ms" => 0,
+ "is_passive" => false,
+ "autoaim" => true,
+ "max_autoaim_range" => 650,
+ "stamina_cost" => 1,
+ "can_pick_destination" => false,
+ "block_movement" => true,
+ "mechanics" => [
+ %{
+ "type" => "multi_circle_hit",
+ "damage" => 22,
+ "range" => 280.0,
+ "interval_ms" => 200,
+ "amount" => 3,
+ "offset" => 200
+ }
+ ],
+ "effects_to_apply" => []
+ },
+ %{
+ "name" => "uma_veil_radiance",
+ "type" => "ultimate",
+ "cooldown_mechanism" => "time",
+ "cooldown_ms" => 9000,
+ "execution_duration_ms" => 300,
+ "activation_delay_ms" => 150,
+ "is_passive" => false,
+ "autoaim" => true,
+ "max_autoaim_range" => 0,
+ "can_pick_destination" => false,
+ "block_movement" => true,
+ "mechanics" => [
+ %{
+ "type" => "circle_hit",
+ "damage" => 80,
+ "range" => 800.0,
+ "offset" => 0
+ }
+ ],
+ "effects_to_apply" => [
+ "invisible"
+ ]
+ },
+ %{
+ "name" => "uma_sneak",
+ "type" => "dash",
+ "cooldown_mechanism" => "time",
+ "cooldown_ms" => 5000,
+ "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" => [
+ %{
+ "type" => "dash",
+ "speed" => 4.0,
+ "duration_ms" => 250
+ }
+ ],
+ "effects_to_apply" => []
+ },
+ %{
+ "name" => "valt_singularity",
+ "type" => "ultimate",
+ "cooldown_mechanism" => "time",
+ "cooldown_ms" => 9000,
+ "execution_duration_ms" => 500,
+ "activation_delay_ms" => 300,
+ "is_passive" => false,
+ "autoaim" => true,
+ "max_autoaim_range" => 1200,
+ "can_pick_destination" => true,
+ "block_movement" => true,
+ "mechanics" => [
+ %{
+ "type" => "spawn_pool",
+ "name" => "singularity",
+ "activation_delay" => 400,
+ "duration_ms" => 5000,
+ "radius" => 450.0,
+ "range" => 1200.0,
+ "effects_to_apply" => [
+ "singularity"
+ ]
+ }
+ ],
+ "effects_to_apply" => []
+ },
+ %{
+ "name" => "valt_warp",
+ "type" => "dash",
+ "cooldown_mechanism" => "time",
+ "cooldown_ms" => 6000,
+ "execution_duration_ms" => 450,
+ "inmune_while_executing" => true,
+ "activation_delay_ms" => 300,
+ "is_passive" => false,
+ "autoaim" => false,
+ "max_autoaim_range" => 0,
+ "can_pick_destination" => true,
+ "block_movement" => true,
+ "stamina_cost" => 1,
+ "mechanics" => [
+ %{
+ "type" => "teleport",
+ "range" => 1100,
+ "duration_ms" => 150
+ }
+ ],
+ "effects_to_apply" => []
+ },
+ %{
+ "name" => "valt_antimatter",
+ "type" => "basic",
+ "cooldown_mechanism" => "stamina",
+ "execution_duration_ms" => 450,
+ "activation_delay_ms" => 150,
+ "is_passive" => false,
+ "autoaim" => true,
+ "max_autoaim_range" => 1300,
+ "stamina_cost" => 1,
+ "can_pick_destination" => false,
+ "block_movement" => true,
+ "mechanics" => [
+ %{
+ "type" => "simple_shoot",
+ "speed" => 1.8,
+ "duration_ms" => 1100,
+ "remove_on_collision" => true,
+ "projectile_offset" => 100,
+ "radius" => 100.0,
+ "damage" => 0,
+ "on_explode_mechanics" => [
+ %{
+ "type" => "circle_hit",
+ "damage" => 58,
+ "range" => 250.0,
+ "offset" => 0
+ }
+ ],
+ "on_collide_effects" => %{
+ "apply_effect_to_entity_type" => [
+ "pool"
+ ],
+ "effects" => [
+ "buff_singularity"
+ ]
+ }
+ }
+ ],
+ "effects_to_apply" => []
+ }
+]
+
+skills =
+ Enum.map(skills, fn skill_params ->
+ {:ok, skill} =
+ Map.put(skill_params, "game_id", curse_of_mirra_id)
+ |> Skills.insert_skill()
+
+ {skill.name, skill.id}
+ end)
+ |> Map.new()
+
# Characters params
muflus_params = %{
name: "muflus",
@@ -241,11 +559,9 @@ muflus_params = %{
max_inventory_size: 1,
natural_healing_interval: 1000,
natural_healing_damage_interval: 3500,
- skills: %{
- "1": "muflus_crush",
- "2": "muflus_leap",
- "3": "muflus_dash"
- }
+ basic_skill_id: skills["muflus_crush"],
+ ultimate_skill_id: skills["muflus_leap"],
+ dash_skill_id: skills["muflus_dash"]
}
h4ck_params = %{
@@ -259,11 +575,9 @@ h4ck_params = %{
max_inventory_size: 1,
natural_healing_interval: 1000,
natural_healing_damage_interval: 3500,
- skills: %{
- "1": "h4ck_slingshot",
- "2": "h4ck_denial_of_service",
- "3": "h4ck_dash"
- }
+ basic_skill_id: skills["h4ck_slingshot"],
+ ultimate_skill_id: skills["h4ck_denial_of_service"],
+ dash_skill_id: skills["h4ck_dash"]
}
uma_params = %{
@@ -277,11 +591,9 @@ uma_params = %{
max_inventory_size: 1,
natural_healing_interval: 1000,
natural_healing_damage_interval: 3500,
- skills: %{
- "1": "uma_avenge",
- "2": "uma_veil_radiance",
- "3": "uma_sneak"
- }
+ basic_skill_id: skills["uma_avenge"],
+ ultimate_skill_id: skills["uma_veil_radiance"],
+ dash_skill_id: skills["uma_sneak"]
}
valtimer_params = %{
@@ -295,11 +607,9 @@ valtimer_params = %{
max_inventory_size: 1,
natural_healing_interval: 1000,
natural_healing_damage_interval: 3500,
- skills: %{
- "1": "valt_antimatter",
- "2": "valt_singularity",
- "3": "valt_warp"
- }
+ basic_skill_id: skills["valt_antimatter"],
+ ultimate_skill_id: skills["valt_singularity"],
+ dash_skill_id: skills["valt_warp"]
}
# Insert characters