diff --git a/apps/amethyst/lib/api/game.ex b/apps/amethyst/lib/api/game.ex index a9c5c03..6b86c5e 100644 --- a/apps/amethyst/lib/api/game.ex +++ b/apps/amethyst/lib/api/game.ex @@ -20,7 +20,8 @@ defmodule Amethyst.API.Game do a game with Amethyst. """ @callback instantiate() :: {:ok, new_state :: term} | {:error, reason :: term} - @callback login(connection_process :: pid(), player_cfg :: keyword(), state :: term) :: {:ok, new_state :: term} | {:refuse, new_state :: term} + @callback login(from :: pid(), player_cfg :: keyword(), state :: term) :: {:ok, new_state :: term} | {:refuse, new_state :: term} + @callback player_position(from :: pid(), {x :: float(), y :: float(), z :: float()}, state :: term) :: {{x :: float(), y :: float(), z :: float()}, state :: term} defmacro __using__(opts) do meta = Keyword.get(opts, :meta, []) @@ -54,6 +55,12 @@ defmodule Amethyst.API.Game do send(caller, :refuse) loop(mod, state) end + {:player_position, caller, pos} -> + case mod.player_position(caller, pos, state) do + {pos, state} -> + send(caller, pos) + loop(mod, state) + end end end @@ -64,4 +71,12 @@ defmodule Amethyst.API.Game do :refuse -> :refuse end end + + @spec player_position(atom() | pid() | port() | reference() | {atom(), atom()}, {float(), float(), float()}) :: {float(), float(), float()} + def player_position(pid, pos) do + send(pid, {:player_position, self(), pos}) + receive do + pos -> pos + end + end end diff --git a/apps/amethyst/lib/servers/play.ex b/apps/amethyst/lib/servers/play.ex index 162d386..eb1baf0 100644 --- a/apps/amethyst/lib/servers/play.ex +++ b/apps/amethyst/lib/servers/play.ex @@ -35,6 +35,26 @@ defmodule Amethyst.Server.Play do {[channel], data} = Read.start(data) |> Read.string |> Read.stop {:serverbound_plugin_message, channel, data} end + @impl true + def deserialize(0x1A, data) do + {[x, feet_y, z, on_ground], ""} = Read.start(data) |> Read.double |> Read.double |> Read.double |> Read.bool |> Read.stop + {:set_player_position, x, feet_y, z, on_ground} + end + @impl true + def deserialize(0x1B, data) do + {[x, feet_y, z, yaw, pitch, on_ground], ""} = Read.start(data) |> Read.double |> Read.double |> Read.double |> Read.float |> Read.float |> Read.bool |> Read.stop + {:set_position_position_and_rotation, x, feet_y, z, yaw, pitch, on_ground} + end + @impl true + def deserialize(0x1C, data) do + {[yaw, pitch, on_ground], ""} = Read.start(data) |> Read.float |> Read.float |> Read.bool |> Read.stop() + {:set_player_rotation, yaw, pitch, on_ground} + end + @impl true + def deserialize(0x1D, data) do + {[on_ground], ""} = Read.start(data) |> Read.bool |> Read.stop() + {:set_player_on_ground, on_ground} + end def deserialize(type, _) do raise RuntimeError, "Got unknown packet type #{type}!" end @@ -80,8 +100,25 @@ defmodule Amethyst.Server.Play do Logger.debug("Got plugin message #{channel} with data #{inspect(data)}") {:ok, state} end - def handle(tuple, _, state) do - Logger.error("Unhandled but known packet #{elem(tuple, 0)}") + def handle({:set_player_position, x, y, z, _on_ground}, _client, state) do + # We do not accept movement packets until we get a :confirm_teleportation + if Keyword.fetch(state, :awaiting_tp_confirm) != :error do + Logger.warning("Got :set_player_position packet while waiting for :confirm_teleportation!") + {:ok, state} + else + game = Keyword.fetch!(state, :game) + {nx, ny, nz} = Amethyst.API.Game.player_position(game, {x, y, z}) + # If pos is not very close to the position the client wants, synchronize them + # TODO: Make a general-purpose distance function + dist = :math.sqrt((nx - x)**2 + (ny - y)**2 + (nz - z)**2) + if dist > 0.01 do + # TODO: Implement synchronization + raise "The game wants to resynchronize the player, but I haven't implemented that yet" + end + end + end + def handle(tuple, _, state) do # TODO: These error cases should be somehow moved into some shared area? Maybe even moved out of the modules themselves + Logger.error("Unhandled but known packet #{inspect(tuple)}") {:unhandled, state} end