From 930a508ad91a67a1f11b916ebba33259e9ce5e83 Mon Sep 17 00:00:00 2001 From: Kodi Craft Date: Thu, 19 Sep 2024 15:40:16 +0200 Subject: [PATCH] "Loading terrain..."! --- apps/amethyst/lib/api/game.ex | 17 ++++++- apps/amethyst/lib/states/configuration.ex | 60 +++++++++++++---------- apps/amethyst/lib/states/play.ex | 45 +++++++++++++---- apps/example_game/lib/example/game.ex | 14 +++--- 4 files changed, 93 insertions(+), 43 deletions(-) diff --git a/apps/amethyst/lib/api/game.ex b/apps/amethyst/lib/api/game.ex index 0276fe6..95ad5fb 100644 --- a/apps/amethyst/lib/api/game.ex +++ b/apps/amethyst/lib/api/game.ex @@ -33,6 +33,9 @@ defmodule Amethyst.API.Game do You may either :accept or :reject the player for whatever reason, avoid rejecting the player in your default game as that will disconnect the player. + If you :accept the player, you must return a spawn position ({x, y, z}) and + rotation ({yaw, pitch}) + Note that if no new players can join for any reason, your game should return false from `joinable?/1`. The PID received in 'from' will be the one that calls all callbacks @@ -44,7 +47,7 @@ defmodule Amethyst.API.Game do - 'player_cfg' is a keyword list containing the configuration passed by the game client - 'state_refs' are your references (see `instantiate/1`) """ - @callback login(from :: pid(), player_cfg :: keyword(), state_refs :: map()) :: :accept | :reject + @callback login(from :: pid(), player_cfg :: keyword(), state_refs :: map()) :: {:accept, {x :: float(), y :: float(), z :: float()}, {yaw :: float(), pitch ::float()}} | :reject def login(%{:mod => mod, :refs => refs}, player_cfg) do mod.login(self(), player_cfg, refs) end @@ -61,6 +64,18 @@ defmodule Amethyst.API.Game do mod.player_position(self(), {x, y, z}, refs) end @doc """ + `player_rotation/3` is called when a player rotates. This function is called with the absolute angles + that the player client expects. TODO: Teleport Player API. + + - 'from' is the PID of the player's connection process (see `login/3`). + - 'yaw' and 'pitch' are the angles the player expects to rotate to. These are in Minecraft's rotation format. + - `state_refs` are your references (see `instantiate/1`) + """ + @callback player_rotation(from :: pid(), {yaw :: float(), pitch :: float()}, state_refs :: map()) :: :ok + def player_rotation(%{:mod => mod, :refs => refs}, {yaw, pitch}) do + mod.player_rotation(self(), {yaw, pitch}, refs) + end + @doc """ `accept_teleport/3` is called when a client accepts a teleportation as sent by the Synchronize Player Position packet (TODO: Teleport Player API). This lets you know that the client is now where you expect it to be. diff --git a/apps/amethyst/lib/states/configuration.ex b/apps/amethyst/lib/states/configuration.ex index e6f6c39..6513ac6 100644 --- a/apps/amethyst/lib/states/configuration.ex +++ b/apps/amethyst/lib/states/configuration.ex @@ -271,33 +271,39 @@ defmodule Amethyst.ConnectionState.Configuration do send(self(), {:set_state, Amethyst.ConnectionState.Play}) game = Application.fetch_env!(:amethyst, :default_game) |> Amethyst.GameCoordinator.find_or_create() state = state |> Map.put(:game, game) - if Amethyst.API.Game.login(game, state) == :reject do - send(self(), {:disconnect, "Default game rejected connection"}) - :ok - else - send(self(), {:send_packet, %{ - packet_type: :login, - entity_id: 0, - is_hardcore: false, - dimensions: [%{name: "minecraft:overworld"}], - max_players: 0, - view_distance: 16, - simulation_distance: 16, - reduced_debug_info: false, - enable_respawn_screen: true, - do_limited_crafting: false, - dimension_type: 0, - dimension_name: "minecraft:overworld", - hashed_seed: 0, - game_mode: 3, - previous_game_mode: -1, - is_debug: false, - is_flat: false, - death_location: nil, - portal_cooldown: 0, - enforces_secure_chat: false - }}) - state + login = Amethyst.API.Game.login(game, state) + case login do + :reject -> + send(self(), {:disconnect, "Default game rejected connection"}) + :ok + {:accept, {x, y, z}, {yaw, pitch}} -> + send(self(), {:send_packet, %{ + packet_type: :login, + entity_id: 0, + is_hardcore: false, + dimensions: [%{name: "minecraft:overworld"}], + max_players: 0, + view_distance: 16, + simulation_distance: 16, + reduced_debug_info: false, + enable_respawn_screen: true, + do_limited_crafting: false, + dimension_type: 0, + dimension_name: "minecraft:overworld", + hashed_seed: 0, + game_mode: 3, + previous_game_mode: -1, + is_debug: false, + is_flat: false, + death_location: nil, + portal_cooldown: 0, + enforces_secure_chat: false + }}) + send(self(), {:send_packet, %{ + packet_type: :synchronize_player_position, + x: x, y: y, z: z, yaw: yaw, pitch: pitch, teleport_id: 0, flags: 0x00 + }}) + state end end diff --git a/apps/amethyst/lib/states/play.ex b/apps/amethyst/lib/states/play.ex index 42a043f..0dbfd2c 100644 --- a/apps/amethyst/lib/states/play.ex +++ b/apps/amethyst/lib/states/play.ex @@ -60,21 +60,48 @@ defmodule Amethyst.ConnectionState.Play do ] Macros.defpacket_serverbound :confirm_teleportation, 0x00, 767, [teleport_id: :varint] + Macros.defpacket_serverbound :set_player_position, 0x1A, 767, [ + x: :double, + feet_y: :double, + z: :double, + on_ground: :bool + ] + Macros.defpacket_serverbound :set_player_position_and_rotation, 0x1B, 767, [ + x: :double, + feet_y: :double, + z: :double, + yaw: :float, + pitch: :float, + on_ground: :bool + ] + Macros.defpacket_serverbound :set_player_rotation, 0x1C, 767, [ + yaw: :float, + pitch: :float, + on_ground: :bool # I don't understand their obsession with this... + ] - @spec handle( - %{ - :packet_type => :confirm_teleportation, - :teleport_id => any(), - optional(any()) => any() - }, - 767, - nil | maybe_improper_list() | map() - ) :: :ok def handle(%{packet_type: :confirm_teleportation, teleport_id: id}, 767, state) do Amethyst.API.Game.accept_teleport(state[:game], id) :ok end + def handle(%{packet_type: :set_player_position_and_rotation, x: x, feet_y: y, z: z, yaw: yaw, pitch: pitch, on_ground: _ground}, 767, state) do + # I don't know why we would ever trust on_ground here... the server computes that + Amethyst.API.Game.player_position(state[:game], {x, y, z}) + Amethyst.API.Game.player_rotation(state[:game], {yaw, pitch}) + :ok + end + def handle(%{packet_type: :set_player_position, x: x, feet_y: y, z: z, on_ground: _ground}, 767, state) do + # I don't know why we would ever trust on_ground here... the server computes that + Amethyst.API.Game.player_position(state[:game], {x, y, z}) + :ok + end + def handle(%{packet_type: :set_player_rotation, yaw: yaw, pitch: pitch, on_ground: _ground}, 767, state) do + # I don't know why we would ever trust on_ground here... the server computes that + Amethyst.API.Game.player_rotation(state[:game], {yaw, pitch}) + :ok + end + def disconnect(reason) do %{ packet_type: :disconnect, diff --git a/apps/example_game/lib/example/game.ex b/apps/example_game/lib/example/game.ex index 4754215..7e8caa5 100644 --- a/apps/example_game/lib/example/game.ex +++ b/apps/example_game/lib/example/game.ex @@ -12,20 +12,22 @@ defmodule Example.Game do def login(from, cfg, refs) do Logger.info("Player logged in from #{inspect(from)}: #{inspect(cfg)}") Logger.info("The refs for this game are #{inspect(refs)}") - #send(from, {:send_packet, %{ - # packet_type: :synchronize_player_position, - # x: 0.0, y: 0.0, z: 0.0, yaw: 0.0, pitch: 0.0, flags: 0x00, - # teleport_id: 0 - #}}) - :accept + {:accept, {0.0, 0.0, 0.0}, {0.0, 0.0}} end @impl true + @spec player_position(any(), {any(), any(), any()}, any()) :: :ok def player_position(from, {x, y, z}, _refs) do Logger.info("Player at #{inspect(from)} moved to #{x}, #{y}, #{z}") :ok end + @impl true + def player_rotation(from, {yaw, pitch}, _refs) do + Logger.info("Player at #{inspect(from)} rotated to #{yaw}, #{pitch}") + :ok + end + @impl true def accept_teleport(from, id, _state_refs) do Logger.info("Player at #{inspect(from)} accepted teleport #{inspect(id)}")