# Amethyst - An experimental Minecraft server written in Elixir. # Copyright (C) 2024 KodiCraft # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . defmodule Amethyst.Server.Play do @moduledoc """ This module contains the logic for the Play stage of the server. """ require Logger use Amethyst.Server alias Amethyst.Minecraft.Read alias Amethyst.Minecraft.Write @impl true def init(state) do state end ## DESERIALIZATION @impl true def deserialize(0x12, data) do {[channel], data} = Read.start(data) |> Read.string |> Read.stop {:serverbound_plugin_message, channel, data} end def deserialize(type, _) do raise RuntimeError, "Got unknown packet type #{type}!" end ## SERIALIZATION @impl true # Login https://wiki.vg/Protocol#Login_(play) def serialize({:login, eid, hardcore, dimensions, max_players, view_distance, simulation_distance, reduce_debug, enable_respawn_screen, limited_crafting, dim_type, dim_name, hashed_seed, gamemode, prev_gm, is_debug, is_flat, death_loc, portal_cooldown, enforce_chat}) when byte_size(hashed_seed) == 8 do # TODO: This is a big unreadable slab of serialization which makes bugs really hard to catch, it needs a proper rework at some point Write.varint(0x2B) <> Write.int(eid) <> Write.bool(hardcore) <> Write.varint(length(dimensions)) <> Write.list(dimensions, &Write.string/1) <> Write.varint(max_players) <> Write.varint(view_distance) <> Write.varint(simulation_distance) <> Write.bool(reduce_debug) <> Write.bool(enable_respawn_screen) <> Write.bool(limited_crafting) <> Write.varint(dim_type) <> Write.string(dim_name) <> hashed_seed <> Write.ubyte(gamemode_id(gamemode)) <> Write.byte(gamemode_id(prev_gm)) <> Write.bool(is_debug) <> Write.bool(is_flat) <> if(death_loc == nil, do: Write.bool(false), else: Write.bool(true) <> Write.string(elem(death_loc, 0)) <> Write.position(elem(death_loc, 1))) <> Write.varint(portal_cooldown) <> Write.bool(enforce_chat) end def serialize(packet) do raise ArgumentError, "Tried serializing unknown packet #{inspect(packet)}" end ## HANDLING @impl true def handle({:serverbound_plugin_message, channel, data}, _client, state) 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)}") {:unhandled, state} end defp gamemode_id(gm) do case gm do nil -> -1 :survival -> 0 :creative -> 1 :adventure -> 2 :spectator -> 3 end end end