Attempt implementing light
All checks were successful
Build & Test / nix-build (push) Successful in 1m49s

This commit is contained in:
Kodi Craft 2024-10-07 21:05:37 +02:00
parent 5743ae55e0
commit b44784e9ef
Signed by: kodi
GPG Key ID: 69D9EED60B242822
6 changed files with 104 additions and 65 deletions

View File

@ -153,69 +153,94 @@ defmodule Amethyst.ConnectionHandler do
heightmaps = compound(%{}) heightmaps = compound(%{})
data = Enum.chunk_every(chunk_array, 16, 16, 0) # 0 -> air data = Enum.chunk_every(chunk_array, 16, 16, 0) # 0 -> air
|> Enum.reduce("", fn chunk_section, acc -> |> Enum.reduce("", fn chunk_section, acc ->
blocks = chunk_section |> List.flatten() blocks_and_lights = chunk_section |> List.flatten()
block_count = blocks |> Enum.filter(&(&1 != 0)) |> length block_count = blocks_and_lights |> Enum.filter(fn {bs, _, _} -> bs != 0 end) |> length
# Put together the palette # Put together the palette
unique_blocks = MapSet.new(blocks) unique_blocks = MapSet.new(blocks_and_lights |> Enum.map(fn {bs, _, _} -> bs end))
min_bpe = MapSet.size(unique_blocks) |> :math.log2() |> ceil() 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)
Write.ubyte(bpe) <> paletted_container_data = case min_bpe do
Write.varint(map_size(palette)) <> 0 ->
Enum.reduce(palette, "", fn {_k, v}, acc -> # SINGLE VALUED
acc <> Write.varint(v) Write.ubyte(0) <>
end) <> Write.varint(MapSet.to_list(unique_blocks) |> List.first()) <>
Write.varint(floor(bit_size(paletted_data) / 64)) <> Write.varint(0) # No data, empty pallette
paletted_data
_ ->
# DIRECT
data = long_aligned_bit_string_reduce(blocks, 15)
Write.ubyte(15) <>
Write.varint(floor(bit_size(data) / 64)) <>
data
end
acc <> Write.short(block_count) <> paletted_container_data <> min_bpe when min_bpe in 1..8 ->
<<0::8, 0::8, 0::8>> # TODO: This should be biome data # INDIRECT
end) # 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 -> <<bs::signed-integer-big-size(bpe)>> 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, # DIRECT
chunk_x: cx, chunk_z: cz, data = long_aligned_bit_string_reduce(blocks_and_lights, 15,
heightmaps: heightmaps, fn {bs, _sl, _bl} -> <<bs::signed-integer-big-size(15)>> end)
data: data, Write.ubyte(15) <>
block_entities: [], Write.varint(floor(bit_size(data) / 64)) <>
# TODO: Light data
sky_light_mask: Write.varint(0), end
block_light_mask: Write.varint(0),
empty_sky_light_mask: Write.varint(0), acc <> Write.short(block_count) <> paletted_container_data <>
empty_block_light_mask: Write.varint(0), <<0::8, 0::8, 0::8>> # TODO: This should be biome data
sky_light_arrays: [], end)
block_light_arrays: []
}}) sky_light_sections = Enum.chunk_every(chunk_array, 16, 16, 0)
:ok |> 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(&not/1)
sky_light_array = non_empty_sky_light_sections |> Enum.map(&(Enum.reduce(&1, <<>>, fn light, acc ->
<<acc::bitstring, light::size(4)>>
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(&not/1)
block_light_array = non_empty_block_light_sections |> Enum.map(&(Enum.reduce(&1, <<>>, fn light, acc ->
<<acc::bitstring, light::size(4)>>
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 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 -> values |> Enum.reduce(<<>>, fn value, acc ->
next = <<acc::bitstring, value::big-size(bpe)>> ret = func.(value)
next = <<acc::bitstring, ret::bitstring-size(bpe)>>
# man i hope they dont suddenly change the size of a long # man i hope they dont suddenly change the size of a long
if rem(bit_size(next), 64) + bpe > 64 do if rem(bit_size(next), 64) + bpe > 64 do
# gotta pad it # gotta pad it

View File

@ -16,6 +16,7 @@
defmodule Amethyst.Minecraft.Write do defmodule Amethyst.Minecraft.Write do
import Bitwise import Bitwise
require Logger
@moduledoc """ @moduledoc """
This module contains functions for writing Minecraft data. This module contains functions for writing Minecraft data.
@ -139,6 +140,18 @@ defmodule Amethyst.Minecraft.Write do
def nbt(value) do def nbt(value) do
Amethyst.NBT.Write.write_net(value) Amethyst.NBT.Write.write_net(value)
end 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
<<unaligned::bitstring, 0::size(64 - rem(bit_size(unaligned), 64))>>
end
Logger.debug("Writing as #{inspect(aligned)}")
varint(div(byte_size(aligned), 8)) <> aligned
end
end end
defmodule Amethyst.Minecraft.Read do defmodule Amethyst.Minecraft.Read do

View File

@ -92,7 +92,7 @@ defmodule Amethyst.Game do
For now, this data must be formatted as a 3D list, indexed as [y][z][x]. 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 def chunk(%{:mod => mod, :refs => refs}, pos) do
mod.chunk(self(), pos, refs) mod.chunk(self(), pos, refs)
end end

View File

@ -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}) when is_nil(value), do: true
def type_matches(value, {:optional, type}), do: type_matches(value, type) 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, {: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(value, {:compound, signature}) when is_map(value), do: check_type(value, signature)
def type_matches(_, _) do def type_matches(_, _) do
false false

View File

@ -36,10 +36,10 @@ defmodule Amethyst.ConnectionState.Play do
type: :varint, type: :varint,
data: :nbt data: :nbt
]}, ]},
sky_light_mask: :raw, sky_light_mask: :bitset,
block_light_mask: :raw, block_light_mask: :bitset,
empty_sky_light_mask: :raw, empty_sky_light_mask: :bitset,
empty_block_light_mask: :raw, empty_block_light_mask: :bitset,
sky_light_arrays: {:array, [ sky_light_arrays: {:array, [
sky_light_array: :byte_array sky_light_array: :byte_array
]}, ]},

View File

@ -46,13 +46,13 @@ defmodule Example.Game do
(0..255) |> Enum.map(fn y -> (0..255) |> Enum.map(fn y ->
(0..15) |> Enum.map(fn z -> (0..15) |> Enum.map(fn z ->
(0..15) |> Enum.map(fn x -> (0..15) |> Enum.map(fn x ->
gx = cx*16 + x gx = cx * 16 + x
gz = cz*16 + z gz = cz * 16 + z
gy = y gy = y
if rem(gx, 4) == 0 && rem(gy, 4) == 0 && rem(gz, 4) == 0 do 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 else
0 {0, 15, 0}
end end
end) end)
end) end)