diff --git a/lib/nbt.ex b/lib/nbt.ex new file mode 100644 index 0000000..f3f75fe --- /dev/null +++ b/lib/nbt.ex @@ -0,0 +1,117 @@ +# 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.NBT.Write do + @moduledoc """ + This module contains the logic for writing NBT data. + """ + + def write(name, {type, value}) do + <> + end + + def write_net({type, value}) do + <> + end + + defp type_id(:end), do: 0 + defp type_id(:byte), do: 1 + defp type_id(:short), do: 2 + defp type_id(:int), do: 3 + defp type_id(:long), do: 4 + defp type_id(:float), do: 5 + defp type_id(:double), do: 6 + defp type_id(:byte_array), do: 7 + defp type_id(:string), do: 8 + defp type_id(:list), do: 9 + defp type_id(:compound), do: 10 + defp type_id(:int_array), do: 11 + defp type_id(:long_array), do: 12 + + defp payload(:byte, value), do: <> + defp payload(:short, value), do: <> + defp payload(:int, value), do: <> + defp payload(:long, value), do: <> + defp payload(:float, value), do: <> + defp payload(:double, value), do: <> + defp payload(:byte_array, values), do: <> <> Enum.map_join(values, "", &payload(:byte, &1)) + defp payload(:string, value), do: <> <> value + defp payload(:list, {type, values}), do: <> <> <> <> Enum.map_join(values, "", &payload(type, &1)) + defp payload(:compound, values), do: Enum.map_join(values, "", fn {name, {type, value}} -> write(name, {type, value}) end) <> <<0::size(8)>> + defp payload(:int_array, values), do: <> <> Enum.map_join(values, "", &payload(:int, &1)) + defp payload(:long_array, values), do: <> <> Enum.map_join(values, "", &payload(:long, &1)) + + defmacro byte(value) do + quote do + {:byte, unquote(value)} + end + end + defmacro short(value) do + quote do + {:short, unquote(value)} + end + end + defmacro int(value) do + quote do + {:int, unquote(value)} + end + end + defmacro long(value) do + quote do + {:long, unquote(value)} + end + end + defmacro float(value) do + quote do + {:float, unquote(value)} + end + end + defmacro double(value) do + quote do + {:double, unquote(value)} + end + end + defmacro byte_array(values) do + quote do + {:byte_array, unquote(values)} + end + end + defmacro string(value) do + quote do + {:string, unquote(value)} + end + end + defmacro list(type, values) do + quote do + {:list, {unquote(type), unquote(values)}} + end + end + defmacro compound(values) do + quote do + {:compound, unquote(values)} + end + end + defmacro int_array(values) do + quote do + {:int_array, unquote(values)} + end + end + defmacro long_array(values) do + quote do + {:long_array, unquote(values)} + end + end +end diff --git a/lib/servers/configuration.ex b/lib/servers/configuration.ex index addd285..e9752c9 100644 --- a/lib/servers/configuration.ex +++ b/lib/servers/configuration.ex @@ -138,8 +138,15 @@ defmodule Amethyst.Server.Configuration do Write.varint(0x06) end # Registry Data https://wiki.vg/Protocol#Registry_Data - def serialize({:registry_data, _id, _entries}) do - raise ArgumentError, "Packet 'Registry Data' is not yet implemented" + def serialize({:registry_data, id, entries}) do + Write.varint(0x07) <> Write.string(id) <> Write.varint(length(entries)) <> Enum.map_join(entries, "", fn {name, nbt} -> + Write.string(name) <> + if nbt == nil do + Write.bool(false) + else + Write.bool(true) <> Amethyst.NBT.Write.write_net(nbt) + end + end) end # Remove Resource Pack https://wiki.vg/Protocol#Remove_Resource_Pack_(configuration) def serialize({:remove_resource_pack, id}) do @@ -239,13 +246,82 @@ defmodule Amethyst.Server.Configuration do # TODO: Here we should create the game handling task for this player and give it # this data. transmit({:clientbound_plugin_message, "minecraft:brand", Write.string("amethyst")}, client) - transmit({:clientbound_known_packs, [{"minecraft", "core", "1.21"}]}, client) + transmit({:clientbound_known_packs, [{"minecraft", "core", "1.21"}, {"minecraft", "dimension_type", "1.21"}]}, client) {:ok, state} end # Serverbound Known Packs https://wiki.vg/Protocol#Serverbound_Known_Packs def handle({:serverbound_known_packs, _packs}, client, state) do # L + ratio + don't care + didn't ask + finish configuration - # TODO: we should send registries i think? does amethyst need to deal with vanilla registries at all? god only knows + import Amethyst.NBT.Write + # TODO: This shouldn't be hard-coded but we obviously don't know what we will need until we have game handling + # This can at least be followed as a "minimum" of what we need to send for the client not to complain + transmit({:registry_data, "minecraft:dimension_type", [{"amethyst:basic", compound(%{ + "has_skylight" => byte(1), + "has_ceiling" => byte(0), + "ultrawarm" => byte(0), + "natural" => byte(1), + "coordinate_scale" => float(1.0), + "bed_works" => byte(1), + "respawn_anchor_works" => byte(1), + "min_y" => int(0), + "height" => int(256), + "logical_height" => int(256), + "infiniburn" => string("#"), + "effects" => string("minecraft:overworld"), + "ambient_light" => float(0.0), + "piglin_safe" => byte(0), + "has_raids" => byte(1), + "monster_spawn_light_level" => int(0), + "monster_spawn_block_light_limit" => int(0) + })}]}, client) + transmit({:registry_data, "minecraft:painting_variant", [{"minecraft:kebab", compound(%{ + "asset_id" => string("minecraft:kebab"), + "height" => int(1), + "width" => int(1), + })}]}, client) + transmit({:registry_data, "minecraft:wolf_variant", [{"minecraft:wolf_ashen", compound(%{ + "wild_texture" => string("minecraft:entity/wolf/wolf_ashen"), + "tame_texture" => string("minecraft:entity/wolf/wolf_ashen_tame"), + "angry_texture" => string("minecraft:entity/wolf/wolf_ashen_angry"), + "biomes" => string("amethyst:basic"), + })}]}, client) + # https://gist.github.com/WinX64/ab8c7a8df797c273b32d3a3b66522906 minecraft:plains + basic_biome = compound(%{ + "effects" => compound(%{ + "sky_color" => int(7907327), + "water_fog_color" => int(329011), + "fog_color" => int(12638463), + "water_color" => int(4159204), + "mood_sound" => compound(%{ + "tick_delay" => int(6000), + "offset" => float(2.0), + "sound" => string("minecraft:ambient.cave"), + "block_search_extent" => int(8) + }), + }), + "has_precipitation" => byte(1), + "temperature" => float(0.8), + "downfall" => float(0.4), + }) + transmit({:registry_data, "minecraft:worldgen/biome", [ + {"amethyst:basic", basic_biome}, {"minecraft:plains", basic_biome} + ]}, client) + # holy fucking shit + generic_damage = compound(%{ + "scaling" => string("when_caused_by_living_non_player"), + "exhaustion" => float(0.0), + "message_id" => string("generic") + }) + transmit({:registry_data, "minecraft:damage_type", [ + {"minecraft:in_fire", generic_damage}, {"minecraft:campfire", generic_damage}, {"minecraft:lightning_bolt", generic_damage}, + {"minecraft:on_fire", generic_damage}, {"minecraft:lava", generic_damage}, {"minecraft:hot_floor", generic_damage}, + {"minecraft:in_wall", generic_damage}, {"minecraft:cramming", generic_damage}, {"minecraft:drown", generic_damage}, + {"minecraft:starve", generic_damage}, {"minecraft:cactus", generic_damage}, {"minecraft:fall", generic_damage}, + {"minecraft:fly_into_wall", generic_damage}, {"minecraft:out_of_world", generic_damage}, {"minecraft:generic", generic_damage}, + {"minecraft:magic", generic_damage}, {"minecraft:wither", generic_damage}, {"minecraft:dragon_breath", generic_damage}, + {"minecraft:dry_out", generic_damage}, {"minecraft:sweet_berry_bush", generic_damage}, {"minecraft:freeze", generic_damage}, + {"minecraft:stalagmite", generic_damage}, {"minecraft:outside_border", generic_damage}, {"minecraft:generic_kill", generic_damage}, + ]}, client) transmit({:finish_configuration}, client) {:ok, state} end