diff --git a/apps/amethyst/lib/apps/connection_handler.ex b/apps/amethyst/lib/apps/connection_handler.ex index 772a054..8b94be1 100644 --- a/apps/amethyst/lib/apps/connection_handler.ex +++ b/apps/amethyst/lib/apps/connection_handler.ex @@ -153,69 +153,94 @@ defmodule Amethyst.ConnectionHandler do 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 + |> Enum.reduce("", fn chunk_section, acc -> + blocks_and_lights = chunk_section |> List.flatten() + block_count = blocks_and_lights |> Enum.filter(fn {bs, _, _} -> bs != 0 end) |> 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 -> - # SINGLE VALUED - Write.ubyte(0) <> - 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} -> {i, v} end) - paletted_blocks = blocks |> - Enum.map(&(Map.get(palette, &1))) - paletted_data = long_aligned_bit_string_reduce(paletted_blocks, bpe) + # Put together the palette + unique_blocks = MapSet.new(blocks_and_lights |> Enum.map(fn {bs, _, _} -> bs end)) + min_bpe = MapSet.size(unique_blocks) |> :math.log2() |> ceil() - Write.ubyte(bpe) <> - Write.varint(map_size(palette)) <> - Enum.reduce(palette, "", fn {_k, v}, acc -> - acc <> Write.varint(v) - 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 + paletted_container_data = case min_bpe do + 0 -> + # SINGLE VALUED + Write.ubyte(0) <> + Write.varint(MapSet.to_list(unique_blocks) |> List.first()) <> + Write.varint(0) # No data, empty pallette - acc <> Write.short(block_count) <> paletted_container_data <> - <<0::8, 0::8, 0::8>> # TODO: This should be biome data - end) + 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 {v, i} -> {v, i} end) + paletted_blocks = blocks_and_lights |> + Enum.map(fn {bs, _, _} -> Map.get(palette, bs) end) + paletted_data = long_aligned_bit_string_reduce(paletted_blocks, bpe, + fn bs -> <> end) + Write.ubyte(bpe) <> + Write.varint(map_size(palette)) <> + (Enum.sort(palette, fn {_k1, v1}, {_k2, v2} -> v1 < v2 end) |> + Enum.reduce("", fn {k, _v}, acc -> acc <> Write.varint(k) end)) <> + Write.varint(floor(bit_size(paletted_data) / 64)) <> + paletted_data - 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: Write.varint(0), - block_light_mask: Write.varint(0), - empty_sky_light_mask: Write.varint(0), - empty_block_light_mask: Write.varint(0), - sky_light_arrays: [], - block_light_arrays: [] - }}) - :ok + _ -> + # DIRECT + data = long_aligned_bit_string_reduce(blocks_and_lights, 15, + fn {bs, _sl, _bl} -> <> end) + Write.ubyte(15) <> + Write.varint(floor(bit_size(data) / 64)) <> + data + end + + acc <> Write.short(block_count) <> paletted_container_data <> + <<0::8, 0::8, 0::8>> # TODO: This should be biome data + end) + + sky_light_sections = Enum.chunk_every(chunk_array, 16, 16, 0) + |> Enum.map(fn chunk_section -> + chunk_section |> List.flatten() |> Enum.map(fn {_bs, sl, _bl} -> sl end) + end) + non_empty_sky_light_sections = sky_light_sections |> Enum.filter(fn section -> !Enum.all?(section, &(&1 == 0)) end) + has_sky_light = [false] ++ (sky_light_sections |> Enum.map(fn section -> !Enum.all?(section, &(&1 == 0)) end)) ++ [false] + empty_sky_light = has_sky_light |> Enum.map(¬/1) + sky_light_array = non_empty_sky_light_sections |> Enum.map(&(Enum.reduce(&1, <<>>, fn light, acc -> + <> + end))) + + block_light_sections = Enum.chunk_every(chunk_array, 16, 16, 0) + |> Enum.map(fn chunk_section -> + chunk_section |> List.flatten() |> Enum.map(fn {_bs, _sl, bl} -> bl end) + end) + non_empty_block_light_sections = block_light_sections |> Enum.filter(fn section -> !Enum.all?(section, &(&1 == 0)) end) + has_block_light = [false] ++ (block_light_sections |> Enum.map(fn section -> !Enum.all?(section, &(&1 == 0)) end)) ++ [false] + empty_block_light = has_block_light |> Enum.map(¬/1) + block_light_array = non_empty_block_light_sections |> Enum.map(&(Enum.reduce(&1, <<>>, fn light, acc -> + <> + end))) + + send(to, {:send_packet, %{ + packet_type: :chunk_data_and_update_light, + chunk_x: cx, chunk_z: cz, + heightmaps: heightmaps, + data: data, + block_entities: [], + sky_light_mask: has_sky_light, + block_light_mask: has_block_light, + empty_sky_light_mask: empty_sky_light, + empty_block_light_mask: empty_block_light, + sky_light_arrays: sky_light_array |> Enum.map(&(%{sky_light_array: &1})), + block_light_arrays: block_light_array |> Enum.map(&(%{block_light_array: &1})) + }}) + :ok end - defp long_aligned_bit_string_reduce(values, bpe) do + defp long_aligned_bit_string_reduce(values, bpe, func) do values |> Enum.reduce(<<>>, fn value, acc -> - next = <> + ret = func.(value) + next = <> # man i hope they dont suddenly change the size of a long if rem(bit_size(next), 64) + bpe > 64 do # gotta pad it diff --git a/apps/amethyst/lib/data.ex b/apps/amethyst/lib/data.ex index 12fd126..49fdb31 100644 --- a/apps/amethyst/lib/data.ex +++ b/apps/amethyst/lib/data.ex @@ -16,6 +16,7 @@ defmodule Amethyst.Minecraft.Write do import Bitwise + require Logger @moduledoc """ This module contains functions for writing Minecraft data. @@ -139,6 +140,18 @@ defmodule Amethyst.Minecraft.Write do def nbt(value) do Amethyst.NBT.Write.write_net(value) end + + def bitset(list) do + Logger.debug("Writing bitset #{inspect(list)}") + unaligned = Enum.reduce(list, <<>>, &(if &1 do <<&2::bitstring, 1::1>> else <<&2::bitstring, 0::1>> end)) + aligned = if rem(bit_size(unaligned), 64) == 0 do + unaligned + else + <> + end + Logger.debug("Writing as #{inspect(aligned)}") + varint(div(byte_size(aligned), 8)) <> aligned + end end defmodule Amethyst.Minecraft.Read do diff --git a/apps/amethyst/lib/game.ex b/apps/amethyst/lib/game.ex index cebfd9c..08792ef 100644 --- a/apps/amethyst/lib/game.ex +++ b/apps/amethyst/lib/game.ex @@ -92,7 +92,7 @@ defmodule Amethyst.Game do 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()]]] + @callback chunk(from :: pid(), {x :: integer(), z :: integer()}, state_refs :: map()) :: [[[{block_state :: pos_integer(), sky_light :: 0..15, block_light :: 0..15}]]] def chunk(%{:mod => mod, :refs => refs}, pos) do mod.chunk(self(), pos, refs) end diff --git a/apps/amethyst/lib/states/macros.ex b/apps/amethyst/lib/states/macros.ex index 0f40a0d..d1644e4 100644 --- a/apps/amethyst/lib/states/macros.ex +++ b/apps/amethyst/lib/states/macros.ex @@ -154,6 +154,7 @@ defmodule Amethyst.ConnectionState.Macros do def type_matches(value, {:optional, _type}) when is_nil(value), do: true def type_matches(value, {:optional, type}), do: type_matches(value, type) def type_matches(value, {:array, signature}) when is_list(value), do: Enum.all?(value, fn item -> check_type(item, signature) end) + def type_matches(value, :bitset) when is_list(value), do: Enum.all?(value, fn item -> is_boolean(item) end) def type_matches(value, {:compound, signature}) when is_map(value), do: check_type(value, signature) def type_matches(_, _) do false diff --git a/apps/amethyst/lib/states/play.ex b/apps/amethyst/lib/states/play.ex index b1730e7..3c95619 100644 --- a/apps/amethyst/lib/states/play.ex +++ b/apps/amethyst/lib/states/play.ex @@ -36,10 +36,10 @@ defmodule Amethyst.ConnectionState.Play do type: :varint, data: :nbt ]}, - sky_light_mask: :raw, - block_light_mask: :raw, - empty_sky_light_mask: :raw, - empty_block_light_mask: :raw, + sky_light_mask: :bitset, + block_light_mask: :bitset, + empty_sky_light_mask: :bitset, + empty_block_light_mask: :bitset, sky_light_arrays: {:array, [ sky_light_array: :byte_array ]}, diff --git a/apps/example_game/lib/example/game.ex b/apps/example_game/lib/example/game.ex index 62dce35..ec8b67a 100644 --- a/apps/example_game/lib/example/game.ex +++ b/apps/example_game/lib/example/game.ex @@ -46,13 +46,13 @@ defmodule Example.Game do (0..255) |> Enum.map(fn y -> (0..15) |> Enum.map(fn z -> (0..15) |> Enum.map(fn x -> - gx = cx*16 + x - gz = cz*16 + z + gx = cx * 16 + x + gz = cz * 16 + z gy = y if rem(gx, 4) == 0 && rem(gy, 4) == 0 && rem(gz, 4) == 0 do - 1 + {abs(rem(div(gx + gy + gz, 4), 1000)), 15, 0} else - 0 + {0, 15, 0} end end) end)