diff --git a/.github/workflows/arena-brazil-testing-deploy.yml b/.github/workflows/arena-brazil-testing-deploy.yml index 3f8538824..34af9b9c7 100644 --- a/.github/workflows/arena-brazil-testing-deploy.yml +++ b/.github/workflows/arena-brazil-testing-deploy.yml @@ -55,6 +55,7 @@ jobs: SECRET_KEY_BASE: ${{ secrets.SECRET_KEY_BASE }} NEWRELIC_APP_NAME: ${{ vars.NEWRELIC_APP_NAME }} NEWRELIC_KEY: ${{ secrets.NEWRELIC_KEY }} + DBUS_SESSION_BUS_ADDRESS: ${{ vars.DBUS_SESSION_BUS_ADDRESS }} BRANCH_NAME: ${{ github.head_ref || github.ref_name }} run: | set -ex @@ -72,4 +73,5 @@ jobs: SECRET_KEY_BASE=${SECRET_KEY_BASE} \ NEWRELIC_APP_NAME=${NEWRELIC_APP_NAME} \ NEWRELIC_KEY=${NEWRELIC_KEY} \ + DBUS_SESSION_BUS_ADDRESS=${DBUS_SESSION_BUS_ADDRESS} \ /home/${SSH_USERNAME}/deploy-script/deploy.sh diff --git a/.github/workflows/arena-europe-testing-deploy.yml b/.github/workflows/arena-europe-testing-deploy.yml index dc9fd66fc..fcb3b1ba4 100644 --- a/.github/workflows/arena-europe-testing-deploy.yml +++ b/.github/workflows/arena-europe-testing-deploy.yml @@ -55,6 +55,7 @@ jobs: SECRET_KEY_BASE: ${{ secrets.SECRET_KEY_BASE }} NEWRELIC_APP_NAME: ${{ vars.NEWRELIC_APP_NAME }} NEWRELIC_KEY: ${{ secrets.NEWRELIC_KEY }} + DBUS_SESSION_BUS_ADDRESS: ${{ vars.DBUS_SESSION_BUS_ADDRESS }} BRANCH_NAME: ${{ github.head_ref || github.ref_name }} run: | set -ex @@ -72,4 +73,5 @@ jobs: SECRET_KEY_BASE=${SECRET_KEY_BASE} \ NEWRELIC_APP_NAME=${NEWRELIC_APP_NAME} \ NEWRELIC_KEY=${NEWRELIC_KEY} \ + DBUS_SESSION_BUS_ADDRESS=${DBUS_SESSION_BUS_ADDRESS} \ /home/${SSH_USERNAME}/deploy-script/deploy.sh diff --git a/.github/workflows/central-europe-testing-deploy.yml b/.github/workflows/central-europe-testing-deploy.yml index 86831943a..3fe183678 100644 --- a/.github/workflows/central-europe-testing-deploy.yml +++ b/.github/workflows/central-europe-testing-deploy.yml @@ -56,6 +56,7 @@ jobs: JWT_PRIVATE_KEY_BASE_64: ${{ secrets.JWT_PRIVATE_KEY_BASE_64 }} NEWRELIC_APP_NAME: ${{ vars.NEWRELIC_APP_NAME }} NEWRELIC_KEY: ${{ secrets.NEWRELIC_KEY }} + DBUS_SESSION_BUS_ADDRESS: ${{ vars.DBUS_SESSION_BUS_ADDRESS }} BRANCH_NAME: ${{ github.head_ref || github.ref_name }} run: | set -ex @@ -74,4 +75,5 @@ jobs: JWT_PRIVATE_KEY_BASE_64=${JWT_PRIVATE_KEY_BASE_64} \ NEWRELIC_APP_NAME=${NEWRELIC_APP_NAME} \ NEWRELIC_KEY=${NEWRELIC_KEY} \ + DBUS_SESSION_BUS_ADDRESS=${DBUS_SESSION_BUS_ADDRESS} \ /home/${SSH_USERNAME}/deploy-script/deploy.sh diff --git a/.github/workflows/loadtest-brazil-client-deploy.yml b/.github/workflows/loadtest-brazil-client-deploy.yml index 46372ea56..b9cab31ce 100644 --- a/.github/workflows/loadtest-brazil-client-deploy.yml +++ b/.github/workflows/loadtest-brazil-client-deploy.yml @@ -71,6 +71,7 @@ jobs: SECRET_KEY_BASE: ${{ secrets.SECRET_KEY_BASE }} NEWRELIC_APP_NAME: ${{ vars.NEWRELIC_APP_NAME_LOADTEST }} NEWRELIC_KEY: ${{ secrets.NEWRELIC_KEY }} + DBUS_SESSION_BUS_ADDRESS: ${{ vars.DBUS_SESSION_BUS_ADDRESS }} BRANCH_NAME: ${{ github.head_ref || github.ref_name }} run: | set -ex @@ -90,4 +91,5 @@ jobs: SECRET_KEY_BASE=${SECRET_KEY_BASE} \ NEWRELIC_APP_NAME=${NEWRELIC_APP_NAME} \ NEWRELIC_KEY=${NEWRELIC_KEY} \ + DBUS_SESSION_BUS_ADDRESS=${DBUS_SESSION_BUS_ADDRESS} \ /home/${SSH_USERNAME}/deploy-script/deploy.sh diff --git a/.github/workflows/loadtest-brazil-server-deploy.yml b/.github/workflows/loadtest-brazil-server-deploy.yml index eacc2fc10..8af839110 100644 --- a/.github/workflows/loadtest-brazil-server-deploy.yml +++ b/.github/workflows/loadtest-brazil-server-deploy.yml @@ -53,6 +53,7 @@ jobs: SECRET_KEY_BASE: ${{ secrets.SECRET_KEY_BASE }} NEWRELIC_APP_NAME: ${{ vars.NEWRELIC_APP_NAME_LOADTEST }} NEWRELIC_KEY: ${{ secrets.NEWRELIC_KEY }} + DBUS_SESSION_BUS_ADDRESS: ${{ vars.DBUS_SESSION_BUS_ADDRESS }} BRANCH_NAME: ${{ github.head_ref || github.ref_name }} run: | set -ex @@ -68,4 +69,5 @@ jobs: SECRET_KEY_BASE=${SECRET_KEY_BASE} \ NEWRELIC_APP_NAME=${NEWRELIC_APP_NAME} \ NEWRELIC_KEY=${NEWRELIC_KEY} \ + DBUS_SESSION_BUS_ADDRESS=${DBUS_SESSION_BUS_ADDRESS} \ /home/${SSH_USERNAME}/deploy-script/deploy.sh diff --git a/.github/workflows/loadtest-europe-client-deploy.yml b/.github/workflows/loadtest-europe-client-deploy.yml index 0305752bf..f7427183b 100644 --- a/.github/workflows/loadtest-europe-client-deploy.yml +++ b/.github/workflows/loadtest-europe-client-deploy.yml @@ -70,6 +70,7 @@ jobs: SECRET_KEY_BASE: ${{ secrets.SECRET_KEY_BASE }} NEWRELIC_APP_NAME: ${{ vars.NEWRELIC_APP_NAME_LOADTEST }} NEWRELIC_KEY: ${{ secrets.NEWRELIC_KEY }} + DBUS_SESSION_BUS_ADDRESS: ${{ vars.DBUS_SESSION_BUS_ADDRESS }} BRANCH_NAME: ${{ github.head_ref || github.ref_name }} run: | set -ex @@ -88,4 +89,5 @@ jobs: SECRET_KEY_BASE=${SECRET_KEY_BASE} \ NEWRELIC_APP_NAME=${NEWRELIC_APP_NAME} \ NEWRELIC_KEY=${NEWRELIC_KEY} \ + DBUS_SESSION_BUS_ADDRESS=${DBUS_SESSION_BUS_ADDRESS} \ /home/${SSH_USERNAME}/deploy-script/deploy.sh diff --git a/.github/workflows/loadtest-europe-server-deploy.yml b/.github/workflows/loadtest-europe-server-deploy.yml index 808f3fc6f..aed96b934 100644 --- a/.github/workflows/loadtest-europe-server-deploy.yml +++ b/.github/workflows/loadtest-europe-server-deploy.yml @@ -53,6 +53,7 @@ jobs: SECRET_KEY_BASE: ${{ secrets.SECRET_KEY_BASE }} NEWRELIC_APP_NAME: ${{ vars.NEWRELIC_APP_NAME_LOADTEST }} NEWRELIC_KEY: ${{ secrets.NEWRELIC_KEY }} + DBUS_SESSION_BUS_ADDRESS: ${{ vars.DBUS_SESSION_BUS_ADDRESS }} BRANCH_NAME: ${{ github.head_ref || github.ref_name }} run: | set -ex @@ -68,4 +69,5 @@ jobs: SECRET_KEY_BASE=${SECRET_KEY_BASE} \ NEWRELIC_APP_NAME=${NEWRELIC_APP_NAME} \ NEWRELIC_KEY=${NEWRELIC_KEY} \ + DBUS_SESSION_BUS_ADDRESS=${DBUS_SESSION_BUS_ADDRESS} \ /home/${SSH_USERNAME}/deploy-script/deploy.sh diff --git a/apps/arena/lib/arena/game/player.ex b/apps/arena/lib/arena/game/player.ex index ef341c95e..411615cf7 100644 --- a/apps/arena/lib/arena/game/player.ex +++ b/apps/arena/lib/arena/game/player.ex @@ -3,6 +3,7 @@ defmodule Arena.Game.Player do Module for interacting with Player entity """ + alias Arena.Game.Crate alias Arena.GameUpdater alias Arena.GameTracker alias Arena.Utils @@ -175,6 +176,13 @@ defmodule Arena.Game.Player do {auto_aim?, skill_direction} = skill_params.target |> Skill.maybe_auto_aim(skill, player, targetable_players(game_state.players)) + |> case do + {false, _} -> + Skill.maybe_auto_aim(skill_params.target, skill, player, Crate.interactable_crates(game_state.crates)) + + auto_aim -> + auto_aim + end execution_duration = calculate_duration(skill, player.position, skill_direction, auto_aim?) Process.send_after(self(), {:block_actions, player.id}, execution_duration) diff --git a/apps/arena/lib/arena/game/skill.ex b/apps/arena/lib/arena/game/skill.ex index 8e076f6e7..2d6d0c95c 100644 --- a/apps/arena/lib/arena/game/skill.ex +++ b/apps/arena/lib/arena/game/skill.ex @@ -375,11 +375,10 @@ defmodule Arena.Game.Skill do def maybe_auto_aim(%{x: x, y: y}, skill, player, entities) when x == 0.0 and y == 0.0 do case skill.autoaim do true -> - nearest_entity_position_in_range = + {use_autoaim?, nearest_entity_position_in_range} = Physics.nearest_entity_position_in_range(player, entities, skill.max_autoaim_range) - |> maybe_normalize(not skill.can_pick_destination) - {nearest_entity_position_in_range != player.direction, nearest_entity_position_in_range} + {use_autoaim?, nearest_entity_position_in_range |> maybe_normalize(not skill.can_pick_destination)} false -> {false, player.direction |> maybe_normalize(not skill.can_pick_destination)} diff --git a/apps/arena/lib/arena/game_socket_handler.ex b/apps/arena/lib/arena/game_socket_handler.ex index 598fb8421..9ac09711c 100644 --- a/apps/arena/lib/arena/game_socket_handler.ex +++ b/apps/arena/lib/arena/game_socket_handler.ex @@ -206,6 +206,9 @@ defmodule Arena.GameSocketHandler do defp handle_decoded_message(%{action_type: {:toggle_bots, _bots_params}}, state), do: GameUpdater.toggle_bots(state.game_pid) + defp handle_decoded_message(%{action_type: {:change_tickrate, tickrate_params}}, state), + do: GameUpdater.change_tickrate(state.game_pid, tickrate_params.tickrate) + defp handle_decoded_message(_action_type, %{enable: false} = _state), do: nil defp handle_decoded_message( @@ -244,5 +247,10 @@ defmodule Arena.GameSocketHandler do end end - defp handle_decoded_message(_, _), do: nil + # We don't do anything in these messages, we already handle these actions when we have to in previous functions. + defp handle_decoded_message(%{action_type: {action, _}}, _state) when action in [:move, :attack, :use_item], do: nil + + defp handle_decoded_message(message, _) do + Logger.info("Unexpected message: #{inspect(message)}") + end end diff --git a/apps/arena/lib/arena/game_updater.ex b/apps/arena/lib/arena/game_updater.ex index 01f20f180..e6d6023c4 100644 --- a/apps/arena/lib/arena/game_updater.ex +++ b/apps/arena/lib/arena/game_updater.ex @@ -47,6 +47,10 @@ defmodule Arena.GameUpdater do GenServer.cast(game_pid, :toggle_bots) end + def change_tickrate(game_pid, tickrate) do + GenServer.cast(game_pid, {:change_tickrate, tickrate}) + end + ########################## # END API ########################## @@ -168,6 +172,10 @@ defmodule Arena.GameUpdater do {:noreply, state} end + def handle_cast({:change_tickrate, tickrate}, state) do + {:noreply, put_in(state, [:game_config, :game, :tick_rate_ms], tickrate)} + end + ########################## # END API Callbacks ########################## diff --git a/apps/arena/lib/arena/serialization/messages.pb.ex b/apps/arena/lib/arena/serialization/messages.pb.ex index fabfe1f70..f69dc723d 100644 --- a/apps/arena/lib/arena/serialization/messages.pb.ex +++ b/apps/arena/lib/arena/serialization/messages.pb.ex @@ -642,6 +642,14 @@ defmodule Arena.Serialization.ToggleBots do use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" end +defmodule Arena.Serialization.ChangeTickrate do + @moduledoc false + + use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" + + field(:tickrate, 1, type: :int64) +end + defmodule Arena.Serialization.GameAction do @moduledoc false @@ -661,6 +669,13 @@ defmodule Arena.Serialization.GameAction do field(:toggle_zone, 6, type: Arena.Serialization.ToggleZone, json_name: "toggleZone", oneof: 0) field(:toggle_bots, 7, type: Arena.Serialization.ToggleBots, json_name: "toggleBots", oneof: 0) + + field(:change_tickrate, 8, + type: Arena.Serialization.ChangeTickrate, + json_name: "changeTickrate", + oneof: 0 + ) + field(:timestamp, 3, type: :int64) end diff --git a/apps/arena/native/physics/src/lib.rs b/apps/arena/native/physics/src/lib.rs index 82fe789d6..647d91428 100644 --- a/apps/arena/native/physics/src/lib.rs +++ b/apps/arena/native/physics/src/lib.rs @@ -145,12 +145,13 @@ fn nearest_entity_position_in_range( entity: Entity, entities: HashMap, range: i64, -) -> Position { +) -> (bool, Position) { let mut max_distance = range as f32; let mut position = Position { x: entity.direction.x, y: entity.direction.y, }; + let mut use_autoaim: bool = false; for other_entity in entities.values() { if entity.id != other_entity.id { @@ -163,11 +164,12 @@ fn nearest_entity_position_in_range( x: difference_between_positions.x, y: difference_between_positions.y, }; + use_autoaim = true; } } } - position + (use_autoaim, position) } #[rustler::nif()] fn distance_between_entities(entity_a: Entity, entity_b: Entity) -> f32 { 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 979706284..b493a709a 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 @@ -689,6 +689,14 @@ defmodule ArenaLoadTest.Serialization.ToggleBots do use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" end +defmodule ArenaLoadTest.Serialization.ChangeTickrate do + @moduledoc false + + use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" + + field(:tickrate, 1, type: :int64) +end + defmodule ArenaLoadTest.Serialization.GameAction do @moduledoc false @@ -718,6 +726,12 @@ defmodule ArenaLoadTest.Serialization.GameAction do oneof: 0 ) + field(:change_tickrate, 8, + type: ArenaLoadTest.Serialization.ChangeTickrate, + json_name: "changeTickrate", + oneof: 0 + ) + field(:timestamp, 3, type: :int64) end diff --git a/apps/bot_manager/lib/protobuf/messages.pb.ex b/apps/bot_manager/lib/protobuf/messages.pb.ex index 0f4042d55..3cea4bdbe 100644 --- a/apps/bot_manager/lib/protobuf/messages.pb.ex +++ b/apps/bot_manager/lib/protobuf/messages.pb.ex @@ -642,6 +642,14 @@ defmodule BotManager.Protobuf.ToggleBots do use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" end +defmodule BotManager.Protobuf.ChangeTickrate do + @moduledoc false + + use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" + + field(:tickrate, 1, type: :int64) +end + defmodule BotManager.Protobuf.GameAction do @moduledoc false @@ -661,6 +669,13 @@ defmodule BotManager.Protobuf.GameAction do field(:toggle_zone, 6, type: BotManager.Protobuf.ToggleZone, json_name: "toggleZone", oneof: 0) field(:toggle_bots, 7, type: BotManager.Protobuf.ToggleBots, json_name: "toggleBots", oneof: 0) + + field(:change_tickrate, 8, + type: BotManager.Protobuf.ChangeTickrate, + json_name: "changeTickrate", + oneof: 0 + ) + field(:timestamp, 3, type: :int64) end diff --git a/apps/configurator/lib/configurator/configuration.ex b/apps/configurator/lib/configurator/configuration.ex new file mode 100644 index 000000000..1058df652 --- /dev/null +++ b/apps/configurator/lib/configurator/configuration.ex @@ -0,0 +1,104 @@ +defmodule Configurator.Configuration do + @moduledoc """ + The Configuration context. + """ + + import Ecto.Query, warn: false + alias Configurator.Repo + + alias Configurator.Configuration.Character + + @doc """ + Returns the list of characters. + + ## Examples + + iex> list_characters() + [%Character{}, ...] + + """ + def list_characters do + Repo.all(Character) + end + + @doc """ + Gets a single character. + + Raises `Ecto.NoResultsError` if the Character does not exist. + + ## Examples + + iex> get_character!(123) + %Character{} + + iex> get_character!(456) + ** (Ecto.NoResultsError) + + """ + def get_character!(id), do: Repo.get!(Character, id) + + @doc """ + Creates a character. + + ## Examples + + iex> create_character(%{field: value}) + {:ok, %Character{}} + + iex> create_character(%{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def create_character(attrs \\ %{}) do + %Character{} + |> Character.changeset(attrs) + |> Repo.insert() + end + + @doc """ + Updates a character. + + ## Examples + + iex> update_character(character, %{field: new_value}) + {:ok, %Character{}} + + iex> update_character(character, %{field: bad_value}) + {:error, %Ecto.Changeset{}} + + """ + def update_character(%Character{} = character, attrs) do + character + |> Character.changeset(attrs) + |> Repo.update() + end + + @doc """ + Deletes a character. + + ## Examples + + iex> delete_character(character) + {:ok, %Character{}} + + iex> delete_character(character) + {:error, %Ecto.Changeset{}} + + """ + def delete_character(%Character{} = character) do + Repo.delete(character) + end + + @doc """ + Returns an `%Ecto.Changeset{}` for tracking character changes. + + ## Examples + + iex> change_character(character) + %Ecto.Changeset{data: %Character{}} + + """ + def change_character(%Character{} = character, attrs \\ %{}) do + Character.changeset(character, attrs) + end +end diff --git a/apps/configurator/lib/configurator/configuration/character.ex b/apps/configurator/lib/configurator/configuration/character.ex new file mode 100644 index 000000000..611bc9034 --- /dev/null +++ b/apps/configurator/lib/configurator/configuration/character.ex @@ -0,0 +1,49 @@ +defmodule Configurator.Configuration.Character do + @moduledoc """ + Character schema + """ + use Configurator.Schema + import Ecto.Changeset + + schema "characters" do + field :active, :boolean, default: false + field :name, :string + field :base_speed, :decimal + field :base_size, :decimal + field :base_health, :integer + field :base_stamina, :integer + + field :max_inventory_size, :integer + field :natural_healing_interval, :integer + field :natural_healing_damage_interval, :integer + field :stamina_interval, :integer + + # TODO This should be removed once we have the skills relationship, issue: https://github.com/lambdaclass/mirra_backend/issues/717 + field :skills, {:map, :string} + + timestamps(type: :utc_datetime) + end + + @required [ + :name, + :active, + :base_speed, + :base_size, + :base_health, + :base_stamina, + :stamina_interval, + :max_inventory_size, + :natural_healing_interval, + :natural_healing_damage_interval, + :skills + ] + + @permitted [] ++ @required + + @doc false + def changeset(character, attrs) do + character + |> cast(attrs, @permitted) + |> validate_required(@required) + end +end diff --git a/apps/configurator/lib/configurator/configure/configuration.ex b/apps/configurator/lib/configurator/configure/configuration.ex index 454cb19fb..313a9f951 100644 --- a/apps/configurator/lib/configurator/configure/configuration.ex +++ b/apps/configurator/lib/configurator/configure/configuration.ex @@ -2,7 +2,7 @@ defmodule Configurator.Configure.Configuration do @moduledoc """ Configuration in DB """ - use Ecto.Schema + use Configurator.Schema import Ecto.Changeset schema "configurations" do diff --git a/apps/configurator/lib/configurator/schema.ex b/apps/configurator/lib/configurator/schema.ex new file mode 100644 index 000000000..63c70f9c4 --- /dev/null +++ b/apps/configurator/lib/configurator/schema.ex @@ -0,0 +1,14 @@ +defmodule Configurator.Schema do + @moduledoc """ + Base module for all schemas in configurator + + It sets the primary key to be a binary_id and the foreign key type to be a binary_id + """ + defmacro __using__(_) do + quote do + use Ecto.Schema + @primary_key {:id, :binary_id, autogenerate: true} + @foreign_key_type :binary_id + end + end +end diff --git a/apps/configurator/lib/configurator_web/components/layouts/app.html.heex b/apps/configurator/lib/configurator_web/components/layouts/app.html.heex index 0997ae9a0..f9d41dc09 100644 --- a/apps/configurator/lib/configurator_web/components/layouts/app.html.heex +++ b/apps/configurator/lib/configurator_web/components/layouts/app.html.heex @@ -1,26 +1,4 @@ -
-
-
- - - -

- v<%= Application.spec(:phoenix, :vsn) %> -

-
- -
-
+
<.flash_group flash={@flash} /> diff --git a/apps/configurator/lib/configurator_web/controllers/character_controller.ex b/apps/configurator/lib/configurator_web/controllers/character_controller.ex new file mode 100644 index 000000000..d02c47d90 --- /dev/null +++ b/apps/configurator/lib/configurator_web/controllers/character_controller.ex @@ -0,0 +1,76 @@ +defmodule ConfiguratorWeb.CharacterController do + use ConfiguratorWeb, :controller + + alias Configurator.Configuration + alias Configurator.Configuration.Character + alias ConfiguratorWeb.Utils + + def index(conn, _params) do + characters = Configuration.list_characters() + render(conn, :index, characters: characters) + end + + def new(conn, _params) do + changeset = Configuration.change_character(%Character{}) + render(conn, :new, changeset: changeset) + 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) + + case Configuration.create_character(character_params) do + {:ok, character} -> + conn + |> put_flash(:success, "Character created successfully.") + |> redirect(to: ~p"/characters/#{character}") + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, :new, changeset: changeset) + end + end + + def show(conn, %{"id" => id}) do + character = Configuration.get_character!(id) + render(conn, :show, character: character) + end + + def edit(conn, %{"id" => id}) do + character = Configuration.get_character!(id) + changeset = Configuration.change_character(character) + render(conn, :edit, character: character, changeset: changeset) + 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 = Configuration.get_character!(id) + + case Configuration.update_character(character, character_params) do + {:ok, character} -> + conn + |> put_flash(:success, "Character updated successfully.") + |> redirect(to: ~p"/characters/#{character}") + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, :edit, character: character, changeset: changeset) + end + end + + def delete(conn, %{"id" => id}) do + character = Configuration.get_character!(id) + {:ok, _character} = Configuration.delete_character(character) + + conn + |> put_flash(:success, "Character deleted successfully.") + |> redirect(to: ~p"/characters") + end + + def characters(conn, _params) do + characters = Configuration.list_characters() |> Utils.transform_to_map_from_ecto_struct() + + send_resp(conn, 200, Jason.encode!(characters)) + end +end diff --git a/apps/configurator/lib/configurator_web/controllers/character_html.ex b/apps/configurator/lib/configurator_web/controllers/character_html.ex new file mode 100644 index 000000000..24dcbd08f --- /dev/null +++ b/apps/configurator/lib/configurator_web/controllers/character_html.ex @@ -0,0 +1,13 @@ +defmodule ConfiguratorWeb.CharacterHTML do + use ConfiguratorWeb, :html + + embed_templates "character_html/*" + + @doc """ + Renders a character form. + """ + attr :changeset, Ecto.Changeset, required: true + attr :action, :string, required: true + + def character_form(assigns) +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 new file mode 100644 index 000000000..73aa2a08a --- /dev/null +++ b/apps/configurator/lib/configurator_web/controllers/character_html/character_form.html.heex @@ -0,0 +1,22 @@ +<.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" /> + <.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_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" /> + + <.input field={f[:max_inventory_size]} type="number" label="Max inventory size" step="any" /> + <.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)} /> + + <: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 new file mode 100644 index 000000000..fb271da45 --- /dev/null +++ b/apps/configurator/lib/configurator_web/controllers/character_html/edit.html.heex @@ -0,0 +1,8 @@ +<.header> + Edit Character <%= @character.id %> + <:subtitle>Use this form to manage character records in your database. + + +<.character_form changeset={@changeset} action={~p"/characters/#{@character}"} /> + +<.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 new file mode 100644 index 000000000..1673b1b74 --- /dev/null +++ b/apps/configurator/lib/configurator_web/controllers/character_html/index.html.heex @@ -0,0 +1,30 @@ +<.header> + Characters + <:actions> + <.link href={~p"/characters/new"}> + <.button>New Character + + + + +<.table id="characters" rows={@characters} row_click={&JS.navigate(~p"/characters/#{&1}")}> + <:col :let={character} label="Name"><%= character.name %> + <:col :let={character} label="Active"><%= character.active %> + <:col :let={character} label="Base speed"><%= character.base_speed %> + <: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 %> + + <:action :let={character}> +
+ <.link navigate={~p"/characters/#{character}"}>Show +
+ <.link navigate={~p"/characters/#{character}/edit"}>Edit + + <:action :let={character}> + <.link href={~p"/characters/#{character}"} method="delete" data-confirm="Are you sure?"> + Delete + + + 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 new file mode 100644 index 000000000..5bbd0af66 --- /dev/null +++ b/apps/configurator/lib/configurator_web/controllers/character_html/new.html.heex @@ -0,0 +1,8 @@ +<.header> + New Character + <:subtitle>Use this form to manage character records in your database. + + +<.character_form changeset={@changeset} action={~p"/characters"} /> + +<.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 new file mode 100644 index 000000000..f9e1f01e5 --- /dev/null +++ b/apps/configurator/lib/configurator_web/controllers/character_html/show.html.heex @@ -0,0 +1,25 @@ +<.header> + Character <%= @character.id %> + <:subtitle>This is a character record from your database. + <:actions> + <.link href={~p"/characters/#{@character}/edit"}> + <.button>Edit character + + + + +<.list> + <:item title="Name"><%= @character.name %> + <:item title="Active"><%= @character.active %> + <:item title="Base speed"><%= @character.base_speed %> + <:item title="Base size"><%= @character.base_size %> + <:item title="Base health"><%= @character.base_health %> + <:item title="Base stamina"><%= @character.base_stamina %> + <:item title="Stamina Interval"><%= @character.stamina_interval %> + <: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) %> + + +<.back navigate={~p"/characters"}>Back to characters diff --git a/apps/configurator/lib/configurator_web/controllers/utils.ex b/apps/configurator/lib/configurator_web/controllers/utils.ex new file mode 100644 index 000000000..7113558b2 --- /dev/null +++ b/apps/configurator/lib/configurator_web/controllers/utils.ex @@ -0,0 +1,23 @@ +defmodule ConfiguratorWeb.Utils do + @moduledoc """ + Utility functions for the Configurator App + """ + + def transform_to_map_from_ecto_struct(ecto_struct) when is_struct(ecto_struct) do + ecto_struct + |> Map.from_struct() + |> Map.drop([ + :__meta__, + :__struct__, + :inserted_at, + :updated_at, + :id + ]) + end + + def transform_to_map_from_ecto_struct(ecto_structs) when is_list(ecto_structs) do + Enum.map(ecto_structs, fn ecto_struct -> + transform_to_map_from_ecto_struct(ecto_struct) + end) + end +end diff --git a/apps/configurator/lib/configurator_web/router.ex b/apps/configurator/lib/configurator_web/router.ex index 02198bb51..f5023ef58 100644 --- a/apps/configurator/lib/configurator_web/router.ex +++ b/apps/configurator/lib/configurator_web/router.ex @@ -21,6 +21,7 @@ defmodule ConfiguratorWeb.Router do resources "/configurations", ConfigurationController, only: [:index, :new, :create, :show] get "/configurations/new/:id", ConfigurationController, :new put "/configurations/set_default/:id", ConfigurationController, :set_default + resources "/characters", CharacterController end scope "/api", ConfiguratorWeb do @@ -28,6 +29,8 @@ defmodule ConfiguratorWeb.Router do get "/default_config", ConfigurationController, :show get "/configurations/:id", ConfigurationController, :show + + get "/characters", CharacterController, :characters end # Other scopes may use custom stacks. diff --git a/apps/configurator/priv/repo/migrations/20240618194229_create_characters.exs b/apps/configurator/priv/repo/migrations/20240618194229_create_characters.exs new file mode 100644 index 000000000..0ae69023b --- /dev/null +++ b/apps/configurator/priv/repo/migrations/20240618194229_create_characters.exs @@ -0,0 +1,22 @@ +defmodule Configurator.Repo.Migrations.CreateCharacters do + use Ecto.Migration + + def change do + create table(:characters) do + add :name, :string + add :active, :boolean, default: false, null: false + add :base_speed, :decimal + add :base_size, :decimal + add :base_health, :integer + add :base_stamina, :integer + + add :max_inventory_size, :integer + add :natural_healing_interval, :integer + add :natural_healing_damage_interval, :integer + add :stamina_interval, :integer + add :skills, :map + + timestamps(type: :utc_datetime) + end + end +end diff --git a/apps/configurator/test/configurator/configuration_test.exs b/apps/configurator/test/configurator/configuration_test.exs new file mode 100644 index 000000000..e945daf98 --- /dev/null +++ b/apps/configurator/test/configurator/configuration_test.exs @@ -0,0 +1,84 @@ +defmodule Configurator.ConfigurationTest do + use Configurator.DataCase + + alias Configurator.Configuration + + describe "characters" do + alias Configurator.Configuration.Character + + import Configurator.ConfigurationFixtures + + @invalid_attrs %{active: nil, name: nil, base_speed: nil, base_size: nil, base_health: nil, base_stamina: nil} + + test "get_character!/1 returns the character with given id" do + character = character_fixture() + assert Configuration.get_character!(character.id) == character + end + + test "create_character/1 with valid data creates a character" do + valid_attrs = %{ + active: true, + base_health: 42, + base_size: "120.5", + base_speed: "120.5", + base_stamina: 42, + name: "some name", + max_inventory_size: 42, + natural_healing_damage_interval: 42, + natural_healing_interval: 42, + stamina_interval: 42, + skills: %{} + } + + assert {:ok, %Character{} = character} = Configuration.create_character(valid_attrs) + assert character.active == true + assert character.name == "some name" + assert character.base_speed == Decimal.new("120.5") + assert character.base_size == Decimal.new("120.5") + assert character.base_health == 42 + assert character.base_stamina == 42 + end + + test "create_character/1 with invalid data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Configuration.create_character(@invalid_attrs) + end + + test "update_character/2 with valid data updates the character" do + character = character_fixture() + + update_attrs = %{ + active: false, + name: "some updated name", + base_speed: "456.7", + base_size: "456.7", + base_health: 43, + base_stamina: 43 + } + + assert {:ok, %Character{} = character} = Configuration.update_character(character, update_attrs) + assert character.active == false + assert character.name == "some updated name" + assert character.base_speed == Decimal.new("456.7") + assert character.base_size == Decimal.new("456.7") + assert character.base_health == 43 + assert character.base_stamina == 43 + end + + test "update_character/2 with invalid data returns error changeset" do + character = character_fixture() + assert {:error, %Ecto.Changeset{}} = Configuration.update_character(character, @invalid_attrs) + assert character == Configuration.get_character!(character.id) + end + + test "delete_character/1 deletes the character" do + character = character_fixture() + assert {:ok, %Character{}} = Configuration.delete_character(character) + assert_raise Ecto.NoResultsError, fn -> Configuration.get_character!(character.id) end + end + + test "change_character/1 returns a character changeset" do + character = character_fixture() + assert %Ecto.Changeset{} = Configuration.change_character(character) + end + end +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 new file mode 100644 index 000000000..31e72d469 --- /dev/null +++ b/apps/configurator/test/configurator_web/controllers/character_controller_test.exs @@ -0,0 +1,117 @@ +defmodule ConfiguratorWeb.CharacterControllerTest do + use ConfiguratorWeb.ConnCase + + import Configurator.ConfigurationFixtures + + @create_attrs %{ + active: true, + base_health: 42, + base_size: 120.5, + base_speed: 120.5, + base_stamina: 42, + name: "some name", + max_inventory_size: 42, + natural_healing_damage_interval: 42, + natural_healing_interval: 42, + stamina_interval: 42, + skills: "{}" + } + @update_attrs %{ + active: true, + base_health: 42, + base_size: 120.5, + base_speed: 120.5, + base_stamina: 42, + name: "some updated name", + max_inventory_size: 42, + natural_healing_damage_interval: 42, + natural_healing_interval: 42, + stamina_interval: 42, + skills: "{}" + } + + @invalid_attrs %{ + active: nil, + name: nil, + base_speed: nil, + base_size: nil, + base_health: nil, + base_stamina: nil, + skills: "{}" + } + + describe "index" do + test "lists all characters", %{conn: conn} do + conn = get(conn, ~p"/characters") + assert html_response(conn, 200) =~ "Characters" + end + end + + describe "new character" do + test "renders form", %{conn: conn} do + conn = get(conn, ~p"/characters/new") + assert html_response(conn, 200) =~ "New Character" + end + end + + describe "create character" do + test "redirects to show when data is valid", %{conn: conn} do + conn = post(conn, ~p"/characters", character: @create_attrs) + + assert %{id: id} = redirected_params(conn) + assert redirected_to(conn) == ~p"/characters/#{id}" + + conn = get(conn, ~p"/characters/#{id}") + assert html_response(conn, 200) =~ "Character #{id}" + end + + test "renders errors when data is invalid", %{conn: conn} do + conn = post(conn, ~p"/characters", character: @invalid_attrs) + assert html_response(conn, 200) =~ "New Character" + end + end + + describe "edit character" do + setup [:create_character] + + test "renders form for editing chosen character", %{conn: conn, character: character} do + conn = get(conn, ~p"/characters/#{character}/edit") + assert html_response(conn, 200) =~ "Edit Character" + end + end + + describe "update character" do + setup [:create_character] + + test "redirects when data is valid", %{conn: conn, character: character} do + conn = put(conn, ~p"/characters/#{character}", character: @update_attrs) + assert redirected_to(conn) == ~p"/characters/#{character}" + + conn = get(conn, ~p"/characters/#{character}") + assert html_response(conn, 200) =~ "some updated name" + end + + test "renders errors when data is invalid", %{conn: conn, character: character} do + conn = put(conn, ~p"/characters/#{character}", character: @invalid_attrs) + assert html_response(conn, 200) =~ "Edit Character" + end + end + + describe "delete character" do + setup [:create_character] + + test "deletes chosen character", %{conn: conn, character: character} do + conn = delete(conn, ~p"/characters/#{character}") + assert redirected_to(conn) == ~p"/characters" + + assert_error_sent 404, fn -> + get(conn, ~p"/characters/#{character}") + end + end + end + + defp create_character(_) do + character = character_fixture() + %{character: character} + end +end diff --git a/apps/configurator/test/support/fixtures/configuration_fixtures.ex b/apps/configurator/test/support/fixtures/configuration_fixtures.ex new file mode 100644 index 000000000..817fd7891 --- /dev/null +++ b/apps/configurator/test/support/fixtures/configuration_fixtures.ex @@ -0,0 +1,30 @@ +defmodule Configurator.ConfigurationFixtures do + @moduledoc """ + This module defines test helpers for creating + entities via the `Configurator.Configuration` context. + """ + + @doc """ + Generate a character. + """ + def character_fixture(attrs \\ %{}) do + {:ok, character} = + attrs + |> Enum.into(%{ + active: true, + base_health: 42, + base_size: "120.5", + base_speed: "120.5", + base_stamina: 42, + name: "some name", + max_inventory_size: 42, + natural_healing_damage_interval: 42, + natural_healing_interval: 42, + stamina_interval: 42, + skills: %{} + }) + |> Configurator.Configuration.create_character() + + character + 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 83be0d42e..c1e7473da 100644 --- a/apps/game_client/assets/js/protobuf/messages_pb.js +++ b/apps/game_client/assets/js/protobuf/messages_pb.js @@ -25,6 +25,7 @@ goog.exportSymbol('proto.Attack', null, global); goog.exportSymbol('proto.AttackParameters', null, global); goog.exportSymbol('proto.BountyInfo', null, global); goog.exportSymbol('proto.Bush', null, global); +goog.exportSymbol('proto.ChangeTickrate', null, global); goog.exportSymbol('proto.ClientConfig', null, global); goog.exportSymbol('proto.ConfigCharacter', null, global); goog.exportSymbol('proto.ConfigGame', null, global); @@ -850,6 +851,27 @@ if (goog.DEBUG && !COMPILED) { */ proto.ToggleBots.displayName = 'proto.ToggleBots'; } +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.ChangeTickrate = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.ChangeTickrate, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.ChangeTickrate.displayName = 'proto.ChangeTickrate'; +} /** * Generated by JsPbCodeGenerator. * @param {Array=} opt_data Optional initial data array, typically from a @@ -9500,6 +9522,136 @@ proto.ToggleBots.serializeBinaryToWriter = function(message, writer) { + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.ChangeTickrate.prototype.toObject = function(opt_includeInstance) { + return proto.ChangeTickrate.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.ChangeTickrate} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.ChangeTickrate.toObject = function(includeInstance, msg) { + var f, obj = { + tickrate: jspb.Message.getFieldWithDefault(msg, 1, 0) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.ChangeTickrate} + */ +proto.ChangeTickrate.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.ChangeTickrate; + return proto.ChangeTickrate.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.ChangeTickrate} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.ChangeTickrate} + */ +proto.ChangeTickrate.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {number} */ (reader.readInt64()); + msg.setTickrate(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.ChangeTickrate.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.ChangeTickrate.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.ChangeTickrate} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.ChangeTickrate.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getTickrate(); + if (f !== 0) { + writer.writeInt64( + 1, + f + ); + } +}; + + +/** + * optional int64 tickrate = 1; + * @return {number} + */ +proto.ChangeTickrate.prototype.getTickrate = function() { + return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0)); +}; + + +/** + * @param {number} value + * @return {!proto.ChangeTickrate} returns this + */ +proto.ChangeTickrate.prototype.setTickrate = function(value) { + return jspb.Message.setProto3IntField(this, 1, value); +}; + + + /** * Oneof group definitions for this message. Each group defines the field * numbers belonging to that group. When of these fields' value is set, all @@ -9508,7 +9660,7 @@ proto.ToggleBots.serializeBinaryToWriter = function(message, writer) { * @private {!Array>} * @const */ -proto.GameAction.oneofGroups_ = [[1,2,4,5,6,7]]; +proto.GameAction.oneofGroups_ = [[1,2,4,5,6,7,8]]; /** * @enum {number} @@ -9520,7 +9672,8 @@ proto.GameAction.ActionTypeCase = { USE_ITEM: 4, SELECT_BOUNTY: 5, TOGGLE_ZONE: 6, - TOGGLE_BOTS: 7 + TOGGLE_BOTS: 7, + CHANGE_TICKRATE: 8 }; /** @@ -9567,6 +9720,7 @@ proto.GameAction.toObject = function(includeInstance, msg) { selectBounty: (f = msg.getSelectBounty()) && proto.SelectBounty.toObject(includeInstance, f), toggleZone: (f = msg.getToggleZone()) && proto.ToggleZone.toObject(includeInstance, f), toggleBots: (f = msg.getToggleBots()) && proto.ToggleBots.toObject(includeInstance, f), + changeTickrate: (f = msg.getChangeTickrate()) && proto.ChangeTickrate.toObject(includeInstance, f), timestamp: jspb.Message.getFieldWithDefault(msg, 3, 0) }; @@ -9634,6 +9788,11 @@ proto.GameAction.deserializeBinaryFromReader = function(msg, reader) { reader.readMessage(value,proto.ToggleBots.deserializeBinaryFromReader); msg.setToggleBots(value); break; + case 8: + var value = new proto.ChangeTickrate; + reader.readMessage(value,proto.ChangeTickrate.deserializeBinaryFromReader); + msg.setChangeTickrate(value); + break; case 3: var value = /** @type {number} */ (reader.readInt64()); msg.setTimestamp(value); @@ -9715,6 +9874,14 @@ proto.GameAction.serializeBinaryToWriter = function(message, writer) { proto.ToggleBots.serializeBinaryToWriter ); } + f = message.getChangeTickrate(); + if (f != null) { + writer.writeMessage( + 8, + f, + proto.ChangeTickrate.serializeBinaryToWriter + ); + } f = message.getTimestamp(); if (f !== 0) { writer.writeInt64( @@ -9947,6 +10114,43 @@ proto.GameAction.prototype.hasToggleBots = function() { }; +/** + * optional ChangeTickrate change_tickrate = 8; + * @return {?proto.ChangeTickrate} + */ +proto.GameAction.prototype.getChangeTickrate = function() { + return /** @type{?proto.ChangeTickrate} */ ( + jspb.Message.getWrapperField(this, proto.ChangeTickrate, 8)); +}; + + +/** + * @param {?proto.ChangeTickrate|undefined} value + * @return {!proto.GameAction} returns this +*/ +proto.GameAction.prototype.setChangeTickrate = function(value) { + return jspb.Message.setOneofWrapperField(this, 8, proto.GameAction.oneofGroups_[0], value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.GameAction} returns this + */ +proto.GameAction.prototype.clearChangeTickrate = function() { + return this.setChangeTickrate(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.GameAction.prototype.hasChangeTickrate = function() { + return jspb.Message.getField(this, 8) != null; +}; + + /** * optional int64 timestamp = 3; * @return {number} diff --git a/apps/game_client/lib/game_client/client_socket_handler.ex b/apps/game_client/lib/game_client/client_socket_handler.ex index 349bc9135..05c54b098 100644 --- a/apps/game_client/lib/game_client/client_socket_handler.ex +++ b/apps/game_client/lib/game_client/client_socket_handler.ex @@ -73,6 +73,17 @@ defmodule GameClient.ClientSocketHandler do {:reply, {:binary, game_action}, state} end + def handle_info(:toggle_bots, state) do + Logger.info("Sending GameAction frame with toggle_bots payload") + + game_action = + GameClient.Protobuf.GameAction.encode(%GameClient.Protobuf.GameAction{ + action_type: {:toggle_bots, %GameClient.Protobuf.ToggleBots{}} + }) + + {:reply, {:binary, game_action}, state} + end + def handle_info(:close, state) do Logger.info("ClientSocket closed") {:close, state} 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 1377988cb..68041bba2 100644 --- a/apps/game_client/lib/game_client/protobuf/messages.pb.ex +++ b/apps/game_client/lib/game_client/protobuf/messages.pb.ex @@ -642,6 +642,14 @@ defmodule GameClient.Protobuf.ToggleBots do use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" end +defmodule GameClient.Protobuf.ChangeTickrate do + @moduledoc false + + use Protobuf, syntax: :proto3, protoc_gen_elixir_version: "0.12.0" + + field(:tickrate, 1, type: :int64) +end + defmodule GameClient.Protobuf.GameAction do @moduledoc false @@ -661,6 +669,13 @@ defmodule GameClient.Protobuf.GameAction do field(:toggle_zone, 6, type: GameClient.Protobuf.ToggleZone, json_name: "toggleZone", oneof: 0) field(:toggle_bots, 7, type: GameClient.Protobuf.ToggleBots, json_name: "toggleBots", oneof: 0) + + field(:change_tickrate, 8, + type: GameClient.Protobuf.ChangeTickrate, + json_name: "changeTickrate", + oneof: 0 + ) + field(:timestamp, 3, type: :int64) end diff --git a/apps/game_client/lib/game_client_web/live/pages/board/show.ex b/apps/game_client/lib/game_client_web/live/pages/board/show.ex index ac75f0de9..c51d64867 100644 --- a/apps/game_client/lib/game_client_web/live/pages/board/show.ex +++ b/apps/game_client/lib/game_client_web/live/pages/board/show.ex @@ -63,6 +63,11 @@ defmodule GameClientWeb.BoardLive.Show do {:noreply, socket} end + def handle_event("toggle_bots", _, socket) do + send(socket.assigns.game_socket_handler_pid, :toggle_bots) + {:noreply, socket} + end + defp player_name(player_id), do: "P#{player_id}" defp handle_game_event({:joined, _joined_info}, socket) do @@ -95,6 +100,10 @@ defmodule GameClientWeb.BoardLive.Show do {:noreply, assign(socket, :ping_latency, ping_event.latency)} end + defp handle_game_event({noop_event, _}, socket) when noop_event in [:toggle_bots] do + {:noreply, socket} + end + defp transform_entity_entry({_entity_id, entity}) do %{ id: entity.id, diff --git a/apps/game_client/lib/game_client_web/live/pages/board/show.html.heex b/apps/game_client/lib/game_client_web/live/pages/board/show.html.heex index eba0594b5..2b0d3af12 100644 --- a/apps/game_client/lib/game_client_web/live/pages/board/show.html.heex +++ b/apps/game_client/lib/game_client_web/live/pages/board/show.html.heex @@ -9,6 +9,9 @@
  • Status: <%= @game_status %>
  • +
  • + +
  • diff --git a/apps/serialization/messages.proto b/apps/serialization/messages.proto index 307fb4d18..8d55c2631 100644 --- a/apps/serialization/messages.proto +++ b/apps/serialization/messages.proto @@ -304,6 +304,9 @@ message SelectBounty { message ToggleZone { } message ToggleBots { } +message ChangeTickrate { + int64 tickrate = 1; +} message GameAction { oneof action_type { @@ -313,6 +316,7 @@ message GameAction { SelectBounty select_bounty = 5; ToggleZone toggle_zone = 6; ToggleBots toggle_bots = 7; + ChangeTickrate change_tickrate = 8; } int64 timestamp = 3; } diff --git a/config/config.exs b/config/config.exs index ca24a365d..ef4a0cfd3 100644 --- a/config/config.exs +++ b/config/config.exs @@ -203,6 +203,8 @@ config :configurator, ConfiguratorWeb.Endpoint, pubsub_server: Configurator.PubSub, live_view: [signing_salt: "6A8twvHJ"] +config :configurator, Configurator.Repo, migration_primary_key: [type: :binary_id] + ############################ # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index eaa9cbcd4..dc55340b5 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -234,3 +234,89 @@ Config.get_characters_config() end) ################### END CURSE OF MIRRA ################### + +##################### Configurator ##################### +# Insert characters +alias Configurator.Configuration + +muflus_params = %{ + name: "muflus", + active: true, + base_speed: 17.5, + base_size: 110.0, + base_health: 440, + base_stamina: 3, + stamina_interval: 2000, + max_inventory_size: 1, + natural_healing_interval: 1000, + natural_healing_damage_interval: 3500, + skills: %{ + "1": "muflus_crush", + "2": "muflus_leap", + "3": "muflus_dash" + } +} + +{:ok, _muflus} = Configuration.create_character(muflus_params) + +h4ck_params = %{ + name: "h4ck", + active: true, + base_speed: 22.5, + base_size: 90.0, + base_health: 400, + base_stamina: 3, + stamina_interval: 1800, + 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" + } +} + +{:ok, _h4ck} = Configuration.create_character(h4ck_params) + +uma_params = %{ + name: "uma", + active: true, + base_speed: 20.0, + base_size: 95.0, + base_health: 400, + base_stamina: 3, + stamina_interval: 2000, + max_inventory_size: 1, + natural_healing_interval: 1000, + natural_healing_damage_interval: 3500, + skills: %{ + "1": "uma_avenge", + "2": "uma_veil_radiance", + "3": "uma_sneak" + } +} + +{:ok, _uma} = Configuration.create_character(uma_params) + +valtimer_params = %{ + name: "valtimer", + active: false, + base_speed: 20.0, + 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, + skills: %{ + "1": "valt_antimatter", + "2": "valt_singularity", + "3": "valt_warp" + } +} + +{:ok, _valtimer} = Configuration.create_character(valtimer_params) + +##################### End Configurator #####################