merge
This commit is contained in:
parent
7821b20758
commit
f083a99702
@ -16,8 +16,9 @@
|
||||
|
||||
defmodule Amethyst.API.Game do
|
||||
@moduledoc """
|
||||
This module includes the interface for defining and registering
|
||||
a game with Amethyst.
|
||||
This behaviour should be implemented by any Amethyst Game. It additionally
|
||||
contains functions that the internal connection handler code uses to more
|
||||
conveniently call a game's callbacks.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
@ -32,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
|
||||
@ -43,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
|
||||
@ -60,6 +64,39 @@ 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.
|
||||
|
||||
- 'from' is the PID of the player's connection process (see `login/3`).
|
||||
- 'id' is the teleport ID (TODO: Teleport Player API)
|
||||
- 'state_refs' are your references (see `instantiate/1`)
|
||||
"""
|
||||
@callback accept_teleport(from :: pid(), id :: integer(), state_refs :: map()) :: :ok
|
||||
def accept_teleport(%{:mod => mod, :refs => refs}, id) do
|
||||
mod.accept_teleport(self(), id, refs)
|
||||
end
|
||||
@doc """
|
||||
The terrain of a specific chunk column. This is automatically used to load chunks for a player.
|
||||
|
||||
For now, this data must be formatted as a 3D list, indexed as [y][z][x].
|
||||
"""
|
||||
@callback chunk(from :: pid(), {x :: integer(), z :: integer()}, state_refs :: map()) :: [[[pos_integer()]]]
|
||||
def chunk(%{:mod => mod, :refs => refs}, pos) do
|
||||
mod.chunk(self(), pos, refs)
|
||||
end
|
||||
@doc """
|
||||
Whether or not this game instance can be joined by a new player. This should include basic logic such as
|
||||
if joining makes sense, for instance if the game is full or if the game has already started.
|
||||
"""
|
||||
|
@ -60,6 +60,42 @@ defmodule Amethyst.ConnectionHandler do
|
||||
{:set_version, newversion} ->
|
||||
Logger.debug("Switching to version #{newversion} from #{version}")
|
||||
loop(socket, connstate, newversion, state)
|
||||
{:set_position, position} ->
|
||||
Logger.debug("Updating client position to #{inspect(position)}")
|
||||
prev_position = Map.get(state, :position)
|
||||
state = Map.put(state, :position, position)
|
||||
# If there was no prev position, we consider that we
|
||||
# definitely moved
|
||||
prev_cp = if prev_position == nil do nil else chunk_pos(elem(prev_position, 0), elem(prev_position, 1)) end
|
||||
cp = chunk_pos(elem(position, 0), elem(position, 1))
|
||||
if prev_cp != cp do
|
||||
Logger.debug("Client entered new chunk #{inspect(cp)}")
|
||||
# We changed chunk borders, update center chunk and begin sending new chunks
|
||||
send(self(), {:send_packet, %{
|
||||
packet_type: :set_center_chunk,
|
||||
chunk_x: elem(cp, 0),
|
||||
chunk_z: elem(cp, 1)
|
||||
}})
|
||||
# Figure out which new chunks are visible
|
||||
prev_chunks =
|
||||
if prev_cp == nil do
|
||||
MapSet.new([])
|
||||
else
|
||||
MapSet.new(visible_chunks_from(elem(prev_cp, 0), elem(prev_cp, 1), Map.get(state, :view_distance, 16)))
|
||||
end
|
||||
chunks = MapSet.new(visible_chunks_from(elem(cp, 0), elem(cp, 1), Map.get(state, :view_distance, 16)))
|
||||
new_chunks = MapSet.difference(chunks, prev_chunks)
|
||||
# We can process all chunks in parallel
|
||||
me = self()
|
||||
ts = state |> Map.get(:game) |> Map.get(:refs) |> Map.get(:task_supervisor)
|
||||
Task.Supervisor.async(ts, fn ->
|
||||
Task.Supervisor.async_stream(ts,
|
||||
new_chunks,
|
||||
fn chunk -> process_chunk(me, chunk, state) end,
|
||||
[ordered: false]
|
||||
) |> Stream.run() end)
|
||||
end
|
||||
loop(socket, connstate, version, state)
|
||||
{:send_packet, packet} ->
|
||||
Logger.debug("Sending packet #{inspect(packet)}")
|
||||
send_packet(socket, connstate, packet, version)
|
||||
@ -73,6 +109,103 @@ defmodule Amethyst.ConnectionHandler do
|
||||
end
|
||||
end
|
||||
|
||||
defp chunk_pos(x, z) do
|
||||
{div(floor(x), 16), div(floor(z), 16)}
|
||||
end
|
||||
|
||||
defp visible_chunks_from(x, z, view_distance) do
|
||||
{cx, cz} = chunk_pos(x, z)
|
||||
(cx - view_distance - 3 .. cx + view_distance + 3) |> Enum.flat_map(fn ix ->
|
||||
(cz - view_distance - 3 .. cz + view_distance + 3) |> Enum.map(fn iz ->
|
||||
{ix, iz}
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
defp process_chunk(to, chunk, state) do
|
||||
import Amethyst.NBT.Write
|
||||
alias Amethyst.Minecraft.Write
|
||||
|
||||
chunk_array = Amethyst.API.Game.chunk(Map.get(state, :game), chunk)
|
||||
{cx, cz} = chunk
|
||||
|
||||
# TODO: Actually do heightmaps
|
||||
# TODO: Doing all this processing could be at home somewhere else
|
||||
heightmaps = compound(%{})
|
||||
|
||||
data = Enum.chunk_every(chunk_array, 16, 16, 0) # 0 -> air
|
||||
|> Enum.reduce("", fn chunk_section, acc ->
|
||||
blocks = chunk_section |> List.flatten()
|
||||
block_count = blocks |> Enum.filter(&(&1 != 0)) |> length
|
||||
|
||||
# Put together the palette
|
||||
unique_blocks = MapSet.new(blocks)
|
||||
min_bpe = MapSet.size(unique_blocks) |> :math.log2() |> ceil()
|
||||
paletted_container_data = case min_bpe do
|
||||
0 -> <<0::8>> <>
|
||||
# SINGLE VALUED
|
||||
Write.varint(MapSet.to_list(unique_blocks) |> List.first()) <>
|
||||
Write.varint(0) # No data, empty pallette
|
||||
min_bpe when min_bpe in 1..8 ->
|
||||
# INDIRECT
|
||||
# Minimum bpe accepted by minecraft is 4
|
||||
bpe = max(min_bpe, 4)
|
||||
palette = MapSet.to_list(unique_blocks) |>
|
||||
Enum.with_index() |>
|
||||
Map.new(fn {i, v} -> {v, i} end)
|
||||
paletted_blocks = blocks |>
|
||||
Enum.map(&(Map.get(palette, &1)))
|
||||
paletted_data = long_aligned_bit_string_reduce(paletted_blocks, bpe)
|
||||
|
||||
Write.ubyte(bpe) <>
|
||||
Write.varint(map_size(palette)) <>
|
||||
Enum.reduce(0..(map_size(palette)-1), "", fn i, acc ->
|
||||
acc <> Write.varint(Map.get(palette, i))
|
||||
end) <>
|
||||
Write.varint(floor(bit_size(paletted_data) / 64)) <>
|
||||
paletted_data
|
||||
_ ->
|
||||
# DIRECT
|
||||
data = long_aligned_bit_string_reduce(blocks, 15)
|
||||
Write.ubyte(15) <>
|
||||
Write.varint(floor(bit_size(data) / 64)) <>
|
||||
data
|
||||
end
|
||||
|
||||
# TODO: Send biome data, if that even makes sense
|
||||
acc <> Write.short(block_count) <> paletted_container_data <> <<0::8, 0::8>>
|
||||
end)
|
||||
|
||||
send(to, {:send_packet, %{
|
||||
packet_type: :chunk_data_and_update_light,
|
||||
chunk_x: cx, chunk_z: cz,
|
||||
heightmaps: heightmaps,
|
||||
data: data,
|
||||
block_entities: [],
|
||||
# TODO: Light
|
||||
sky_light_mask: <<0>>,
|
||||
block_light_mask: <<0>>,
|
||||
empty_sky_light_mask: <<0>>,
|
||||
empty_block_light_mask: <<0>>,
|
||||
sky_light_arrays: [],
|
||||
block_light_arrays: []
|
||||
}})
|
||||
:ok
|
||||
end
|
||||
|
||||
defp long_aligned_bit_string_reduce(values, bpe) do
|
||||
values |> Enum.reduce(<<>>, fn value, acc ->
|
||||
next = <<acc::bitstring, value::size(bpe)-big>>
|
||||
# man i hope they dont suddenly change the size of a long
|
||||
if rem(bit_size(next), 64) + bpe < 64 do
|
||||
# gotta pad it
|
||||
<<next::bitstring, 0::big-size(64 - rem(bit_size(next), 64))>>
|
||||
else
|
||||
next
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp handle_packet(id, data, connstate, version, state) do
|
||||
try do
|
||||
packet = connstate.deserialize(id, version, data)
|
||||
|
@ -21,7 +21,7 @@ defmodule Amethyst.TCPListener do
|
||||
"""
|
||||
|
||||
def accept(port) do
|
||||
{:ok, socket} = :gen_tcp.listen(port, [:binary, packet: 0, active: false, reuseaddr: true])
|
||||
{:ok, socket} = :gen_tcp.listen(port, [:binary, packet: 0, active: false, reuseaddr: true, nodelay: true])
|
||||
Logger.info("Listening on port #{port}")
|
||||
loop_acceptor(socket)
|
||||
end
|
||||
|
@ -258,6 +258,8 @@ defmodule Amethyst.ConnectionState.Configuration do
|
||||
%{id: "minecraft:stalagmite", data: generic_damage},
|
||||
%{id: "minecraft:outside_border", data: generic_damage},
|
||||
%{id: "minecraft:generic_kill", data: generic_damage},
|
||||
%{id: "minecraft:hot_floor", data: generic_damage},
|
||||
%{id: "minecraft:in_wall", data: generic_damage},
|
||||
]
|
||||
}})
|
||||
send(self(), {:send_packet, %{packet_type: :finish_configuration}})
|
||||
@ -269,10 +271,12 @@ 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
|
||||
login = Amethyst.API.Game.login(game, state)
|
||||
case login do
|
||||
:reject ->
|
||||
send(self(), {:disconnect, "Default game rejected connection"})
|
||||
:ok
|
||||
else
|
||||
{:accept, {x, y, z}, {yaw, pitch}} ->
|
||||
send(self(), {:send_packet, %{
|
||||
packet_type: :login,
|
||||
entity_id: 0,
|
||||
@ -295,6 +299,16 @@ defmodule Amethyst.ConnectionState.Configuration do
|
||||
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
|
||||
}})
|
||||
send(self(), {:send_packet, Amethyst.ConnectionState.Play.ge_start_waiting_for_level_chunks(767)})
|
||||
send(self(), {:send_packet, %{packet_type: :set_center_chunk,
|
||||
chunk_x: div(floor(x), 16),
|
||||
chunk_z: div(floor(z), 16)
|
||||
}})
|
||||
send(self(), {:set_position, {x, y, z}})
|
||||
state
|
||||
end
|
||||
end
|
||||
|
@ -70,7 +70,7 @@ defmodule Amethyst.ConnectionState.Login do
|
||||
uuid: player_uuid,
|
||||
username: name,
|
||||
properties: [],
|
||||
strict_error_handling: false
|
||||
strict_error_handling: true
|
||||
}})
|
||||
:ok
|
||||
end
|
||||
|
@ -16,18 +16,18 @@
|
||||
|
||||
defmodule Amethyst.ConnectionState.Macros do
|
||||
require Logger
|
||||
defmacro defpacket_serverbound(name, id, version, signature) do
|
||||
defmacro defpacket_serverbound(name, id, version, signature, where \\ true) do
|
||||
quote do
|
||||
def deserialize(unquote(id), unquote(version), data) do
|
||||
def deserialize(unquote(id), unquote(version), data) when unquote(where) do
|
||||
{packet, ""} = Amethyst.ConnectionState.Macros.read_signature(data, unquote(signature))
|
||||
packet |> Map.put(:packet_type, unquote(name))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmacro defpacket_clientbound(name, id, version, signature) do
|
||||
defmacro defpacket_clientbound(name, id, version, signature, where \\ true) do
|
||||
quote do
|
||||
def serialize(%{packet_type: unquote(name)} = packet, unquote(version)) do
|
||||
def serialize(%{packet_type: unquote(name)} = packet, unquote(version)) when unquote(where) do
|
||||
if Amethyst.ConnectionState.Macros.check_type(packet, unquote(signature)) do
|
||||
Amethyst.Minecraft.Write.varint(unquote(id)) <> Amethyst.ConnectionState.Macros.write_signature(packet, unquote(signature))
|
||||
else
|
||||
@ -79,7 +79,6 @@ defmodule Amethyst.ConnectionState.Macros do
|
||||
|
||||
def write_signature(packet, signature) do
|
||||
Enum.reduce(signature, "", fn {name, type}, acc ->
|
||||
#acc <> apply(Write, type, [Map.get(packet, name)])
|
||||
case type do
|
||||
{:optional, {:compound, signature}} ->
|
||||
case Map.get(packet, name) do
|
||||
@ -96,6 +95,7 @@ defmodule Amethyst.ConnectionState.Macros do
|
||||
Enum.reduce(Map.get(packet, name), "", fn item, acc ->
|
||||
acc <> write_signature(item, signature)
|
||||
end)
|
||||
{:literal, {type, value}} -> acc <> apply(Write, type, [value])
|
||||
t -> acc <> apply(Write, t, [Map.get(packet, name)])
|
||||
end
|
||||
end)
|
||||
@ -105,7 +105,12 @@ defmodule Amethyst.ConnectionState.Macros do
|
||||
try do
|
||||
Enum.all?(signature, fn {name, type} ->
|
||||
case Map.get(packet, name, :missing) do
|
||||
:missing -> throw {:missing, name}
|
||||
:missing ->
|
||||
if elem(type, 0) == :literal do
|
||||
true
|
||||
else
|
||||
throw {:missing, name}
|
||||
end
|
||||
value -> case type_matches(value, type) do
|
||||
true -> true
|
||||
false -> throw {:mismatch, name, value, type}
|
||||
@ -126,8 +131,8 @@ defmodule Amethyst.ConnectionState.Macros do
|
||||
def type_matches(value, :ushort) when is_integer(value) and value in 0..65535, do: true
|
||||
def type_matches(value, :int) when is_integer(value) and value in -2147483648..2147483647, do: true
|
||||
def type_matches(value, :long) when is_integer(value) and value in -9223372036854775808..9223372036854775807, do: true
|
||||
def type_matches(value, :float) when is_float(value), do: true
|
||||
def type_matches(value, :double) when is_float(value), do: true
|
||||
def type_matches(value, :float) when is_number(value), do: true
|
||||
def type_matches(value, :double) when is_number(value), do: true
|
||||
def type_matches(value, :varint) when is_integer(value) and value in -2147483648..2147483647, do: true
|
||||
def type_matches(value, :varlong) when is_integer(value) and value in -9223372036854775808..9223372036854775807, do: true
|
||||
def type_matches(value, :uuid) when is_binary(value) and byte_size(value) == 36, do: true
|
||||
|
@ -25,6 +25,28 @@ defmodule Amethyst.ConnectionState.Play do
|
||||
"""
|
||||
|
||||
Macros.defpacket_clientbound :disconnect, 0x1D, 767, [reason: :nbt]
|
||||
Macros.defpacket_clientbound :chunk_data_and_update_light, 0x27, 767, [
|
||||
chunk_x: :int,
|
||||
chunk_z: :int,
|
||||
heightmaps: :nbt,
|
||||
data: :byte_array,
|
||||
block_entities: {:array, [
|
||||
packed_xz: :ubyte, # TODO: This would be interesting to have in a clearer format
|
||||
y: :short,
|
||||
type: :varint,
|
||||
data: :nbt
|
||||
]},
|
||||
sky_light_mask: :raw,
|
||||
block_light_mask: :raw,
|
||||
empty_sky_light_mask: :raw,
|
||||
empty_block_light_mask: :raw,
|
||||
sky_light_arrays: {:array, [
|
||||
sky_light_array: :byte_array
|
||||
]},
|
||||
block_light_arrays: {:array, [
|
||||
block_light_array: :byte_array
|
||||
]}
|
||||
]
|
||||
Macros.defpacket_clientbound :login, 0x2B, 767, [
|
||||
entity_id: :int,
|
||||
is_hardcore: :bool,
|
||||
@ -49,6 +71,133 @@ defmodule Amethyst.ConnectionState.Play do
|
||||
portal_cooldown: :varint,
|
||||
enforces_secure_chat: :bool,
|
||||
]
|
||||
Macros.defpacket_clientbound :player_info_update_add_player, 0x2E, 767, [
|
||||
actions: {:literal, 0x01},
|
||||
players: {:array, [
|
||||
uuid: :uuid,
|
||||
name: :string,
|
||||
properties: {:array, [
|
||||
name: :string,
|
||||
value: :string,
|
||||
signature: {:optional, :string},
|
||||
]}
|
||||
]}
|
||||
]
|
||||
Macros.defpacket_clientbound :player_info_update_initialize_chat, 0x2E, 767, [
|
||||
actions: {:literal, 0x02},
|
||||
players: {:array, [
|
||||
uuid: :uuid,
|
||||
data: {:optional, {:compound, [
|
||||
chat_session_id: :uuid,
|
||||
public_key_expiry_time: :long,
|
||||
encoded_public_key: :byte_array,
|
||||
public_key_signature: :byte_array
|
||||
]}}
|
||||
]}
|
||||
]
|
||||
Macros.defpacket_clientbound :player_info_update_update_game_mode, 0x2E, 767, [
|
||||
actions: {:literal, 0x04},
|
||||
players: {:array, [
|
||||
uuid: :uuid,
|
||||
gamemode: :varint
|
||||
]}
|
||||
]
|
||||
Macros.defpacket_clientbound :player_info_update_update_listed, 0x2E, 767, [
|
||||
actions: {:literal, 0x08},
|
||||
players: {:array, [
|
||||
uuid: :uuid,
|
||||
listed: :bool
|
||||
]}
|
||||
]
|
||||
Macros.defpacket_clientbound :player_info_update_update_latency, 0x2E, 767, [
|
||||
actions: {:literal, 0x10},
|
||||
players: {:array, [
|
||||
uuid: :uuid,
|
||||
ping: :varint, # Milliseconds
|
||||
]}
|
||||
]
|
||||
Macros.defpacket_clientbound :player_info_update_update_display_name, 0x2E, 767, [
|
||||
actions: {:literal, 0x20},
|
||||
players: {:array, [
|
||||
uuid: :uuid,
|
||||
display_name: {:optional, :nbt}
|
||||
]}
|
||||
]
|
||||
Macros.defpacket_clientbound :synchronize_player_position, 0x40, 767, [
|
||||
x: :double,
|
||||
y: :double,
|
||||
z: :double,
|
||||
yaw: :float,
|
||||
pitch: :float,
|
||||
flags: :byte,
|
||||
teleport_id: :varint
|
||||
]
|
||||
Macros.defpacket_clientbound :set_center_chunk, 0x54, 767, [
|
||||
chunk_x: :varint, chunk_z: :varint
|
||||
]
|
||||
|
||||
Macros.defpacket_clientbound :game_event, 0x22, 767, [
|
||||
event: :ubyte, value: :float
|
||||
]
|
||||
# We can use functions to wrap over this packet and make it a bit clearer.
|
||||
# Taking the protocol version here makes it less portable but whatever, fuck this packet
|
||||
def ge_no_respawn_block_available(767), do: %{packet_type: :game_event, event: 0, value: 0}
|
||||
def ge_begin_raining(767), do: %{packet_type: :game_event, event: 1, value: 0}
|
||||
def ge_end_raining(767), do: %{packet_type: :game_event, event: 2, value: 0}
|
||||
def ge_change_game_mode(767, gm) when is_integer(gm), do: %{packet_type: :game_event, event: 3, value: gm}
|
||||
def ge_win_game(767, credits?) when is_integer(credits?), do: %{packet_type: :game_event, event: 4, value: credits?}
|
||||
def ge_game_event(767, event) when is_integer(event), do: %{packet_type: :game_event, event: 5, value: event}
|
||||
def ge_arrow_hit_player(767), do: %{packet_type: :game_event, event: 6, value: 0}
|
||||
def ge_rain_level_change(767, value) when is_number(value), do: %{packet_type: :game_event, event: 7, value: value}
|
||||
def ge_thunder_level_change(767, value) when is_number(value), do: %{packet_type: :game_event, event: 8, value: value}
|
||||
def ge_play_pufferfish_sting_sound(767), do: %{packet_type: :game_event, event: 9, value: 0}
|
||||
def ge_play_elder_guardian_mob_appearance(767), do: %{packet_type: :game_event, event: 10, value: 0}
|
||||
def ge_enable_respawn_screen(767, enabled?) when is_integer(enabled?), do: %{packet_type: :game_event, event: 11, value: enabled?}
|
||||
def ge_limited_crafting(767, enabled?) when is_integer(enabled?), do: %{packet_type: :game_event, event: 12, value: enabled?}
|
||||
def ge_start_waiting_for_level_chunks(767), do: %{packet_type: :game_event, event: 13, value: 0}
|
||||
|
||||
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...
|
||||
]
|
||||
|
||||
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
|
||||
%{
|
||||
|
@ -12,17 +12,46 @@ 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)}")
|
||||
:accept
|
||||
{:accept, {0.0, 10.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)}")
|
||||
:ok
|
||||
end
|
||||
|
||||
@impl true
|
||||
def joinable?(_refs) do
|
||||
true
|
||||
end
|
||||
|
||||
@impl true
|
||||
def chunk(_from, {x, z}, _state_refs) do
|
||||
# Logger.info("Player at #{inspect(from)} wants to know chunk #{x}, #{z}")
|
||||
(0..255) |> Enum.map(fn y ->
|
||||
if y < 5 do
|
||||
(0..15) |> Enum.map(fn _z ->
|
||||
(0..15) |> Enum.map(fn _x -> 1 end)
|
||||
end)
|
||||
else
|
||||
(0..15) |> Enum.map(fn _z ->
|
||||
(0..15) |> Enum.map(fn _x -> 0 end)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
@ -10,5 +10,5 @@
|
||||
import Config
|
||||
config :logger, :console,
|
||||
level: :debug,
|
||||
format: "$date $time [$level] $metadata$message",
|
||||
format: "$date $time [$level] $metadata$message\n",
|
||||
metadata: []
|
||||
|
1
mix.lock
1
mix.lock
@ -2,6 +2,7 @@
|
||||
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
|
||||
"credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"},
|
||||
"earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"},
|
||||
"elixir_math": {:hex, :elixir_math, "0.1.2", "5655bdf7f34e30906f31cdcf3031b43dd522ce8d2936b60ad4696b2c752bf5c9", [:mix], [], "hexpm", "34f4e4384903097a8ec566784fa8e9aa2b741247d225741f07cc48250c2aa64c"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"},
|
||||
"file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"},
|
||||
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
|
||||
|
Loading…
Reference in New Issue
Block a user