mirror of
https://github.com/neocturne/MinedMap.git
synced 2025-04-25 21:45:09 +02:00
Compare commits
445 commits
Author | SHA1 | Date | |
---|---|---|---|
118034dd27 | |||
ca880ab3b4 | |||
dd56e842b5 | |||
69b62576ea | |||
58f2ff53b9 | |||
42b302f493 | |||
5f84ec8ed2 | |||
ba6e4bae7f | |||
442009eb08 | |||
23b2f274be | |||
3008203080 | |||
d638c810f2 | |||
6e5b958912 | |||
974a0f37df | |||
54ea2b2f28 | |||
6a54f57c50 | |||
dca365f4e2 | |||
e600a9dabb | |||
0f52f6a2c1 | |||
d6cd0fc53b | |||
c49ad6e608 | |||
5ee826a11b | |||
775fcb2d1b | |||
7bba5bae55 | |||
5c85687554 | |||
708fb9645d | |||
53a0f24600 | |||
404ad74235 | |||
deb232ddf3 | |||
fbdd5ed457 | |||
b1c0f316cb | |||
7686996fd3 | |||
b8a5d1580b | |||
f8c8ca78ba | |||
40bc6cd2a9 | |||
5ee8e493d4 | |||
6e2f2697fc | |||
24c266fc78 | |||
0a08e8cc46 | |||
90f2c5fdd0 | |||
8cb1eee60b | |||
282f62fc30 | |||
7bc15f97de | |||
901489dbc3 | |||
dba3dd551e | |||
850b1a668b | |||
3b5ce82873 | |||
cb0aa235db | |||
d02ca9aea2 | |||
c10e9e4902 | |||
37126f69fc | |||
971afea727 | |||
f661f854a4 | |||
1d9be9a41c | |||
a25b3cdbd7 | |||
d96bb727f7 | |||
0dd36a409a | |||
a10151a4f3 | |||
d7fc95c950 | |||
561a1e6577 | |||
0bf1d46aad | |||
a2f0ad401d | |||
ec309dc15f | |||
6763e2b4ec | |||
c23b53a8c3 | |||
bb11b29e92 | |||
28a191a23a | |||
52572a9e81 | |||
b9cd94b235 | |||
f9954d1ce4 | |||
a67bdb3b67 | |||
0a3f6d7765 | |||
ff6e28d381 | |||
9375af8d54 | |||
7f329ac8e7 | |||
650cd23198 | |||
941cd16337 | |||
c1d2eaa17e | |||
00237101f2 | |||
8f408e78a0 | |||
7b98954c80 | |||
00216341ca | |||
83bd936f80 | |||
9b3b345318 | |||
11e25106cf | |||
491ec5cdca | |||
feaf90c96c | |||
4933d8e15f | |||
![]() |
2156bff59a | ||
7b44ee30d6 | |||
f9a03b332c | |||
f43e84eb2b | |||
269f08d356 | |||
f1bc18add1 | |||
6d6f3e5b49 | |||
414ad5a493 | |||
7f3e47fcb4 | |||
bc8219772f | |||
661da4698d | |||
e9abe6b502 | |||
4eb963f147 | |||
e74e7be686 | |||
66189d279c | |||
d435643cfc | |||
39b073f24b | |||
44e914599b | |||
bccd6d6cb4 | |||
![]() |
00eea45375 | ||
9fd3989a95 | |||
d6716a598b | |||
f186681b41 | |||
3d84e9c9e4 | |||
7009dd791d | |||
8a1a26c13c | |||
a99a734df8 | |||
3ceb7ae188 | |||
e18761a3e4 | |||
05d8faeb5c | |||
6299c871a9 | |||
03521684b9 | |||
643035eaed | |||
3d024c6cd8 | |||
08f84fa339 | |||
625f2a13a3 | |||
7daddd6bbc | |||
7b3ac8647e | |||
76df56c9ce | |||
31de0dc0bd | |||
ac0fd06b16 | |||
989428f78d | |||
43d7dc751e | |||
7834315dd3 | |||
dca9c394f2 | |||
87d4371922 | |||
a1dd77c8fd | |||
9fd5689ebb | |||
6da921cca3 | |||
fb361145eb | |||
9aacd9cc2b | |||
48a6e242ea | |||
abf87e75ee | |||
8814dcff89 | |||
1874d3082d | |||
d29c0df25d | |||
cde6a4b6e6 | |||
1143396068 | |||
0f308788ef | |||
7740ce0522 | |||
e36ae4601d | |||
825cf70e51 | |||
7297c03567 | |||
c44f6ab859 | |||
5d40d061a4 | |||
f0e0db63d3 | |||
638d5046c9 | |||
61d456846a | |||
f78dd795ca | |||
0988ebe095 | |||
f79edb462c | |||
e5d9c813ba | |||
7b12c4aea3 | |||
2e6a200c6c | |||
f08acd06e2 | |||
e8165aa47d | |||
1e41e1bc6a | |||
5273e4b366 | |||
3a796325ba | |||
7ea09a46f9 | |||
96c60b048e | |||
b48cde7ba8 | |||
e856800c3d | |||
5602d3d499 | |||
fbb99bb467 | |||
a37dd79a02 | |||
997d807dc7 | |||
e5b7838ac5 | |||
cb7f428974 | |||
a8e0af287b | |||
fdf44ebc80 | |||
c67f3b8c48 | |||
1812e5c6d6 | |||
1432df7c93 | |||
93c1ce9437 | |||
25f675bd3b | |||
a5ad057e0c | |||
fa15a4e6e5 | |||
4574c06f5d | |||
74a4fc93d0 | |||
b32bdb4d8e | |||
196e21718f | |||
ec4b1053c5 | |||
cc9d2c00b4 | |||
5f2dea6b4f | |||
a21feceeb6 | |||
412568603e | |||
2c1c8c17ef | |||
fba9b6cb55 | |||
506631a18f | |||
fd48f94f16 | |||
09374d755e | |||
284892cea6 | |||
46c04e632f | |||
38da1616d5 | |||
920547f64a | |||
30e5aee09e | |||
ceb71f035c | |||
1559486727 | |||
d9d7cda5ee | |||
8873ff74c4 | |||
c7145c5c4a | |||
7d37f6a5d0 | |||
a8eb2da95d | |||
e57ec81d96 | |||
fcd05b5e1c | |||
f898116209 | |||
92fb2a9ba5 | |||
63b9f085e1 | |||
a153889ce6 | |||
646534c755 | |||
7cc6fca7fe | |||
b5a5c72142 | |||
4ef0200804 | |||
007c5f72fc | |||
d39e35508f | |||
14e57d2085 | |||
b0ad1d8765 | |||
c010ee9194 | |||
8e369f33fd | |||
4f3b43e4a4 | |||
43dfa6c544 | |||
2544ee9e80 | |||
810a05ab36 | |||
1616f3b2e2 | |||
003b48951b | |||
42a800e08c | |||
0273e7bc08 | |||
5fc296fc44 | |||
228f31c568 | |||
248a641035 | |||
09399f5ae9 | |||
9ec1c03ce1 | |||
3cb65ec70e | |||
204d0d06d5 | |||
05a8056cbf | |||
ba86dc8c06 | |||
0842cb4ec2 | |||
0542f2ea11 | |||
2a92eefd09 | |||
427a992897 | |||
722fe00d77 | |||
6a82fcc9b4 | |||
155171803d | |||
b5980b82af | |||
dcc9e6e794 | |||
78fe1ec50e | |||
c1260a63b5 | |||
d5ac38ed9b | |||
4a824680a9 | |||
9c4161f688 | |||
21035a1f7f | |||
b80d9ee420 | |||
84bee6d6d9 | |||
521e799d4b | |||
7b46adf6e7 | |||
cd1a5e869d | |||
7d231b92ea | |||
ab3b273992 | |||
dc68e67a23 | |||
deb33814ee | |||
fb712cd2f5 | |||
c38f00e411 | |||
b650b096ef | |||
0a485343a0 | |||
45171aa26a | |||
8e848394cd | |||
dee00e7a02 | |||
35bbd167ba | |||
a30266f67f | |||
5a765c3862 | |||
e39f48d8fe | |||
80781c9c20 | |||
6077138292 | |||
4d6644f427 | |||
628a702fd7 | |||
e18d4cea82 | |||
cb791f48e9 | |||
c472b61ef7 | |||
d84e2ca49d | |||
c471f84573 | |||
99c4de0b07 | |||
757f6ff166 | |||
429b7888f6 | |||
f9fc9efe8d | |||
b63a18ad6f | |||
216aa6ceec | |||
86382772c3 | |||
b1f7f759f1 | |||
b53d34da3d | |||
e5c96ecb99 | |||
007ce010d4 | |||
dede21806c | |||
17a02dc74c | |||
587db0464c | |||
1abb260997 | |||
59e919e9ea | |||
42cb342749 | |||
31eb92864c | |||
0b392d7a3a | |||
0437ec70b6 | |||
2d5eec13c2 | |||
263fc6d6ce | |||
924ee01f91 | |||
b66bdf5ce1 | |||
25710bb1ed | |||
e801631561 | |||
2dd9283d95 | |||
1a5e8894fe | |||
61fb23b94b | |||
2e05998b48 | |||
d79377ad6b | |||
0d81dfa35b | |||
e912f60ba3 | |||
0d2c99dacf | |||
ddc515a9d7 | |||
b93e613152 | |||
aa2edc3d13 | |||
3ea93296d7 | |||
c6843cfb9c | |||
b8b0e0627f | |||
e117c76937 | |||
524b1b1913 | |||
e462e17ee2 | |||
f2b7808e84 | |||
e62e19796c | |||
bcec704d27 | |||
46802116d9 | |||
fbf212b55f | |||
116e7e5fb6 | |||
4fd316f3fc | |||
202364bfca | |||
51602d5fc1 | |||
482471492c | |||
b4eb0d39f9 | |||
66f8d155f5 | |||
47dc3795a3 | |||
ed422be451 | |||
56573640fd | |||
a4b726992a | |||
fa04e3e94a | |||
26555f1745 | |||
e6e1f55fe9 | |||
d5406851b4 | |||
89f35024b7 | |||
e0467de080 | |||
657d8af8a7 | |||
dda81546de | |||
51a0e178b1 | |||
2ccb282f6f | |||
ec09afcf15 | |||
1d4c7a86ff | |||
ed5fb9a6cf | |||
04bc3e5d53 | |||
5c82b80924 | |||
f2ab424590 | |||
89af4aaee2 | |||
4c2fd6c1a9 | |||
95e4e45974 | |||
483cdf48c8 | |||
cbbc6d8f35 | |||
0673c89bd8 | |||
73b13c1afb | |||
c5ca1d9821 | |||
a2ba7e4738 | |||
ea604b88f2 | |||
ca6afa0cf9 | |||
194715ad09 | |||
e02a1fc0c9 | |||
18cecb947e | |||
d77dcce778 | |||
447a9482fe | |||
04ab8c321f | |||
b68f04496c | |||
768ab13205 | |||
21fd3a42ca | |||
f47b38b2ca | |||
fd0c9fbf1b | |||
b2d849081d | |||
551056803d | |||
2d0f2fb865 | |||
f3a6711458 | |||
f48aa877d2 | |||
2530a557a9 | |||
decb5c67a6 | |||
3ef48783b7 | |||
789a4002c7 | |||
921a218d56 | |||
339e0b6a05 | |||
2a941e5283 | |||
db289bc077 | |||
102baa9197 | |||
9f8446885e | |||
96736bd7ed | |||
1049aad03d | |||
9146c06bec | |||
493608ebc8 | |||
5ee114ed0a | |||
c34f531546 | |||
65c39ea153 | |||
b918ff6106 | |||
3cee4a4247 | |||
8add7679a3 | |||
c130f3cdae | |||
2d782f25b1 | |||
b040a635ed | |||
357ef46731 | |||
a174d627cd | |||
718ecf5909 | |||
6379472282 | |||
8c34f74952 | |||
6186a0d916 | |||
92a9bb3bb3 | |||
1d126ba771 | |||
7c7e36f6be | |||
da8ac506d9 | |||
3cdafa7be9 | |||
daa188eb1d | |||
28b22ce423 | |||
48e03aa266 | |||
0d8b989c10 | |||
5e96be3fda | |||
5a364e2434 | |||
ddd78079dc | |||
40105aaccd | |||
4b83f6376f | |||
c7e04649ff | |||
4ce08a7d5d | |||
c43c185d9b | |||
6605483b4d | |||
6125b1b027 | |||
![]() |
600a20cde6 | ||
71fb865112 | |||
a6a1deaff5 | |||
![]() |
74bd6205b0 | ||
![]() |
7bba6d0863 | ||
fbef307c90 |
227 changed files with 21609 additions and 5631 deletions
4
.dockerignore
Normal file
4
.dockerignore
Normal file
|
@ -0,0 +1,4 @@
|
|||
*
|
||||
!/Cargo.*
|
||||
!/src
|
||||
!/crates
|
16
.github/toolchains/common-w64-mingw32.cmake
vendored
16
.github/toolchains/common-w64-mingw32.cmake
vendored
|
@ -1,16 +0,0 @@
|
|||
set(BUILD_STATIC ON)
|
||||
|
||||
set(CMAKE_SYSTEM_NAME Windows)
|
||||
set(CMAKE_SYSROOT /usr/${TARGET})
|
||||
|
||||
set(CMAKE_C_COMPILER ${TARGET}-gcc)
|
||||
set(CMAKE_CXX_COMPILER ${TARGET}-g++)
|
||||
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||
|
||||
set(ENV{PKG_CONFIG_LIBDIR} ${CMAKE_SYSROOT}/lib/pkgconfig:${CMAKE_SYSROOT}/share/pkgconfig)
|
||||
set(ENV{PKG_CONFIG_PATH} $ENV{PKG_CONFIG_LIBDIR})
|
||||
|
||||
set(CMAKE_BUILD_WITH_INSTALL_RPATH ON)
|
2
.github/toolchains/i686-w64-mingw32.cmake
vendored
2
.github/toolchains/i686-w64-mingw32.cmake
vendored
|
@ -1,2 +0,0 @@
|
|||
set(TARGET i686-w64-mingw32)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/common-w64-mingw32.cmake)
|
0
.github/toolchains/x86_64-linux-gnu.cmake
vendored
0
.github/toolchains/x86_64-linux-gnu.cmake
vendored
2
.github/toolchains/x86_64-w64-mingw32.cmake
vendored
2
.github/toolchains/x86_64-w64-mingw32.cmake
vendored
|
@ -1,2 +0,0 @@
|
|||
set(TARGET x86_64-w64-mingw32)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/common-w64-mingw32.cmake)
|
309
.github/workflows/MinedMap.yml
vendored
309
.github/workflows/MinedMap.yml
vendored
|
@ -1,117 +1,238 @@
|
|||
name: 'MinedMap'
|
||||
on: ['push', 'pull_request', 'workflow_dispatch']
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'main'
|
||||
workflow_dispatch: {}
|
||||
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
RUSTDOCFLAGS: -Dwarnings
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: 'Build MinedMap (${{ matrix.host }})'
|
||||
runs-on: 'ubuntu-20.04'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
host:
|
||||
- 'x86_64-linux-gnu'
|
||||
- 'x86_64-w64-mingw32'
|
||||
include:
|
||||
- host: 'x86_64-linux-gnu'
|
||||
label: 'x86_64-linux-gnu'
|
||||
packages: ['g++', 'libpng-dev']
|
||||
prefix: '/usr'
|
||||
build_libpng: false
|
||||
- host: 'x86_64-w64-mingw32'
|
||||
label: 'Win64'
|
||||
packages: ['g++-mingw-w64-x86-64', 'libz-mingw-w64-dev']
|
||||
prefix: '/usr/x86_64-w64-mingw32'
|
||||
build_libpng: true
|
||||
- host: 'i686-w64-mingw32'
|
||||
label: 'Win32'
|
||||
packages: ['g++-mingw-w64-i686', 'libz-mingw-w64-dev']
|
||||
prefix: '/usr/i686-w64-mingw32'
|
||||
build_libpng: true
|
||||
|
||||
env:
|
||||
CMAKE_TOOLCHAIN_FILE: '${{ github.workspace }}/.github/toolchains/${{ matrix.host }}.cmake'
|
||||
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@v2'
|
||||
|
||||
- name: 'Get version'
|
||||
id: 'tag'
|
||||
run: |
|
||||
set -o pipefail
|
||||
git fetch --prune --unshallow
|
||||
git describe --abbrev=7 --match='v*' | sed 's/^v/::set-output name=tag::/'
|
||||
|
||||
- name: 'Dependencies (apt)'
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
sudo apt-get -y install ninja-build ${{ join(matrix.packages, ' ') }}
|
||||
sudo apt-get -y clean
|
||||
|
||||
- name: 'Dependencies (libpng)'
|
||||
if: '${{ matrix.build_libpng }}'
|
||||
run: |
|
||||
pkgver=1.6.37
|
||||
pkghash=505e70834d35383537b6491e7ae8641f1a4bed1876dbfe361201fc80868d88ca
|
||||
|
||||
mkdir -p build/libpng
|
||||
cd build/libpng
|
||||
wget http://downloads.sourceforge.net/sourceforge/libpng/libpng-$pkgver.tar.xz
|
||||
echo "$pkghash libpng-$pkgver.tar.xz" | sha256sum -c -
|
||||
tar xf libpng-$pkgver.tar.xz
|
||||
cd libpng-$pkgver
|
||||
./configure \
|
||||
--prefix=${{ matrix.prefix }} \
|
||||
--host=${{ matrix.host }} \
|
||||
--disable-shared
|
||||
make -j$(nproc)
|
||||
sudo make install
|
||||
sudo rm ${{ matrix.prefix }}/lib/libpng*.la
|
||||
|
||||
- name: 'Build'
|
||||
uses: lukka/run-cmake@v10
|
||||
with:
|
||||
configurePreset: 'ninja'
|
||||
buildPreset: 'ninja-release'
|
||||
|
||||
- name: 'Install'
|
||||
run: |
|
||||
export DESTDIR="$(pwd)/build/install"
|
||||
ninja -C builds/ninja -f build-RelWithDebInfo.ninja install/strip
|
||||
|
||||
pkgdir='build/pkg/MinedMap-${{ steps.tag.outputs.tag }}-${{ matrix.label }}'
|
||||
mkdir -p "$pkgdir"
|
||||
cp "$DESTDIR/usr/local/bin"/* "$pkgdir"/
|
||||
|
||||
- name: 'Archive'
|
||||
uses: 'actions/upload-artifact@v2'
|
||||
with:
|
||||
name: 'MinedMap-${{ steps.tag.outputs.tag }}-${{ matrix.label }}'
|
||||
path: 'build/pkg'
|
||||
|
||||
viewer:
|
||||
name: 'Package viewer'
|
||||
runs-on: 'ubuntu-20.04'
|
||||
runs-on: 'ubuntu-latest'
|
||||
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@v2'
|
||||
uses: 'actions/checkout@v4'
|
||||
|
||||
- name: 'Get version'
|
||||
id: 'tag'
|
||||
run: |
|
||||
set -o pipefail
|
||||
git fetch --prune --unshallow
|
||||
git describe --abbrev=7 --match='v*' | sed 's/^v/::set-output name=tag::/'
|
||||
git fetch --prune --unshallow --tags -f
|
||||
echo "tag=$(git describe --abbrev=7 --match='v*' | sed 's/^v//')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: 'Install'
|
||||
run: |
|
||||
pkgdir='build/pkg/MinedMap-${{ steps.tag.outputs.tag }}-viewer'
|
||||
mkdir -p "$pkgdir"
|
||||
cp -r viewer/* "$pkgdir"/
|
||||
rm "$pkgdir"/Dockerfile
|
||||
|
||||
- name: 'Archive'
|
||||
uses: 'actions/upload-artifact@v2'
|
||||
uses: 'actions/upload-artifact@v4'
|
||||
with:
|
||||
name: 'MinedMap-${{ steps.tag.outputs.tag }}-viewer'
|
||||
path: 'build/pkg'
|
||||
|
||||
fmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: '1.86'
|
||||
components: rustfmt
|
||||
- run: cargo fmt --all -- --check
|
||||
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: '1.86'
|
||||
components: clippy
|
||||
- uses: swatinem/rust-cache@v2
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --workspace --tests --examples
|
||||
|
||||
docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: '1.86'
|
||||
components: rust-docs
|
||||
- uses: swatinem/rust-cache@v2
|
||||
- run: cargo doc --workspace --no-deps --document-private-items
|
||||
|
||||
test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: '1.86'
|
||||
- uses: swatinem/rust-cache@v2
|
||||
- run: cargo test --workspace
|
||||
- run: cargo test --workspace --no-default-features
|
||||
- run: cargo test --workspace --examples --bins
|
||||
- run: cargo test --workspace --no-default-features --examples --bins
|
||||
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: 'macos-13'
|
||||
target: 'aarch64-apple-darwin'
|
||||
- os: 'macos-13'
|
||||
target: 'x86_64-apple-darwin'
|
||||
- os: 'windows-2019'
|
||||
target: 'x86_64-pc-windows-msvc'
|
||||
ext: '.exe'
|
||||
- os: 'windows-2019'
|
||||
target: 'i686-pc-windows-msvc'
|
||||
ext: '.exe'
|
||||
- os: 'ubuntu-22.04'
|
||||
target: 'x86_64-unknown-linux-gnu'
|
||||
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@v4'
|
||||
|
||||
- name: 'Get version'
|
||||
id: 'tag'
|
||||
shell: 'bash'
|
||||
run: |
|
||||
set -o pipefail
|
||||
git fetch --prune --unshallow --tags -f
|
||||
echo "tag=$(git describe --abbrev=7 --match='v*' | sed 's/^v//')" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: '1.86'
|
||||
targets: '${{ matrix.target }}'
|
||||
|
||||
- uses: swatinem/rust-cache@v2
|
||||
with:
|
||||
key: '${{ matrix.target }}'
|
||||
|
||||
- name: 'Build'
|
||||
shell: 'bash'
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings -Cstrip=symbols
|
||||
run: |
|
||||
pkgdir='target/pkg/MinedMap-${{ steps.tag.outputs.tag }}-${{ matrix.target }}'
|
||||
mkdir -p "$pkgdir"
|
||||
cargo build --release --target=${{ matrix.target }}
|
||||
cp target/${{ matrix.target }}/release/minedmap${{ matrix.ext }} "$pkgdir"/
|
||||
|
||||
- name: 'Archive'
|
||||
uses: 'actions/upload-artifact@v4'
|
||||
with:
|
||||
name: 'MinedMap-${{ steps.tag.outputs.tag }}-${{ matrix.target }}'
|
||||
path: 'target/pkg'
|
||||
|
||||
build-container:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- test
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@v4'
|
||||
|
||||
- name: 'Get version'
|
||||
id: 'tag'
|
||||
run: |
|
||||
set -o pipefail
|
||||
git fetch --prune --unshallow --tags -f
|
||||
echo "tag=$(git describe --abbrev=7 --match='v*' | sed 's/^v//')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/neocturne/minedmap/minedmap
|
||||
tags: |
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
type=ref,event=branch
|
||||
type=ref,event=branch,suffix=-{{sha}}
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
|
||||
- name: Login to GHCR
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
build-args: |
|
||||
MINEDMAP_VERSION=${{ steps.tag.outputs.tag }}
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
viewer-container:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- test
|
||||
steps:
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/neocturne/minedmap/viewer
|
||||
tags: |
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
type=ref,event=branch
|
||||
type=ref,event=branch,suffix=-{{sha}}
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
|
||||
- name: Login to GHCR
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: "{{defaultContext}}:viewer"
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -1,4 +1,5 @@
|
|||
*~
|
||||
/viewer/data
|
||||
.idea/
|
||||
cmake-build-debug/
|
||||
/resource/data
|
||||
/resource/colors.json
|
||||
/target
|
||||
|
|
207
CHANGELOG.md
Normal file
207
CHANGELOG.md
Normal file
|
@ -0,0 +1,207 @@
|
|||
<!-- next-header -->
|
||||
|
||||
## [Unreleased] - ReleaseDate
|
||||
|
||||
### Added
|
||||
|
||||
- Added support for Minecraft 1.21.5
|
||||
|
||||
Added new block types and handling for changed sign text storage format.
|
||||
|
||||
## [2.5.0] - 2025-03-16
|
||||
|
||||
### Added
|
||||
|
||||
- Added experimental watch mode
|
||||
|
||||
Passing `--watch` will cause MinedMap to run continuously instead of exiting
|
||||
after map generation, regenerating tiles whenever they change.
|
||||
|
||||
`--watch-delay` can be used to configure the delay between detecting a change
|
||||
and runing the map generation, also limiting how often the regeneration
|
||||
happens. This defaults to `30s`; significantly smaller values probably don't
|
||||
make sense because Minecraft writes out changes in batches anyways.
|
||||
|
||||
Finally, `--jobs-initial` can be used to configure the number of parallel
|
||||
generation threads for the initial cycle separately from the value used for
|
||||
subsequent cycles after a change is detected (`-j`/`--jobs`). Subsequent
|
||||
cycles usually need to regenerate only a small number of tiles, so setting
|
||||
`--jobs` to a smaller value than `--jobs-initial` may be advantageous.
|
||||
|
||||
- Added jemalloc support to fix performace on musl targets
|
||||
|
||||
The global allocator can be switched to jemalloc by enabling the `jemalloc`
|
||||
cargo feature now. This is not the default because it is not always faster
|
||||
than the default system allocator; in particular, the glibc allocator has
|
||||
slightly better performance in multithreaded mode. In addition, jemalloc
|
||||
uses a bit more memory.
|
||||
|
||||
In addition, the `jemalloc-auto` feature has been introduced, which is enabled
|
||||
by default and sets the global allocator to jemalloc on platforms where it is
|
||||
clearly advantageous. For now, this is only done on musl-based targets, as
|
||||
musl's default allocator is very slow in multithreaded operation (which was
|
||||
making higher thread counts like `-j8` basically useless due to 7-8x
|
||||
slowdowns). With the new default, performance on musl is basically identical
|
||||
to glibc.
|
||||
|
||||
Note that some platforms like `msvc` are unsupported by jemalloc, and trying
|
||||
to enable the `jemalloc` feature on these platforms may break the MinedMap
|
||||
build or cause issues at runtime.
|
||||
- Docker images can be downloaded from the GitHub Container registry
|
||||
|
||||
Two images are provided, one for the tile renderer and one with the viewer
|
||||
and a web server. A `docker-compose.yml` example can be found in the
|
||||
repository as a starting point.
|
||||
|
||||
### Changed
|
||||
|
||||
- Unknown biome types (from not yet supported or modded versions of Minecraft)
|
||||
will now use plains biome colors as a fallback instead of resulting in water,
|
||||
grass and foliage blocks to be rendered as transparent pixels
|
||||
- Switched from zlib-ng to zlib-rs
|
||||
|
||||
This should have no noticable effect on the usage of MinedMap, but avoids
|
||||
an external build dependency on CMake.
|
||||
- Small (1-block) seagrass is now visible on the map
|
||||
|
||||
1-block seagrass in 1-block deep water would previously result in the ground
|
||||
to be shown instead of water, as MinedMap currently doesn't handle the
|
||||
"waterlogged" block status. As 1-block seagrass is relatively big compared to
|
||||
other "small" plants, just considering it opaque seems like a good enough
|
||||
solution that avoids having to implement advanced block status flags.
|
||||
- Use Bincode 2 for storage of intermediate data
|
||||
|
||||
The update from Bincode 1 to 2 slightly reduces the size of the `processed`
|
||||
directory used for intermediate data. At least Rust 1.85 is now required to
|
||||
build MinedMap.
|
||||
|
||||
## [2.4.0] - 2025-01-11
|
||||
|
||||
### Added
|
||||
|
||||
- Added support for rendering tiles in WebP format using the `--image-format` option
|
||||
|
||||
## [2.3.1] - 2025-01-06
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix text colors for signs modified using dye
|
||||
- Fix text colors specified using `#rrggbb` CSS syntax in JSON text
|
||||
|
||||
Only named colors specified via JSON text were working as intended.
|
||||
|
||||
The mapping of color names to values is now handled by the generator. Both the generator and the
|
||||
viewer must be updated for sign text colors to work.
|
||||
|
||||
## [2.3.0] - 2025-01-02
|
||||
|
||||
### Added
|
||||
|
||||
- Added support for Minecraft 1.21.4 block types
|
||||
- Added support for Minecraft 1.21.4 Pale Garden biome
|
||||
- viewer: added images for pale oak signs
|
||||
|
||||
## [2.2.0] - 2024-06-23
|
||||
|
||||
### Added
|
||||
|
||||
- Added support for Minecraft 1.21 block types
|
||||
|
||||
## [2.1.1] - 2024-06-14
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix crash due to incorrect counting in info message
|
||||
|
||||
The calculation of the number of skipped regions could underflow when more invalid than valid
|
||||
regions were encountered.
|
||||
- Ignore empty region files instead of treating them as invalid
|
||||
|
||||
Minecraft generates empty region files in some cases. Just ignore them instead of printing an
|
||||
error message every time.
|
||||
|
||||
## [2.1.0] - 2024-01-27
|
||||
|
||||
### Added
|
||||
|
||||
- Added sign layer
|
||||
|
||||
This feature is disabled by default. Use the `--sign-prefix` and `--sign-filter` options to
|
||||
configure which signs to show on the map. `--sign-transform` allows to modify the displayed
|
||||
sign text.
|
||||
|
||||
### Changed
|
||||
|
||||
- Without `--verbose`, only a single warning is printed at the end of
|
||||
processing for unknown block/biome types, rather than once for every
|
||||
section where such a block/biome is encountered.
|
||||
|
||||
## [2.0.2] - 2024-01-07
|
||||
|
||||
### Added
|
||||
|
||||
- Added support for Minecraft 1.20.3+
|
||||
|
||||
Minecraft 1.20.3 renamed the `grass` block type to `short_grass`.
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated [Leaflet](https://leafletjs.com/) to 1.9.4
|
||||
- Updated attribution URL to https://github.com/neocturne/MinedMap
|
||||
|
||||
## [2.0.1] - 2023-11-18
|
||||
|
||||
### Fixed
|
||||
|
||||
- Proceed with missing tiles rather can failing completely when an invalid
|
||||
region file is encountered and no processed data from a previous run exists
|
||||
|
||||
## [2.0.0] - 2023-09-30
|
||||
|
||||
This is a complete rewrite of the map renderer in Rust, as the previous C++
|
||||
implementation was getting more and more difficult to maintain and keep current
|
||||
versions of Minecraft supported.
|
||||
|
||||
The new implementation is generally faster than the old one (by using better
|
||||
data structures), but it also uses a bit more RAM and storage space for
|
||||
intermediate data.
|
||||
|
||||
### Added
|
||||
|
||||
- Added support for Minecraft 1.20 biomes and block types
|
||||
- Multithreading: Pass `-j N` to minedmap to use *N* CPU cores in parallel. Note
|
||||
that this also multiplies the RAM requirements of MinedMap.
|
||||
- Extended OS support: MinedMap should now run on every system supported by Rust
|
||||
as a target. As I don't have a way to test these builds, binary releases are
|
||||
still limited to Windows and Linux for now; on other targets, MinedMap must
|
||||
be built from source.
|
||||
|
||||
### Changed
|
||||
|
||||
- Biome smoothing uses a different filter kernel now, which might result in
|
||||
nicer gradients?
|
||||
- Log messages have been reduced. Pass `-v` to get a message for each
|
||||
processed file again.
|
||||
- The intermediate data directory `biome` in the output directory has been
|
||||
replaced with a new `processed` directory. The `biome` directory can be
|
||||
deleted when reusing the output directory of an older MinedMap version.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Warnings about unknown biomes or block types have been reduced to once per
|
||||
chunk/section, so rending is not slowed down by these message so much anymore.
|
||||
|
||||
Full support for custom biomes datapacks might be added in a future release.
|
||||
|
||||
<!-- next-url -->
|
||||
[Unreleased]: https://github.com/neocturne/MinedMap/compare/v2.5.0...HEAD
|
||||
[2.5.0]: https://github.com/neocturne/MinedMap/compare/v2.4.0...v2.5.0
|
||||
[2.4.0]: https://github.com/neocturne/MinedMap/compare/v2.3.1...v2.4.0
|
||||
[2.3.1]: https://github.com/neocturne/MinedMap/compare/v2.3.0...v2.3.1
|
||||
[2.3.0]: https://github.com/neocturne/MinedMap/compare/v2.2.0...v2.3.0
|
||||
[2.2.0]: https://github.com/neocturne/MinedMap/compare/v2.1.1...v2.2.0
|
||||
[2.1.1]: https://github.com/neocturne/MinedMap/compare/v2.1.0...v2.1.1
|
||||
[2.1.0]: https://github.com/neocturne/MinedMap/compare/v2.0.2...v2.1.0
|
||||
[2.0.2]: https://github.com/neocturne/MinedMap/compare/v2.0.1...v2.0.2
|
||||
[2.0.1]: https://github.com/neocturne/MinedMap/compare/v2.0.0...v2.0.1
|
||||
[2.0.0]: https://github.com/neocturne/MinedMap/compare/v1.19.1...v2.0.0
|
|
@ -1,17 +0,0 @@
|
|||
cmake_minimum_required(VERSION 3.13)
|
||||
project(MINEDMAP CXX)
|
||||
|
||||
# May not work with all toolchains, added for the Windows CI build
|
||||
option(BUILD_STATIC "Create a statically linked MinedMap executable")
|
||||
|
||||
if(BUILD_STATIC)
|
||||
list(REMOVE_ITEM CMAKE_FIND_LIBRARY_SUFFIXES ".so" ".dll.a")
|
||||
add_link_options("-static")
|
||||
endif()
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
|
||||
pkg_check_modules(ZLIB REQUIRED IMPORTED_TARGET zlib)
|
||||
pkg_check_modules(PNG REQUIRED IMPORTED_TARGET libpng16)
|
||||
|
||||
add_subdirectory(src)
|
|
@ -1,39 +0,0 @@
|
|||
{
|
||||
"version": 3,
|
||||
"cmakeMinimumRequired": {
|
||||
"major": 3,
|
||||
"minor": 7,
|
||||
"patch": 0
|
||||
},
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "ninja",
|
||||
"displayName": "Ninja",
|
||||
"description": "Generate Ninja project files",
|
||||
"binaryDir": "${sourceDir}/builds/${presetName}",
|
||||
"generator": "Ninja Multi-Config",
|
||||
"cacheVariables": {
|
||||
"CMAKE_TOOLCHAIN_FILE": {
|
||||
"type": "FILEPATH",
|
||||
"value": "$env{CMAKE_TOOLCHAIN_FILE}"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
{
|
||||
"name": "ninja-debug",
|
||||
"configurePreset": "ninja",
|
||||
"displayName": "Build ninja-debug",
|
||||
"description": "Build ninja Debug configuration",
|
||||
"configuration": "Debug"
|
||||
},
|
||||
{
|
||||
"name": "ninja-release",
|
||||
"configurePreset": "ninja",
|
||||
"displayName": "Build ninja-release",
|
||||
"description": "Build ninja Release configuration",
|
||||
"configuration": "RelWithDebInfo"
|
||||
}
|
||||
]
|
||||
}
|
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal file
|
@ -0,0 +1,128 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
mschiffer@universe-factory.net.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
1510
Cargo.lock
generated
Normal file
1510
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
73
Cargo.toml
Normal file
73
Cargo.toml
Normal file
|
@ -0,0 +1,73 @@
|
|||
[workspace]
|
||||
members = ["crates/*"]
|
||||
|
||||
[workspace.package]
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/neocturne/MinedMap"
|
||||
|
||||
[workspace.metadata.release]
|
||||
consolidate-commits = false
|
||||
pre-release-commit-message = "{{crate_name}} {{version}}"
|
||||
|
||||
[package]
|
||||
name = "minedmap"
|
||||
version = "2.5.0"
|
||||
description = "Generate browsable maps from Minecraft save data"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
readme.workspace = true
|
||||
repository.workspace = true
|
||||
exclude = [
|
||||
"/.github/",
|
||||
"/docs/",
|
||||
"/viewer/",
|
||||
"/resource/",
|
||||
]
|
||||
|
||||
[package.metadata.release]
|
||||
tag-message = "{{crate_name}} {{version}}"
|
||||
pre-release-replacements = [
|
||||
{file="CHANGELOG.md", search="Unreleased", replace="{{version}}"},
|
||||
{file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1},
|
||||
{file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}"},
|
||||
{file="CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n\n## [Unreleased] - ReleaseDate", exactly=1},
|
||||
{file="CHANGELOG.md", search="<!-- next-url -->", replace="<!-- next-url -->\n[Unreleased]: https://github.com/neocturne/MinedMap/compare/{{tag_name}}...HEAD", exactly=1},
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.68"
|
||||
bincode = "2.0.1"
|
||||
clap = { version = "4.1.4", features = ["derive", "wrap_help"] }
|
||||
enum-map = "2.7.3"
|
||||
fastnbt = "2.3.2"
|
||||
flate2 = { version = "1.1.0", features = ["zlib-rs"] }
|
||||
futures-util = "0.3.28"
|
||||
git-version = "0.3.5"
|
||||
humantime = "2.1.0"
|
||||
image = { version = "0.25.1", default-features = false, features = ["png", "webp"] }
|
||||
indexmap = "2.0.0"
|
||||
lru = "0.13.0"
|
||||
minedmap-default-alloc = { version = "0.1.0", path = "crates/default-alloc", optional = true }
|
||||
minedmap-nbt = { version = "0.2.0", path = "crates/nbt", default-features = false }
|
||||
minedmap-resource = { version = "0.7.0", path = "crates/resource" }
|
||||
minedmap-types = { version = "0.2.0", path = "crates/types" }
|
||||
notify = "8.0.0"
|
||||
num-integer = "0.1.45"
|
||||
num_cpus = "1.16.0"
|
||||
phf = { version = "0.11.2", features = ["macros"] }
|
||||
rayon = "1.7.0"
|
||||
regex = "1.10.2"
|
||||
rustc-hash = "2.0.0"
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = "1.0.99"
|
||||
tokio = { version = "1.31.0", features = ["rt", "parking_lot", "sync"] }
|
||||
tracing = "0.1.37"
|
||||
tracing-subscriber = "0.3.17"
|
||||
zstd = "0.13.0"
|
||||
|
||||
[features]
|
||||
default = ["jemalloc-auto"]
|
||||
jemalloc-auto = ["dep:minedmap-default-alloc"]
|
||||
jemalloc = ["jemalloc-auto", "minedmap-default-alloc/jemalloc"]
|
17
Dockerfile
Normal file
17
Dockerfile
Normal file
|
@ -0,0 +1,17 @@
|
|||
FROM docker.io/library/rust:1.85.1-alpine AS builder
|
||||
|
||||
WORKDIR /build
|
||||
RUN apk add --no-cache build-base tini-static
|
||||
|
||||
ARG MINEDMAP_VERSION
|
||||
|
||||
COPY . .
|
||||
RUN cargo build -r
|
||||
RUN strip target/release/minedmap
|
||||
|
||||
FROM scratch
|
||||
|
||||
COPY --from=builder /sbin/tini-static /build/target/release/minedmap /bin/
|
||||
ENTRYPOINT [ "/bin/tini-static", "--", "/bin/minedmap" ]
|
||||
|
||||
USER 1000:1000
|
37
LICENSE
37
LICENSE
|
@ -1,22 +1,21 @@
|
|||
Copyright (c) 2015-2021, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
MIT License
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
Copyright (c) 2015 Matthias Schiffer
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
119
README.md
119
README.md
|
@ -2,33 +2,41 @@
|
|||
|
||||
* Render beautiful maps of your [Minecraft](https://minecraft.net/) worlds!
|
||||
* Put them on a webserver and view them in your browser!
|
||||
* Compatible with unmodified Minecraft Java Edition 1.8 up to 1.18 (no mod installation necessary!)
|
||||
* Compatible with unmodified Minecraft Java Edition 1.8 up to 1.21.4 (no mod installation required!)
|
||||
* Illumination layer: the world at night
|
||||
* Fast: create a full map for a huge 3GB savegame in less than 5 minutes
|
||||
* Fast: create a full map for a huge 3GB savegame in less than 5 minutes in single-threaded operation
|
||||
* Multi-threading support: pass `-j N` to the renderer to use `N` parallel threads for generation
|
||||
* Incremental updates: only recreate map tiles for regions that have changed
|
||||
* Very low memory usage: typically uses less than 5MB of RAM
|
||||
* Typically uses less than 100MB of RAM in single-threaded operation (may be higher when `-j` is passed)
|
||||
* Cross-platform: runs on Linux, Windows, and likely other systems like MacOS as well
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
## How to use
|
||||
## About
|
||||
|
||||
MinedMap consists of two components: a map renderer generating map tiles from
|
||||
Minecraft save games, and a viewer for displaying and navigating maps in a browser
|
||||
based on [Leaflet](https://leafletjs.com/). The map renderer is heavily inspired by
|
||||
[MapRend](https://github.com/YSelfTool/MapRend), but it has been implemented in C++
|
||||
from scratch for highest performance.
|
||||
[MapRend](https://github.com/YSelfTool/MapRend), but has been reimplemented from scratch
|
||||
(first in C++, now in Rust) for highest performance.
|
||||
|
||||
The viewer expects the the map data in a directory named `data`. To generate a new
|
||||
map, create this empty directory inside the viewer directory. Next, to generate the
|
||||
map files run MinedMap passing the source and the destination paths on the command
|
||||
line:
|
||||
## How to use
|
||||
|
||||
Download the binary release that matches your platform from the Github release
|
||||
page (or install from source using `cargo`), as well as the platform-independent
|
||||
viewer archive. Extract the viewer archive. The extracted directory contains the
|
||||
HTML and JavaScript to operate the viewer and will be made publicly accessible
|
||||
on a web server. The image data generated by MinedMap will be stored in the
|
||||
`data` subdirectory of the extracted viewer.
|
||||
|
||||
Minecraft stores its save data in a directory `~/.minecraft/saves` on Linux,
|
||||
and `C:\Users\<username>\AppData\Roaming\.minecraft\saves`. To generate MinedMap
|
||||
tile data from a save game called "World", use the a command like the following
|
||||
(replacing the first argument with the path to your save data; `<viewer>` refers
|
||||
to the directory where you unpacked the MinedMap viewer):
|
||||
```shell
|
||||
./MinedMap /path/to/save/game /path/to/viewer/data
|
||||
minedmap ~/.minecraft/saves/World <viewer>/data
|
||||
```
|
||||
The save game is stored in `saves` inside your Minecraft main directory
|
||||
(`~/.minecraft` on Linux, `C:\Users\<username>\AppData\Roaming\.minecraft` on Windows)
|
||||
in a subdirectory with the name of your world.
|
||||
|
||||
The first map generation might take a while for big worlds, but subsequent calls will
|
||||
only rebuild tiles for region files that have changed, rarely taking more than a second
|
||||
|
@ -37,7 +45,8 @@ MinedMap as a Cron job every minute.
|
|||
|
||||
Note that it is not possible to open the viewer *index.html* without a webserver, as
|
||||
it cannot load the generated map information from `file://` URIs. For testing purposes,
|
||||
you can use a minimal HTTP server, e.g. (if you have Python installed):
|
||||
you can use a minimal HTTP server, e.g. if you have Python installed just run the
|
||||
following in the viewer directory:
|
||||
```shell
|
||||
python3 -m http.server
|
||||
```
|
||||
|
@ -45,27 +54,69 @@ This test server is very slow and cannot handle multiple requests concurrently,
|
|||
a proper webserver like [nginx](https://nginx.org/) or upload the viewer together with
|
||||
the generated map files to public webspace to make the map available to others.
|
||||
|
||||
If you are uploading the directory to a remote webserver, you do not need to upload the
|
||||
`<viewer>/data/processed` directory, as it is only used locally to allow processing
|
||||
updates more quickly.
|
||||
|
||||
## Building MinedMap
|
||||
### Image formats
|
||||
|
||||
Precompiled MinedMap binaries for Windows (32bit and 64bit versions) are available under
|
||||
"Releases" on the Github page. On other platforms, MinedMap must be built from source.
|
||||
MinedMap renders map tiles as PNG by default. Pass `--image-format webp` to select
|
||||
WebP instead. For typical Minecraft worlds, using WebP reduces file sizes by 20-25%
|
||||
without increasing processing time.
|
||||
|
||||
MinedMap has been tested to work on Windows and Linux. I assume it can also be
|
||||
built for MacOS and pretty much any POSIX-like system, but I didn't check. ¯\\\_(ツ)\_/¯
|
||||
MinedMap always uses lossless compression for tile images, regardless of the
|
||||
image format.
|
||||
|
||||
To build from source, you need Git, CMake, the GCC toolchain and the development
|
||||
files for the libraries *zlib* and *libpng* (packages *git*, *cmake*, *build-essential*,
|
||||
*zlib1g-dev* and *libpng-dev* on Debian/Ubuntu).
|
||||
### Signs
|
||||
|
||||
Use the following commands to build:
|
||||

|
||||
|
||||
MinedMap can display sign markers on the map, which will open a popup showing
|
||||
the sign text when clicked.
|
||||
|
||||
Generation of the sign layer is disabled by default. It can be enabled by passing
|
||||
the `--sign-prefix` or `--sign-filter` options to MinedMap. The options allow
|
||||
to configure which signs should be displayed, and they can be passed multiple
|
||||
times to show every sign that matches at least one prefix or filter.
|
||||
|
||||
`--sign-prefix` will make all signs visible the text of which starts with the
|
||||
given prefix, so something like `--sign-prefix '[Map]'` would allow to put up
|
||||
signs that start with "\[Map\]" in Minecraft to add markers to the map. An
|
||||
empty prefix (`--sign-prefix ''`) can be used to make *all* signs visible on
|
||||
the map.
|
||||
|
||||
`--sign-filter` can be used for more advanced filters based on regular expressions.
|
||||
`--sign-filter '\[Map\]'` would show all signs that contain "\[Map\]"
|
||||
anywhere in their text, and `--sign-filter '.'` makes all non-empty signs (signs
|
||||
containing at least one character) visible. See the documentation of the
|
||||
[regex crate](https://docs.rs/regex) for more information on the supported syntax.
|
||||
|
||||
All prefixes and filters are applied to the front and back text separately, but
|
||||
both the front and the back text will be shown in the popup when one of them
|
||||
matches.
|
||||
|
||||
Finally, `--sign-transform` allows to specify sed-style replacement patterns to
|
||||
modify the text displayed on the map. This can be used if the text matched by
|
||||
`--sign-prefix` or `--sign-filter` should not be displayed:
|
||||
`--sign-transform 's/\[Map\]//'` would replace each occurence of "\[Map\]" with
|
||||
the empty string.
|
||||
|
||||
**Note:** On Windows, double quotes (`"`) must be used for arguments instead
|
||||
of single quotes (`'`), and all backslashes in the arguments must be escaped
|
||||
by doubling them. This can make regular expressions somewhat difficult to
|
||||
write and to read.
|
||||
|
||||
## Installation
|
||||
|
||||
Binary builds of the map generator for Linux and Windows, as well as an archive
|
||||
containing the viewer can be found on the GitHub release page.
|
||||
|
||||
Building the generator from source requires a recent Rust toolchain (1.72.0
|
||||
or newer). The following command can be used to build the current development version:
|
||||
```shell
|
||||
git clone https://github.com/NeoRaider/MinedMap.git # Or download a release ZIP and unpack it
|
||||
mkdir MinedMap-build
|
||||
cd MinedMap-build
|
||||
cmake ../MinedMap -DCMAKE_BUILD_TYPE=RELEASE
|
||||
make
|
||||
cargo install --git 'https://github.com/neocturne/MinedMap.git'
|
||||
```
|
||||
After a successful build, the MinedMap renderer binary can be found in the *src*
|
||||
subdirectory of the build dir `MinedMap-build`. The viewer is located in the cloned
|
||||
Git repository `MinedMap`.
|
||||
|
||||
If you are looking for the older C++ implementation of the MinedMap tile renderer,
|
||||
see the [v1.19.1](https://github.com/neocturne/MinedMap/tree/v1.19.1) tag.
|
||||
|
||||
|
|
6
TODO.md
Normal file
6
TODO.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
# TODO
|
||||
|
||||
## Optimizations
|
||||
|
||||
- To check:
|
||||
- Bulk `block_at()`, possibly other `top_layer()` improvements
|
17
crates/default-alloc/Cargo.toml
Normal file
17
crates/default-alloc/Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "minedmap-default-alloc"
|
||||
version = "0.1.0"
|
||||
description = "Helper crate for target-specific selection of global allocator default"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
readme.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
tikv-jemallocator = { version = "0.6.0", optional = true }
|
||||
|
||||
[target.'cfg(target_env = "musl")'.dependencies]
|
||||
tikv-jemallocator = "0.6.0"
|
||||
|
||||
[features]
|
||||
jemalloc = ["dep:tikv-jemallocator"]
|
3
crates/default-alloc/src/lib.rs
Normal file
3
crates/default-alloc/src/lib.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
#[cfg(any(target_env = "musl", feature = "jemalloc"))]
|
||||
#[global_allocator]
|
||||
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
|
20
crates/nbt/Cargo.toml
Normal file
20
crates/nbt/Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "minedmap-nbt"
|
||||
version = "0.2.0"
|
||||
description = "MinedMap's handling of Minecraft NBT data and region files"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
readme.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.75"
|
||||
bytemuck = "1.13.1"
|
||||
fastnbt = "2.4.4"
|
||||
flate2 = "1.1.0"
|
||||
minedmap-types = { version = "0.2.0", path = "../types" }
|
||||
serde = "1.0.183"
|
||||
|
||||
[dev-dependencies]
|
||||
clap = { version = "4.3.23", features = ["derive"] }
|
||||
flate2 = { version = "1.1.0", features = ["zlib-rs"] }
|
26
crates/nbt/examples/nbtdump.rs
Normal file
26
crates/nbt/examples/nbtdump.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
//! Dumps data from a NBT data file in a human-readable format
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![warn(clippy::missing_docs_in_private_items)]
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
|
||||
/// Dump a Minecraft NBT data file in a human-readable format
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(version)]
|
||||
struct Args {
|
||||
/// Filename to dump
|
||||
file: PathBuf,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
let value: fastnbt::Value = minedmap_nbt::data::from_file(args.file.as_path())?;
|
||||
println!("{:#x?}", value);
|
||||
|
||||
Ok(())
|
||||
}
|
28
crates/nbt/examples/regiondump.rs
Normal file
28
crates/nbt/examples/regiondump.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
//! Dumps data from a region data file in a human-readable format
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![warn(clippy::missing_docs_in_private_items)]
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
|
||||
/// Dump a Minecraft NBT region file in a human-readable format
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(version)]
|
||||
struct Args {
|
||||
/// Filename to dump
|
||||
file: PathBuf,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
minedmap_nbt::region::from_file(args.file.as_path())?.foreach_chunk(
|
||||
|coords, value: fastnbt::Value| {
|
||||
println!("Chunk {:?}: {:#x?}", coords, value);
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
}
|
32
crates/nbt/src/data.rs
Normal file
32
crates/nbt/src/data.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
//! Functions for reading and deserializing compressed NBT data
|
||||
|
||||
use std::{fs::File, io::prelude::*, path::Path};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use flate2::read::GzDecoder;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
/// Reads compressed NBT data from a reader and deserializes to a given data structure
|
||||
pub fn from_reader<R, T>(reader: R) -> Result<T>
|
||||
where
|
||||
R: Read,
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
let mut decoder = GzDecoder::new(reader);
|
||||
let mut buf = vec![];
|
||||
decoder
|
||||
.read_to_end(&mut buf)
|
||||
.context("Failed to read file")?;
|
||||
|
||||
fastnbt::from_bytes(&buf).context("Failed to decode NBT data")
|
||||
}
|
||||
|
||||
/// Reads compressed NBT data from a file and deserializes to a given data structure
|
||||
pub fn from_file<P, T>(path: P) -> Result<T>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
let file = File::open(path).context("Failed to open file")?;
|
||||
from_reader(file)
|
||||
}
|
6
crates/nbt/src/lib.rs
Normal file
6
crates/nbt/src/lib.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
#![doc = env!("CARGO_PKG_DESCRIPTION")]
|
||||
#![warn(missing_docs)]
|
||||
#![warn(clippy::missing_docs_in_private_items)]
|
||||
|
||||
pub mod data;
|
||||
pub mod region;
|
162
crates/nbt/src/region.rs
Normal file
162
crates/nbt/src/region.rs
Normal file
|
@ -0,0 +1,162 @@
|
|||
//! Functions for reading and deserializing region data
|
||||
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{SeekFrom, prelude::*},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use flate2::read::ZlibDecoder;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
use minedmap_types::*;
|
||||
|
||||
/// Data block size of region data files
|
||||
///
|
||||
/// After one header block, the region file consists of one or more consecutive blocks
|
||||
/// of data for each populated chunk.
|
||||
const BLOCKSIZE: usize = 4096;
|
||||
|
||||
/// Chunk descriptor extracted from region file header
|
||||
#[derive(Debug)]
|
||||
struct ChunkDesc {
|
||||
/// Offset of data block where the chunk starts
|
||||
offset: u32,
|
||||
/// Number of data block used by the chunk
|
||||
len: u8,
|
||||
/// Coodinates of chunk described by this descriptor
|
||||
coords: ChunkCoords,
|
||||
}
|
||||
|
||||
/// Parses the header of a region data file
|
||||
fn parse_header(header: &ChunkArray<u32>) -> Vec<ChunkDesc> {
|
||||
let mut chunks: Vec<_> = header
|
||||
.iter()
|
||||
.filter_map(|(coords, &chunk)| {
|
||||
let offset_len = u32::from_be(chunk);
|
||||
|
||||
let offset = offset_len >> 8;
|
||||
let len = offset_len as u8;
|
||||
|
||||
if offset == 0 || len == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(ChunkDesc {
|
||||
offset,
|
||||
len,
|
||||
coords,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
chunks.sort_by_key(|chunk| chunk.offset);
|
||||
|
||||
chunks
|
||||
}
|
||||
|
||||
/// Decompresses chunk data and deserializes to a given data structure
|
||||
fn decode_chunk<T>(buf: &[u8]) -> Result<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
let (format, buf) = buf.split_at(1);
|
||||
if format[0] != 2 {
|
||||
bail!("Unknown chunk format");
|
||||
}
|
||||
|
||||
let mut decoder = ZlibDecoder::new(buf);
|
||||
let mut decode_buffer = vec![];
|
||||
decoder
|
||||
.read_to_end(&mut decode_buffer)
|
||||
.context("Failed to decompress chunk data")?;
|
||||
|
||||
fastnbt::from_bytes(&decode_buffer).context("Failed to decode NBT data")
|
||||
}
|
||||
|
||||
/// Wraps a reader used to read a region data file
|
||||
#[derive(Debug)]
|
||||
pub struct Region<R: Read + Seek> {
|
||||
/// The wrapper reader
|
||||
reader: R,
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> Region<R> {
|
||||
/// Iterates over the chunks of the region data
|
||||
///
|
||||
/// The order of iteration is based on the order the chunks appear in the
|
||||
/// data file.
|
||||
pub fn foreach_chunk<T, F>(self, mut f: F) -> Result<()>
|
||||
where
|
||||
R: Read + Seek,
|
||||
T: DeserializeOwned,
|
||||
F: FnMut(ChunkCoords, T) -> Result<()>,
|
||||
{
|
||||
let Region { mut reader } = self;
|
||||
|
||||
let chunks = {
|
||||
let mut header = ChunkArray::<u32>::default();
|
||||
reader
|
||||
.read_exact(bytemuck::cast_mut::<_, [u8; BLOCKSIZE]>(&mut header.0))
|
||||
.context("Failed to read region header")?;
|
||||
|
||||
parse_header(&header)
|
||||
};
|
||||
|
||||
let mut seen = ChunkArray::<bool>::default();
|
||||
|
||||
for ChunkDesc {
|
||||
offset,
|
||||
len,
|
||||
coords,
|
||||
} in chunks
|
||||
{
|
||||
if seen[coords] {
|
||||
bail!("Duplicate chunk {:?}", coords);
|
||||
}
|
||||
seen[coords] = true;
|
||||
|
||||
reader
|
||||
.seek(SeekFrom::Start(offset as u64 * BLOCKSIZE as u64))
|
||||
.context("Failed to seek chunk data")?;
|
||||
|
||||
let mut len_buf = [0u8; 4];
|
||||
reader
|
||||
.read_exact(&mut len_buf)
|
||||
.with_context(|| format!("Failed to read length for chunk {:?}", coords))?;
|
||||
let byte_len = u32::from_be_bytes(len_buf) as usize;
|
||||
if byte_len < 1 || byte_len > (len as usize) * BLOCKSIZE - 4 {
|
||||
bail!("Invalid length for chunk {:?}", coords);
|
||||
}
|
||||
|
||||
let mut buffer = vec![0; byte_len];
|
||||
reader
|
||||
.read_exact(&mut buffer)
|
||||
.with_context(|| format!("Failed to read data for chunk {:?}", coords))?;
|
||||
let chunk = decode_chunk(&buffer)
|
||||
.with_context(|| format!("Failed to decode data for chunk {:?}", coords))?;
|
||||
|
||||
f(coords, chunk)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [Region] from a reader
|
||||
pub fn from_reader<R>(reader: R) -> Region<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
Region { reader }
|
||||
}
|
||||
|
||||
/// Creates a new [Region] for a file
|
||||
pub fn from_file<P>(path: P) -> Result<Region<File>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let file = File::open(path).context("Failed to open file")?;
|
||||
Ok(from_reader(file))
|
||||
}
|
13
crates/resource/Cargo.toml
Normal file
13
crates/resource/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "minedmap-resource"
|
||||
version = "0.7.0"
|
||||
description = "Data describing Minecraft biomes and block types"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
readme.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
bincode = "2.0.1"
|
||||
enumflags2 = "0.7.7"
|
||||
glam = "0.30.0"
|
117
crates/resource/src/biomes.rs
Normal file
117
crates/resource/src/biomes.rs
Normal file
|
@ -0,0 +1,117 @@
|
|||
//! Biome data
|
||||
//!
|
||||
//! This file is generated using resource/biomes.py, do not edit
|
||||
|
||||
use super::*;
|
||||
use BiomeGrassColorModifier::*;
|
||||
|
||||
/// List if known biomes and their properties
|
||||
pub const BIOMES: &[(&str, Biome)] = &[
|
||||
(
|
||||
"badlands",
|
||||
Biome::new(200, 0)
|
||||
.foliage([158, 129, 77])
|
||||
.grass([144, 129, 77]),
|
||||
),
|
||||
("bamboo_jungle", Biome::new(95, 90)),
|
||||
("basalt_deltas", Biome::new(200, 0)),
|
||||
("beach", Biome::new(80, 40)),
|
||||
("birch_forest", Biome::new(60, 60)),
|
||||
(
|
||||
"cherry_grove",
|
||||
Biome::new(50, 80)
|
||||
.foliage([182, 219, 97])
|
||||
.grass([182, 219, 97])
|
||||
.water([93, 183, 239]),
|
||||
),
|
||||
("cold_ocean", Biome::new(50, 50).water([61, 87, 214])),
|
||||
("crimson_forest", Biome::new(200, 0)),
|
||||
("dark_forest", Biome::new(70, 80).modify(DarkForest)),
|
||||
("deep_cold_ocean", Biome::new(50, 50).water([61, 87, 214])),
|
||||
("deep_dark", Biome::new(80, 40)),
|
||||
("deep_frozen_ocean", Biome::new(50, 50).water([57, 56, 201])),
|
||||
(
|
||||
"deep_lukewarm_ocean",
|
||||
Biome::new(50, 50).water([69, 173, 242]),
|
||||
),
|
||||
("deep_ocean", Biome::new(50, 50)),
|
||||
("desert", Biome::new(200, 0)),
|
||||
("dripstone_caves", Biome::new(80, 40)),
|
||||
("end_barrens", Biome::new(50, 50)),
|
||||
("end_highlands", Biome::new(50, 50)),
|
||||
("end_midlands", Biome::new(50, 50)),
|
||||
(
|
||||
"eroded_badlands",
|
||||
Biome::new(200, 0)
|
||||
.foliage([158, 129, 77])
|
||||
.grass([144, 129, 77]),
|
||||
),
|
||||
("flower_forest", Biome::new(70, 80)),
|
||||
("forest", Biome::new(70, 80)),
|
||||
("frozen_ocean", Biome::new(0, 50).water([57, 56, 201])),
|
||||
("frozen_peaks", Biome::new(-70, 90)),
|
||||
("frozen_river", Biome::new(0, 50).water([57, 56, 201])),
|
||||
("grove", Biome::new(-20, 80)),
|
||||
("ice_spikes", Biome::new(0, 50)),
|
||||
("jagged_peaks", Biome::new(-70, 90)),
|
||||
("jungle", Biome::new(95, 90)),
|
||||
("lukewarm_ocean", Biome::new(50, 50).water([69, 173, 242])),
|
||||
("lush_caves", Biome::new(50, 50)),
|
||||
(
|
||||
"mangrove_swamp",
|
||||
Biome::new(80, 90)
|
||||
.foliage([141, 177, 39])
|
||||
.modify(Swamp)
|
||||
.water([58, 122, 106]),
|
||||
),
|
||||
("meadow", Biome::new(50, 80).water([14, 78, 207])),
|
||||
("mushroom_fields", Biome::new(90, 100)),
|
||||
("nether_wastes", Biome::new(200, 0)),
|
||||
("ocean", Biome::new(50, 50)),
|
||||
("old_growth_birch_forest", Biome::new(60, 60)),
|
||||
("old_growth_pine_taiga", Biome::new(30, 80)),
|
||||
("old_growth_spruce_taiga", Biome::new(25, 80)),
|
||||
(
|
||||
"pale_garden",
|
||||
Biome::new(70, 80)
|
||||
.foliage([135, 141, 118])
|
||||
.grass([119, 130, 114])
|
||||
.water([118, 136, 157]),
|
||||
),
|
||||
("plains", Biome::new(80, 40)),
|
||||
("river", Biome::new(50, 50)),
|
||||
("savanna", Biome::new(200, 0)),
|
||||
("savanna_plateau", Biome::new(200, 0)),
|
||||
("small_end_islands", Biome::new(50, 50)),
|
||||
("snowy_beach", Biome::new(5, 30).water([61, 87, 214])),
|
||||
("snowy_plains", Biome::new(0, 50)),
|
||||
("snowy_slopes", Biome::new(-30, 90)),
|
||||
("snowy_taiga", Biome::new(-50, 40).water([61, 87, 214])),
|
||||
("soul_sand_valley", Biome::new(200, 0)),
|
||||
("sparse_jungle", Biome::new(95, 80)),
|
||||
("stony_peaks", Biome::new(100, 30)),
|
||||
("stony_shore", Biome::new(20, 30)),
|
||||
("sunflower_plains", Biome::new(80, 40)),
|
||||
(
|
||||
"swamp",
|
||||
Biome::new(80, 90)
|
||||
.foliage([106, 112, 57])
|
||||
.modify(Swamp)
|
||||
.water([97, 123, 100]),
|
||||
),
|
||||
("taiga", Biome::new(25, 80)),
|
||||
("the_end", Biome::new(50, 50)),
|
||||
("the_void", Biome::new(50, 50)),
|
||||
("warm_ocean", Biome::new(50, 50).water([67, 213, 238])),
|
||||
("warped_forest", Biome::new(200, 0)),
|
||||
("windswept_forest", Biome::new(20, 30)),
|
||||
("windswept_gravelly_hills", Biome::new(20, 30)),
|
||||
("windswept_hills", Biome::new(20, 30)),
|
||||
("windswept_savanna", Biome::new(200, 0)),
|
||||
(
|
||||
"wooded_badlands",
|
||||
Biome::new(200, 0)
|
||||
.foliage([158, 129, 77])
|
||||
.grass([144, 129, 77]),
|
||||
),
|
||||
];
|
129
crates/resource/src/block_color.rs
Normal file
129
crates/resource/src/block_color.rs
Normal file
|
@ -0,0 +1,129 @@
|
|||
//! Functions for computations of block colors
|
||||
|
||||
use super::{Biome, BlockColor, Color, Colorf};
|
||||
|
||||
/// Converts an u8 RGB color to a float vector
|
||||
fn color_vec_unscaled(color: Color) -> Colorf {
|
||||
Colorf::from_array(color.0.map(f32::from))
|
||||
}
|
||||
|
||||
/// Converts an u8 RGB color to a float vector, scaling the components to 0.0..1.0
|
||||
fn color_vec(color: Color) -> Colorf {
|
||||
color_vec_unscaled(color) / 255.0
|
||||
}
|
||||
|
||||
/// Helper for grass and foliage colors
|
||||
///
|
||||
/// Biome temperature and downfall are modified based on the depth value
|
||||
/// before using them to compute the final color
|
||||
fn color_from_params(colors: &[Colorf; 3], biome: &Biome, depth: f32) -> Colorf {
|
||||
let temp = (biome.temp() - f32::max((depth - 64.0) / 600.0, 0.0)).clamp(0.0, 1.0);
|
||||
let downfall = biome.downfall().clamp(0.0, 1.0) * temp;
|
||||
|
||||
colors[0] + temp * colors[1] + downfall * colors[2]
|
||||
}
|
||||
|
||||
/// Extension trait with helpers for computing biome-specific block colors
|
||||
trait BiomeExt {
|
||||
/// Returns the grass color of the biome at a given depth
|
||||
fn grass_color(&self, depth: f32) -> Colorf;
|
||||
/// Returns the foliage color of the biome at a given depth
|
||||
fn foliage_color(&self, depth: f32) -> Colorf;
|
||||
/// Returns the water color of the biome
|
||||
fn water_color(&self) -> Colorf;
|
||||
}
|
||||
|
||||
impl BiomeExt for Biome {
|
||||
fn grass_color(&self, depth: f32) -> Colorf {
|
||||
use super::BiomeGrassColorModifier::*;
|
||||
|
||||
/// Color matrix extracted from grass color texture
|
||||
const GRASS_COLORS: [Colorf; 3] = [
|
||||
Colorf::new(0.502, 0.706, 0.592), // lower right
|
||||
Colorf::new(0.247, 0.012, -0.259), // lower left - lower right
|
||||
Colorf::new(-0.471, 0.086, -0.133), // upper left - lower left
|
||||
];
|
||||
/// Used for dark forst grass color modifier
|
||||
const DARK_FOREST_GRASS_COLOR: Colorf = Colorf::new(0.157, 0.204, 0.039); // == color_vec(Color([40, 52, 10]))
|
||||
/// Grass color in swamp biomes
|
||||
const SWAMP_GRASS_COLOR: Colorf = Colorf::new(0.416, 0.439, 0.224); // == color_vec(Color([106, 112, 57]))
|
||||
|
||||
let regular_color = || {
|
||||
self.grass_color
|
||||
.map(color_vec)
|
||||
.unwrap_or_else(|| color_from_params(&GRASS_COLORS, self, depth))
|
||||
};
|
||||
|
||||
match self.grass_color_modifier {
|
||||
Some(DarkForest) => 0.5 * (regular_color() + DARK_FOREST_GRASS_COLOR),
|
||||
Some(Swamp) => SWAMP_GRASS_COLOR,
|
||||
None => regular_color(),
|
||||
}
|
||||
}
|
||||
|
||||
fn foliage_color(&self, depth: f32) -> Colorf {
|
||||
/// Color matrix extracted from foliage color texture
|
||||
const FOLIAGE_COLORS: [Colorf; 3] = [
|
||||
Colorf::new(0.376, 0.631, 0.482), // lower right
|
||||
Colorf::new(0.306, 0.012, -0.317), // lower left - lower right
|
||||
Colorf::new(-0.580, 0.106, -0.165), // upper left - lower left
|
||||
];
|
||||
|
||||
self.foliage_color
|
||||
.map(color_vec)
|
||||
.unwrap_or_else(|| color_from_params(&FOLIAGE_COLORS, self, depth))
|
||||
}
|
||||
|
||||
fn water_color(&self) -> Colorf {
|
||||
/// Default biome water color
|
||||
///
|
||||
/// Used for biomes that don't explicitly set a water color
|
||||
const DEFAULT_WATER_COLOR: Colorf = Colorf::new(0.247, 0.463, 0.894); // == color_vec(Color([63, 118, 228]))
|
||||
|
||||
self.water_color
|
||||
.map(color_vec)
|
||||
.unwrap_or(DEFAULT_WATER_COLOR)
|
||||
}
|
||||
}
|
||||
|
||||
/// Color multiplier for birch leaves
|
||||
const BIRCH_COLOR: Colorf = Colorf::new(0.502, 0.655, 0.333); // == color_vec(Color([128, 167, 85]))
|
||||
/// Color multiplier for spruce leaves
|
||||
const EVERGREEN_COLOR: Colorf = Colorf::new(0.380, 0.600, 0.380); // == color_vec(Color([97, 153, 97]))
|
||||
|
||||
/// Determined if calling [block_color] for a given [BlockColor] needs biome information
|
||||
pub fn needs_biome(block: BlockColor) -> bool {
|
||||
use super::BlockFlag::*;
|
||||
|
||||
block.is(Grass) || block.is(Foliage) || block.is(Water)
|
||||
}
|
||||
|
||||
/// Determined the block color to display for a given [BlockColor]
|
||||
///
|
||||
/// [needs_biome] must be used to determine whether passing a [Biome] is necessary.
|
||||
/// Will panic if a [Biome] is necessary, but none is passed.
|
||||
pub fn block_color(block: BlockColor, biome: Option<&Biome>, depth: f32) -> Colorf {
|
||||
use super::BlockFlag::*;
|
||||
|
||||
let get_biome = || biome.expect("needs biome to determine block color");
|
||||
|
||||
let mut color = color_vec_unscaled(block.color);
|
||||
|
||||
if block.is(Grass) {
|
||||
color *= get_biome().grass_color(depth);
|
||||
}
|
||||
if block.is(Foliage) {
|
||||
color *= get_biome().foliage_color(depth);
|
||||
}
|
||||
if block.is(Birch) {
|
||||
color *= BIRCH_COLOR;
|
||||
}
|
||||
if block.is(Spruce) {
|
||||
color *= EVERGREEN_COLOR;
|
||||
}
|
||||
if block.is(Water) {
|
||||
color *= get_biome().water_color();
|
||||
}
|
||||
|
||||
color * (0.5 + 0.005 * depth)
|
||||
}
|
11111
crates/resource/src/block_types.rs
Normal file
11111
crates/resource/src/block_types.rs
Normal file
File diff suppressed because it is too large
Load diff
202
crates/resource/src/legacy_biomes.rs
Normal file
202
crates/resource/src/legacy_biomes.rs
Normal file
|
@ -0,0 +1,202 @@
|
|||
//! Manually maintained biome data (aliases and legacy biome IDs)
|
||||
|
||||
/// Biome ID aliases
|
||||
///
|
||||
/// Some biomes have been renamed or merged in recent Minecraft versions.
|
||||
/// Maintain a list of aliases to support chunks saved by older versions.
|
||||
pub const BIOME_ALIASES: &[(&str, &str)] = &[
|
||||
// Biomes fix
|
||||
("beaches", "beach"),
|
||||
("cold_beach", "snowy_beach"),
|
||||
("cold_deep_ocean", "deep_cold_ocean"),
|
||||
("extreme_hills", "mountains"),
|
||||
("extreme_hills_with_trees", "wooded_mountains"),
|
||||
("forest_hills", "wooded_hills"),
|
||||
("frozen_deep_ocean", "deep_frozen_ocean"),
|
||||
("hell", "nether_wastes"),
|
||||
("ice_flats", "snowy_tundra"),
|
||||
("ice_mountains", "snowy_mountains"),
|
||||
("lukewarm_deep_ocean", "deep_lukewarm_ocean"),
|
||||
("mesa", "badlands"),
|
||||
("mesa_clear_rock", "badlands_plateau"),
|
||||
("mesa_rock", "wooded_badlands_plateau"),
|
||||
("mushroom_island", "mushroom_fields"),
|
||||
("mushroom_island_shore", "mushroom_field_shore"),
|
||||
("mutated_birch_forest", "tall_birch_forest"),
|
||||
("mutated_birch_forest_hills", "tall_birch_hills"),
|
||||
("mutated_desert", "desert_lakes"),
|
||||
("mutated_extreme_hills", "gravelly_mountains"),
|
||||
(
|
||||
"mutated_extreme_hills_with_trees",
|
||||
"modified_gravelly_mountains",
|
||||
),
|
||||
("mutated_forest", "flower_forest"),
|
||||
("mutated_ice_flats", "ice_spikes"),
|
||||
("mutated_jungle", "modified_jungle"),
|
||||
("mutated_jungle_edge", "modified_jungle_edge"),
|
||||
("mutated_mesa", "eroded_badlands"),
|
||||
("mutated_mesa_clear_rock", "modified_badlands_plateau"),
|
||||
("mutated_mesa_rock", "modified_wooded_badlands_plateau"),
|
||||
("mutated_plains", "sunflower_plains"),
|
||||
("mutated_redwood_taiga", "giant_spruce_taiga"),
|
||||
("mutated_redwood_taiga_hills", "giant_spruce_taiga_hills"),
|
||||
("mutated_roofed_forest", "dark_forest_hills"),
|
||||
("mutated_savanna", "shattered_savanna"),
|
||||
("mutated_savanna_rock", "shattered_savanna_plateau"),
|
||||
("mutated_swampland", "swamp_hills"),
|
||||
("mutated_taiga", "taiga_mountains"),
|
||||
("mutated_taiga_cold", "snowy_taiga_mountains"),
|
||||
("redwood_taiga", "giant_tree_taiga"),
|
||||
("redwood_taiga_hills", "giant_tree_taiga_hills"),
|
||||
("roofed_forest", "dark_forest"),
|
||||
("savanna_rock", "savanna_plateau"),
|
||||
("sky", "the_end"),
|
||||
("sky_island_barren", "end_barrens"),
|
||||
("sky_island_high", "end_highlands"),
|
||||
("sky_island_low", "small_end_islands"),
|
||||
("sky_island_medium", "end_midlands"),
|
||||
("smaller_extreme_hills", "mountain_edge"),
|
||||
("stone_beach", "stone_shore"),
|
||||
("swampland", "swamp"),
|
||||
("taiga_cold", "snowy_taiga"),
|
||||
("taiga_cold_hills", "snowy_taiga_hills"),
|
||||
("void", "the_void"),
|
||||
("warm_deep_ocean", "deep_warm_ocean"),
|
||||
// Nether biome rename
|
||||
("nether", "nether_wastes"),
|
||||
// Caves and Cliffs biome renames
|
||||
("badlands_plateau", "badlands"),
|
||||
("bamboo_jungle_hills", "bamboo_jungle"),
|
||||
("birch_forest_hills", "birch_forest"),
|
||||
("dark_forest_hills", "dark_forest"),
|
||||
("desert_hills", "desert"),
|
||||
("desert_lakes", "desert"),
|
||||
("giant_spruce_taiga", "old_growth_spruce_taiga"),
|
||||
("giant_spruce_taiga_hills", "old_growth_spruce_taiga"),
|
||||
("giant_tree_taiga", "old_growth_pine_taiga"),
|
||||
("giant_tree_taiga_hills", "old_growth_pine_taiga"),
|
||||
("gravelly_mountains", "windswept_gravelly_hills"),
|
||||
("jungle_edge", "sparse_jungle"),
|
||||
("jungle_hills", "jungle"),
|
||||
("lofty_peaks", "jagged_peaks"),
|
||||
("modified_badlands_plateau", "badlands"),
|
||||
("modified_gravelly_mountains", "windswept_gravelly_hills"),
|
||||
("modified_jungle", "jungle"),
|
||||
("modified_jungle_edge", "sparse_jungle"),
|
||||
("modified_wooded_badlands_plateau", "wooded_badlands"),
|
||||
("mountain_edge", "windswept_hills"),
|
||||
("mountains", "windswept_hills"),
|
||||
("mushroom_field_shore", "mushroom_fields"),
|
||||
("shattered_savanna", "windswept_savanna"),
|
||||
("shattered_savanna_plateau", "windswept_savanna"),
|
||||
("snowcapped_peaks", "frozen_peaks"),
|
||||
("snowy_mountains", "snowy_plains"),
|
||||
("snowy_taiga_hills", "snowy_taiga"),
|
||||
("snowy_taiga_mountains", "snowy_taiga"),
|
||||
("snowy_tundra", "snowy_plains"),
|
||||
("stone_shore", "stony_shore"),
|
||||
("swamp_hills", "swamp"),
|
||||
("taiga_hills", "taiga"),
|
||||
("taiga_mountains", "taiga"),
|
||||
("tall_birch_forest", "old_growth_birch_forest"),
|
||||
("tall_birch_hills", "old_growth_birch_forest"),
|
||||
("wooded_badlands_plateau", "wooded_badlands"),
|
||||
("wooded_hills", "forest"),
|
||||
("wooded_mountains", "windswept_forest"),
|
||||
// Remove Deep Warm Ocean
|
||||
("deep_warm_ocean", "warm_ocean"),
|
||||
];
|
||||
|
||||
/// Maps old numeric biome IDs to new string IDs
|
||||
pub fn legacy_biome(index: u8) -> &'static str {
|
||||
match index {
|
||||
0 => "ocean",
|
||||
1 => "plains",
|
||||
2 => "desert",
|
||||
3 => "mountains",
|
||||
4 => "forest",
|
||||
5 => "taiga",
|
||||
6 => "swamp",
|
||||
7 => "river",
|
||||
8 => "nether_wastes",
|
||||
9 => "the_end",
|
||||
10 => "frozen_ocean",
|
||||
11 => "frozen_river",
|
||||
12 => "snowy_tundra",
|
||||
13 => "snowy_mountains",
|
||||
14 => "mushroom_fields",
|
||||
15 => "mushroom_field_shore",
|
||||
16 => "beach",
|
||||
17 => "desert_hills",
|
||||
18 => "wooded_hills",
|
||||
19 => "taiga_hills",
|
||||
20 => "mountain_edge",
|
||||
21 => "jungle",
|
||||
22 => "jungle_hills",
|
||||
23 => "jungle_edge",
|
||||
24 => "deep_ocean",
|
||||
25 => "stone_shore",
|
||||
26 => "snowy_beach",
|
||||
27 => "birch_forest",
|
||||
28 => "birch_forest_hills",
|
||||
29 => "dark_forest",
|
||||
30 => "snowy_taiga",
|
||||
31 => "snowy_taiga_hills",
|
||||
32 => "giant_tree_taiga",
|
||||
33 => "giant_tree_taiga_hills",
|
||||
34 => "wooded_mountains",
|
||||
35 => "savanna",
|
||||
36 => "savanna_plateau",
|
||||
37 => "badlands",
|
||||
38 => "wooded_badlands_plateau",
|
||||
39 => "badlands_plateau",
|
||||
40 => "small_end_islands",
|
||||
41 => "end_midlands",
|
||||
42 => "end_highlands",
|
||||
43 => "end_barrens",
|
||||
44 => "warm_ocean",
|
||||
45 => "lukewarm_ocean",
|
||||
46 => "cold_ocean",
|
||||
47 => "deep_warm_ocean",
|
||||
48 => "deep_lukewarm_ocean",
|
||||
49 => "deep_cold_ocean",
|
||||
50 => "deep_frozen_ocean",
|
||||
127 => "the_void",
|
||||
129 => "sunflower_plains",
|
||||
130 => "desert_lakes",
|
||||
131 => "gravelly_mountains",
|
||||
132 => "flower_forest",
|
||||
133 => "taiga_mountains",
|
||||
134 => "swamp_hills",
|
||||
140 => "ice_spikes",
|
||||
149 => "modified_jungle",
|
||||
151 => "modified_jungle_edge",
|
||||
155 => "tall_birch_forest",
|
||||
156 => "tall_birch_hills",
|
||||
157 => "dark_forest_hills",
|
||||
158 => "snowy_taiga_mountains",
|
||||
160 => "giant_spruce_taiga",
|
||||
161 => "giant_spruce_taiga_hills",
|
||||
162 => "modified_gravelly_mountains",
|
||||
163 => "shattered_savanna",
|
||||
164 => "shattered_savanna_plateau",
|
||||
165 => "eroded_badlands",
|
||||
166 => "modified_wooded_badlands_plateau",
|
||||
167 => "modified_badlands_plateau",
|
||||
168 => "bamboo_jungle",
|
||||
169 => "bamboo_jungle_hills",
|
||||
170 => "soul_sand_valley",
|
||||
171 => "crimson_forest",
|
||||
172 => "warped_forest",
|
||||
173 => "basalt_deltas",
|
||||
174 => "dripstone_caves",
|
||||
175 => "lush_caves",
|
||||
177 => "meadow",
|
||||
178 => "grove",
|
||||
179 => "snowy_slopes",
|
||||
180 => "snowcapped_peaks",
|
||||
181 => "lofty_peaks",
|
||||
182 => "stony_peaks",
|
||||
_ => "ocean",
|
||||
}
|
||||
}
|
1027
crates/resource/src/legacy_block_types.rs
Normal file
1027
crates/resource/src/legacy_block_types.rs
Normal file
File diff suppressed because it is too large
Load diff
344
crates/resource/src/lib.rs
Normal file
344
crates/resource/src/lib.rs
Normal file
|
@ -0,0 +1,344 @@
|
|||
#![doc = env!("CARGO_PKG_DESCRIPTION")]
|
||||
#![warn(missing_docs)]
|
||||
#![warn(clippy::missing_docs_in_private_items)]
|
||||
|
||||
mod biomes;
|
||||
mod block_color;
|
||||
mod block_types;
|
||||
mod legacy_biomes;
|
||||
mod legacy_block_types;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use bincode::{BorrowDecode, Decode, Encode};
|
||||
use enumflags2::{BitFlags, bitflags};
|
||||
|
||||
/// Flags describing special properties of [BlockType]s
|
||||
#[bitflags]
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum BlockFlag {
|
||||
/// The block type is opaque
|
||||
Opaque,
|
||||
/// The block type is colored using biome grass colors
|
||||
Grass,
|
||||
/// The block type is colored using biome foliage colors
|
||||
Foliage,
|
||||
/// The block type is birch foliage
|
||||
Birch,
|
||||
/// The block type is spruce foliage
|
||||
Spruce,
|
||||
/// The block type is colored using biome water colors
|
||||
Water,
|
||||
/// The block type is a wall sign
|
||||
///
|
||||
/// The WallSign flag is used to distinguish wall signs from
|
||||
/// freestanding or -hanging signs.
|
||||
WallSign,
|
||||
}
|
||||
|
||||
/// An RGB color with u8 components
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Encode, Decode)]
|
||||
pub struct Color(pub [u8; 3]);
|
||||
|
||||
/// An RGB color with f32 components
|
||||
pub type Colorf = glam::Vec3;
|
||||
|
||||
/// A block type specification
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BlockColor {
|
||||
/// Bit set of [BlockFlag]s describing special properties of the block type
|
||||
pub flags: BitFlags<BlockFlag>,
|
||||
/// Base color of the block type
|
||||
pub color: Color,
|
||||
}
|
||||
|
||||
impl BlockColor {
|
||||
/// Checks whether a block color has a given [BlockFlag] set
|
||||
#[inline]
|
||||
pub fn is(&self, flag: BlockFlag) -> bool {
|
||||
self.flags.contains(flag)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for BlockColor {
|
||||
fn encode<E: bincode::enc::Encoder>(
|
||||
&self,
|
||||
encoder: &mut E,
|
||||
) -> Result<(), bincode::error::EncodeError> {
|
||||
bincode::Encode::encode(&self.flags.bits(), encoder)?;
|
||||
bincode::Encode::encode(&self.color, encoder)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Context> Decode<Context> for BlockColor {
|
||||
fn decode<D: bincode::de::Decoder<Context = Context>>(
|
||||
decoder: &mut D,
|
||||
) -> Result<Self, bincode::error::DecodeError> {
|
||||
Ok(BlockColor {
|
||||
flags: BitFlags::from_bits(bincode::Decode::decode(decoder)?).or(Err(
|
||||
bincode::error::DecodeError::Other("invalid block flags"),
|
||||
))?,
|
||||
color: bincode::Decode::decode(decoder)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, Context> BorrowDecode<'de, Context> for BlockColor {
|
||||
fn borrow_decode<D: bincode::de::BorrowDecoder<'de, Context = Context>>(
|
||||
decoder: &mut D,
|
||||
) -> Result<Self, bincode::error::DecodeError> {
|
||||
Ok(BlockColor {
|
||||
flags: BitFlags::from_bits(bincode::BorrowDecode::borrow_decode(decoder)?).or(Err(
|
||||
bincode::error::DecodeError::Other("invalid block flags"),
|
||||
))?,
|
||||
color: bincode::BorrowDecode::borrow_decode(decoder)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A block type specification (for use in constants)
|
||||
#[derive(Debug, Clone)]
|
||||
struct ConstBlockType {
|
||||
/// Determines the rendered color of the block type
|
||||
pub block_color: BlockColor,
|
||||
/// Material of a sign block
|
||||
pub sign_material: Option<&'static str>,
|
||||
}
|
||||
|
||||
/// A block type specification
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BlockType {
|
||||
/// Determines the rendered color of the block type
|
||||
pub block_color: BlockColor,
|
||||
/// Material of a sign block
|
||||
pub sign_material: Option<String>,
|
||||
}
|
||||
|
||||
impl From<&ConstBlockType> for BlockType {
|
||||
fn from(value: &ConstBlockType) -> Self {
|
||||
BlockType {
|
||||
block_color: value.block_color,
|
||||
sign_material: value.sign_material.map(String::from),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to look up standard Minecraft block types
|
||||
#[derive(Debug)]
|
||||
pub struct BlockTypes {
|
||||
/// Map of string IDs to block types
|
||||
block_type_map: HashMap<String, BlockType>,
|
||||
/// Array used to look up old numeric block type and subtype values
|
||||
legacy_block_types: Box<[[BlockType; 16]; 256]>,
|
||||
}
|
||||
|
||||
impl Default for BlockTypes {
|
||||
fn default() -> Self {
|
||||
let block_type_map: HashMap<_, _> = block_types::BLOCK_TYPES
|
||||
.iter()
|
||||
.map(|(k, v)| (String::from(*k), BlockType::from(v)))
|
||||
.collect();
|
||||
let legacy_block_types = Box::new(legacy_block_types::LEGACY_BLOCK_TYPES.map(|inner| {
|
||||
inner.map(|id| {
|
||||
block_type_map
|
||||
.get(id)
|
||||
.expect("Unknown legacy block type")
|
||||
.clone()
|
||||
})
|
||||
}));
|
||||
|
||||
BlockTypes {
|
||||
block_type_map,
|
||||
legacy_block_types,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockTypes {
|
||||
/// Resolves a Minecraft 1.13+ string block type ID
|
||||
#[inline]
|
||||
pub fn get(&self, id: &str) -> Option<&BlockType> {
|
||||
let suffix = id.strip_prefix("minecraft:")?;
|
||||
self.block_type_map.get(suffix)
|
||||
}
|
||||
|
||||
/// Resolves a Minecraft pre-1.13 numeric block type ID
|
||||
#[inline]
|
||||
pub fn get_legacy(&self, id: u8, data: u8) -> Option<&BlockType> {
|
||||
Some(&self.legacy_block_types[id as usize][data as usize])
|
||||
}
|
||||
}
|
||||
|
||||
pub use block_color::{block_color, needs_biome};
|
||||
|
||||
/// Grass color modifier used by a biome
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Encode, Decode)]
|
||||
pub enum BiomeGrassColorModifier {
|
||||
/// Grass color modifier used by the dark forest biome
|
||||
DarkForest,
|
||||
/// Grass color modifier used by swamp biomes
|
||||
Swamp,
|
||||
}
|
||||
|
||||
/// A biome specification
|
||||
///
|
||||
/// A Biome contains all information about a biome necessary to compute a block
|
||||
/// color given a block type and depth
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Encode, Decode)]
|
||||
pub struct Biome {
|
||||
/// Temperature value
|
||||
///
|
||||
/// For more efficient storage, the temperature is stored as an integer
|
||||
/// after mutiplying the raw value by 20
|
||||
pub temp: i8,
|
||||
/// Downfall value
|
||||
///
|
||||
/// For more efficient storage, the downfall is stored as an integer
|
||||
/// after mutiplying the raw value by 20
|
||||
pub downfall: i8,
|
||||
/// Water color override
|
||||
pub water_color: Option<Color>,
|
||||
/// Foliage color override
|
||||
pub foliage_color: Option<Color>,
|
||||
/// Grass color override
|
||||
pub grass_color: Option<Color>,
|
||||
/// Grass color modifier
|
||||
pub grass_color_modifier: Option<BiomeGrassColorModifier>,
|
||||
}
|
||||
|
||||
impl Biome {
|
||||
/// Constructs a new Biome
|
||||
const fn new(temp: i16, downfall: i16) -> Biome {
|
||||
/// Helper to encode temperature and downfall values
|
||||
///
|
||||
/// Converts temperatue and downfall from the input format
|
||||
/// (mutiplied by 100) to i8 range for more efficient storage.
|
||||
const fn encode(v: i16) -> i8 {
|
||||
(v / 5) as i8
|
||||
}
|
||||
Biome {
|
||||
temp: encode(temp),
|
||||
downfall: encode(downfall),
|
||||
grass_color_modifier: None,
|
||||
water_color: None,
|
||||
foliage_color: None,
|
||||
grass_color: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder function to override the biome water color
|
||||
const fn water(self, water_color: [u8; 3]) -> Biome {
|
||||
Biome {
|
||||
water_color: Some(Color(water_color)),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder function to override the biome foliage color
|
||||
const fn foliage(self, foliage_color: [u8; 3]) -> Biome {
|
||||
Biome {
|
||||
foliage_color: Some(Color(foliage_color)),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder function to override the biome grass color
|
||||
const fn grass(self, grass_color: [u8; 3]) -> Biome {
|
||||
Biome {
|
||||
grass_color: Some(Color(grass_color)),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder function to set a grass color modifier
|
||||
const fn modify(self, grass_color_modifier: BiomeGrassColorModifier) -> Biome {
|
||||
Biome {
|
||||
grass_color_modifier: Some(grass_color_modifier),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Decodes a temperature or downfall value from the storage format to
|
||||
/// f32 for further calculation
|
||||
fn decode(val: i8) -> f32 {
|
||||
f32::from(val) / 20.0
|
||||
}
|
||||
|
||||
/// Returns the biome's temperature decoded to its original float value
|
||||
pub fn temp(&self) -> f32 {
|
||||
Self::decode(self.temp)
|
||||
}
|
||||
|
||||
/// Returns the biome's downfall decoded to its original float value
|
||||
pub fn downfall(&self) -> f32 {
|
||||
Self::decode(self.downfall)
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to look up standard Minecraft biome types
|
||||
#[derive(Debug)]
|
||||
pub struct BiomeTypes {
|
||||
/// Map of string IDs to biome types
|
||||
biome_map: HashMap<String, &'static Biome>,
|
||||
/// Array used to look up old numeric biome IDs
|
||||
legacy_biomes: Box<[&'static Biome; 256]>,
|
||||
/// Fallback for unknown (new/modded) biomes
|
||||
fallback_biome: &'static Biome,
|
||||
}
|
||||
|
||||
impl Default for BiomeTypes {
|
||||
fn default() -> Self {
|
||||
let mut biome_map: HashMap<_, _> = biomes::BIOMES
|
||||
.iter()
|
||||
.map(|(k, v)| (String::from(*k), v))
|
||||
.collect();
|
||||
|
||||
for &(old, new) in legacy_biomes::BIOME_ALIASES.iter().rev() {
|
||||
let biome = biome_map
|
||||
.get(new)
|
||||
.copied()
|
||||
.expect("Biome alias for unknown biome");
|
||||
assert!(biome_map.insert(String::from(old), biome).is_none());
|
||||
}
|
||||
|
||||
let legacy_biomes = (0..=255)
|
||||
.map(|index| {
|
||||
let id = legacy_biomes::legacy_biome(index);
|
||||
*biome_map.get(id).expect("Unknown legacy biome")
|
||||
})
|
||||
.collect::<Box<[_]>>()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
let fallback_biome = *biome_map.get("plains").expect("Plains biome undefined");
|
||||
|
||||
Self {
|
||||
biome_map,
|
||||
legacy_biomes,
|
||||
fallback_biome,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BiomeTypes {
|
||||
/// Resolves a Minecraft 1.18+ string biome type ID
|
||||
#[inline]
|
||||
pub fn get(&self, id: &str) -> Option<&Biome> {
|
||||
let suffix = id.strip_prefix("minecraft:")?;
|
||||
self.biome_map.get(suffix).copied()
|
||||
}
|
||||
|
||||
/// Resolves a Minecraft pre-1.18 numeric biome type ID
|
||||
#[inline]
|
||||
pub fn get_legacy(&self, id: u8) -> Option<&Biome> {
|
||||
Some(self.legacy_biomes[id as usize])
|
||||
}
|
||||
|
||||
/// Returns the fallback for unknown (new/modded) biomes
|
||||
#[inline]
|
||||
pub fn get_fallback(&self) -> &Biome {
|
||||
self.fallback_biome
|
||||
}
|
||||
}
|
12
crates/types/Cargo.toml
Normal file
12
crates/types/Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "minedmap-types"
|
||||
version = "0.2.0"
|
||||
description = "Common types used by several MinedMap crates"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
readme.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
bincode = "2.0.1"
|
||||
itertools = "0.14.0"
|
236
crates/types/src/lib.rs
Normal file
236
crates/types/src/lib.rs
Normal file
|
@ -0,0 +1,236 @@
|
|||
#![doc = env!("CARGO_PKG_DESCRIPTION")]
|
||||
#![warn(missing_docs)]
|
||||
#![warn(clippy::missing_docs_in_private_items)]
|
||||
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
iter::FusedIterator,
|
||||
ops::{Index, IndexMut},
|
||||
};
|
||||
|
||||
use bincode::{Decode, Encode};
|
||||
use itertools::iproduct;
|
||||
|
||||
/// Const generic AXIS arguments for coordinate types
|
||||
pub mod axis {
|
||||
/// The X axis
|
||||
pub const X: u8 = 0;
|
||||
/// The Y axis (height)
|
||||
pub const Y: u8 = 1;
|
||||
/// The Z axis
|
||||
pub const Z: u8 = 2;
|
||||
}
|
||||
|
||||
/// Generates a generic coordinate type with a given range
|
||||
macro_rules! coord_type {
|
||||
($t:ident, $max:expr, $doc:expr $(,)?) => {
|
||||
#[doc = $doc]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct $t<const AXIS: u8>(pub u8);
|
||||
|
||||
impl<const AXIS: u8> $t<AXIS> {
|
||||
const MAX: usize = $max;
|
||||
|
||||
/// Constructs a new value
|
||||
///
|
||||
/// Will panic if the value is not in the valid range
|
||||
#[inline]
|
||||
pub fn new<T: TryInto<u8>>(value: T) -> Self {
|
||||
Self(
|
||||
value
|
||||
.try_into()
|
||||
.ok()
|
||||
.filter(|&v| (v as usize) < Self::MAX)
|
||||
.expect("coordinate should be in the valid range"),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns an iterator over all possible values of the type
|
||||
#[inline]
|
||||
pub fn iter() -> impl DoubleEndedIterator<Item = $t<AXIS>>
|
||||
+ ExactSizeIterator
|
||||
+ FusedIterator
|
||||
+ Clone
|
||||
+ Debug {
|
||||
(0..Self::MAX as u8).map($t)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Number of bits required to store a block coordinate
|
||||
pub const BLOCK_BITS: u8 = 4;
|
||||
/// Number of blocks per chunk in each dimension
|
||||
pub const BLOCKS_PER_CHUNK: usize = 1 << BLOCK_BITS;
|
||||
coord_type!(
|
||||
BlockCoord,
|
||||
BLOCKS_PER_CHUNK,
|
||||
"A block coordinate relative to a chunk",
|
||||
);
|
||||
|
||||
/// A block X coordinate relative to a chunk
|
||||
pub type BlockX = BlockCoord<{ axis::X }>;
|
||||
|
||||
/// A block Y coordinate relative to a chunk section
|
||||
pub type BlockY = BlockCoord<{ axis::Y }>;
|
||||
|
||||
/// A block Z coordinate relative to a chunk
|
||||
pub type BlockZ = BlockCoord<{ axis::Z }>;
|
||||
|
||||
/// X and Z coordinates of a block in a chunk
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct LayerBlockCoords {
|
||||
/// The X coordinate
|
||||
pub x: BlockX,
|
||||
/// The Z coordinate
|
||||
pub z: BlockZ,
|
||||
}
|
||||
|
||||
impl Debug for LayerBlockCoords {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "({}, {})", self.x.0, self.z.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl LayerBlockCoords {
|
||||
/// Computes a block's offset in various data structures
|
||||
///
|
||||
/// Many chunk data structures store block and biome data in the same
|
||||
/// order. This method computes the offset at which the data for the
|
||||
/// block at a given coordinate is stored.
|
||||
#[inline]
|
||||
pub fn offset(&self) -> usize {
|
||||
use BLOCKS_PER_CHUNK as N;
|
||||
let x = self.x.0 as usize;
|
||||
let z = self.z.0 as usize;
|
||||
N * z + x
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic array for data stored per block of a chunk layer
|
||||
///
|
||||
/// Includes various convenient iteration functions.
|
||||
#[derive(Debug, Clone, Copy, Default, Encode, Decode)]
|
||||
pub struct LayerBlockArray<T>(pub [[T; BLOCKS_PER_CHUNK]; BLOCKS_PER_CHUNK]);
|
||||
|
||||
impl<T> Index<LayerBlockCoords> for LayerBlockArray<T> {
|
||||
type Output = T;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, index: LayerBlockCoords) -> &Self::Output {
|
||||
&self.0[index.z.0 as usize][index.x.0 as usize]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IndexMut<LayerBlockCoords> for LayerBlockArray<T> {
|
||||
#[inline]
|
||||
fn index_mut(&mut self, index: LayerBlockCoords) -> &mut Self::Output {
|
||||
&mut self.0[index.z.0 as usize][index.x.0 as usize]
|
||||
}
|
||||
}
|
||||
|
||||
/// X, Y and Z coordinates of a block in a chunk section
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct SectionBlockCoords {
|
||||
/// The X and Z coordinates
|
||||
pub xz: LayerBlockCoords,
|
||||
/// The Y coordinate
|
||||
pub y: BlockY,
|
||||
}
|
||||
|
||||
impl SectionBlockCoords {
|
||||
/// Computes a block's offset in various data structures
|
||||
///
|
||||
/// Many chunk data structures store block and biome data in the same
|
||||
/// order. This method computes the offset at which the data for the
|
||||
/// block at a given coordinate is stored.
|
||||
#[inline]
|
||||
pub fn offset(&self) -> usize {
|
||||
use BLOCKS_PER_CHUNK as N;
|
||||
let y = self.y.0 as usize;
|
||||
N * N * y + self.xz.offset()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for SectionBlockCoords {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "({}, {}, {})", self.xz.x.0, self.y.0, self.xz.z.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// A section Y coordinate
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct SectionY(pub i32);
|
||||
|
||||
/// Number of bits required to store a chunk coordinate
|
||||
pub const CHUNK_BITS: u8 = 5;
|
||||
/// Number of chunks per region in each dimension
|
||||
pub const CHUNKS_PER_REGION: usize = 1 << CHUNK_BITS;
|
||||
coord_type!(
|
||||
ChunkCoord,
|
||||
CHUNKS_PER_REGION,
|
||||
"A chunk coordinate relative to a region",
|
||||
);
|
||||
|
||||
/// A chunk X coordinate relative to a region
|
||||
pub type ChunkX = ChunkCoord<{ axis::X }>;
|
||||
|
||||
/// A chunk Z coordinate relative to a region
|
||||
pub type ChunkZ = ChunkCoord<{ axis::Z }>;
|
||||
|
||||
/// A pair of chunk coordinates relative to a region
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ChunkCoords {
|
||||
/// The X coordinate
|
||||
pub x: ChunkX,
|
||||
/// The Z coordinate
|
||||
pub z: ChunkZ,
|
||||
}
|
||||
|
||||
impl Debug for ChunkCoords {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "({}, {})", self.x.0, self.z.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic array for data stored per chunk of a region
|
||||
///
|
||||
/// Includes various convenient iteration functions.
|
||||
#[derive(Debug, Clone, Copy, Default, Encode, Decode)]
|
||||
pub struct ChunkArray<T>(pub [[T; CHUNKS_PER_REGION]; CHUNKS_PER_REGION]);
|
||||
|
||||
impl<T> ChunkArray<T> {
|
||||
/// Iterates over all possible chunk coordinate pairs used as [ChunkArray] keys
|
||||
#[inline]
|
||||
pub fn keys() -> impl Iterator<Item = ChunkCoords> + Clone + Debug {
|
||||
iproduct!(ChunkZ::iter(), ChunkX::iter()).map(|(z, x)| ChunkCoords { x, z })
|
||||
}
|
||||
|
||||
/// Iterates over all values stored in the [ChunkArray]
|
||||
#[inline]
|
||||
pub fn values(&self) -> impl Iterator<Item = &T> + Clone + Debug {
|
||||
Self::keys().map(|k| &self[k])
|
||||
}
|
||||
|
||||
/// Iterates over pairs of chunk coordinate pairs and corresponding stored values
|
||||
#[inline]
|
||||
pub fn iter(&self) -> impl Iterator<Item = (ChunkCoords, &T)> + Clone + Debug {
|
||||
Self::keys().map(|k| (k, &self[k]))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Index<ChunkCoords> for ChunkArray<T> {
|
||||
type Output = T;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, index: ChunkCoords) -> &Self::Output {
|
||||
&self.0[index.z.0 as usize][index.x.0 as usize]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IndexMut<ChunkCoords> for ChunkArray<T> {
|
||||
#[inline]
|
||||
fn index_mut(&mut self, index: ChunkCoords) -> &mut Self::Output {
|
||||
&mut self.0[index.z.0 as usize][index.x.0 as usize]
|
||||
}
|
||||
}
|
51
docker-compose.yml
Normal file
51
docker-compose.yml
Normal file
|
@ -0,0 +1,51 @@
|
|||
# This is an example docker-compose configuration providing a Minecraft server,
|
||||
# map generator and webserver. Visit http://localhost:8080 to view the map.
|
||||
#
|
||||
# See https://docker-minecraft-server.readthedocs.io/ for more information on
|
||||
# the itzg/minecraft-server image and its configuration.
|
||||
|
||||
services:
|
||||
mc:
|
||||
image: docker.io/itzg/minecraft-server
|
||||
environment:
|
||||
EULA: 'true'
|
||||
ports:
|
||||
- '25565:25565'
|
||||
volumes:
|
||||
- data:/data
|
||||
stdin_open: true
|
||||
tty: true
|
||||
restart: unless-stopped
|
||||
|
||||
minedmap:
|
||||
image: ghcr.io/neocturne/minedmap/minedmap
|
||||
command:
|
||||
- '--jobs-initial=2'
|
||||
- '--image-format=webp'
|
||||
- '--sign-filter=\[Map\]'
|
||||
- '--sign-transform=s/\[Map\]//'
|
||||
- '--watch'
|
||||
- '/input/world'
|
||||
- '/output'
|
||||
volumes:
|
||||
- data:/input:ro
|
||||
- output:/output
|
||||
- processed:/output/processed
|
||||
network_mode: 'none'
|
||||
depends_on:
|
||||
mc:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
|
||||
viewer:
|
||||
image: ghcr.io/neocturne/minedmap/viewer
|
||||
ports:
|
||||
- '8080:80'
|
||||
volumes:
|
||||
- output:/usr/share/nginx/html/data:ro
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
data: {}
|
||||
processed: {}
|
||||
output: {}
|
BIN
docs/images/signs.png
Normal file
BIN
docs/images/signs.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 90 KiB |
|
@ -10,13 +10,16 @@ work.
|
|||
of two different versions
|
||||
- `extract.py`: Takes the block type information from `blocks.json` and texture data
|
||||
from an unpacked Minecraft JAR, storing the result in `colors.json`
|
||||
- `generate.py`: Generates `BlockType.inc.cpp` from `colors.json`
|
||||
- `generate.py`: Generates `block_types.rs` from `colors.json`
|
||||
- `biomes.py`: Generates `biomes.rs` from biome JSON files of an unpacked
|
||||
Minecraft JAR
|
||||
- `sign_textures.py`: Generates all needed sign graphics from Minecraft assets
|
||||
|
||||
In addition to these scripts, the JSON processor *jq* is a useful tool to work
|
||||
with MinedMap's resource metadata.
|
||||
|
||||
|
||||
## How to add support for block IDs of a new Minecraft version
|
||||
## How to add support for block IDs and biomes of a new Minecraft version
|
||||
|
||||
1. Download the Minecraft version you want to support as well as the previous
|
||||
version currently supported by MinedMap. You can use the Minecraft launcher
|
||||
|
@ -42,12 +45,13 @@ with MinedMap's resource metadata.
|
|||
5. Edit `blocks.json` until the following command passes without errors:
|
||||
|
||||
```sh
|
||||
./extract.py blocks.json data/new/assets/minecraft/textures/block colors.json
|
||||
./extract.py blocks.json data/new colors.json
|
||||
```
|
||||
|
||||
If possible, the top texture of blocks should be used where different sides
|
||||
exist. Block types that should not be visible on the map are just set to
|
||||
`null` in the JSON.
|
||||
`null` in the JSON (or have a `null` `texture` field when other flags need
|
||||
to be set, like for sign blocks).
|
||||
|
||||
The `water`, `grass` and `foliage` flags control biome-dependent texture color modifiers.
|
||||
|
||||
|
@ -63,9 +67,21 @@ with MinedMap's resource metadata.
|
|||
7. Update the source code with the new block colors:
|
||||
|
||||
```sh
|
||||
./generate.py colors.json ../src/Resource/BlockType.inc.cpp
|
||||
./generate.py colors.json ../crates/resource/src/block_types.rs
|
||||
cargo fmt --all
|
||||
```
|
||||
|
||||
8. Update the source code for new biome data:
|
||||
|
||||
```sh
|
||||
./biomes.py data/new ../crates/resource/src/biomes.rs
|
||||
cargo fmt --all
|
||||
```
|
||||
|
||||
After regenerating, check if only new biomes were added. If entries
|
||||
got removed, biomes may have been renamed or merged, requiring updates
|
||||
to the alias list in `crates/resource/src/legacy_biomes.rs`.
|
||||
|
||||
After the update, the new version should be tested with old savegames (both
|
||||
before and after migration by the new version) as well as newly generated
|
||||
worlds. Use creative mode to add the new block types to your test world.
|
||||
|
|
70
resource/biomes.py
Executable file
70
resource/biomes.py
Executable file
|
@ -0,0 +1,70 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
if len(sys.argv) != 3:
|
||||
sys.exit('Usage: biomes.py <data directory> <biomes.rs>')
|
||||
|
||||
biomes = {}
|
||||
|
||||
for file in os.scandir(os.path.join(sys.argv[1], 'data/minecraft/worldgen/biome')):
|
||||
(name, ext) = os.path.splitext(file.name)
|
||||
if ext != '.json':
|
||||
continue
|
||||
with open(file) as f:
|
||||
data = json.load(f)
|
||||
biomes[name] = {
|
||||
'downfall': data['downfall'],
|
||||
'temperature': data['temperature'],
|
||||
'foliage_color': data['effects'].get('foliage_color'),
|
||||
'grass_color': data['effects'].get('grass_color'),
|
||||
'grass_color_modifier': data['effects'].get('grass_color_modifier'),
|
||||
'water_color': data['effects'].get('water_color'),
|
||||
}
|
||||
|
||||
def color(v):
|
||||
return f'[{v>>16}, {(v>>8)&0xff}, {v&0xff}]'
|
||||
|
||||
# Converts the snake_case grass color modifier to CamelCase
|
||||
def modify(v):
|
||||
return ''.join([s.capitalize() for s in v.split('_')])
|
||||
|
||||
def gen_biome(name, info, f):
|
||||
temp = round(100*info['temperature'])
|
||||
downfall = round(100*info['downfall'])
|
||||
foliage_color = info['foliage_color']
|
||||
grass_color = info['grass_color']
|
||||
grass_color_modifier = info['grass_color_modifier']
|
||||
water_color = info['water_color']
|
||||
|
||||
print(f'\t("{name}", Biome::new({temp}, {downfall})', file=f)
|
||||
|
||||
if foliage_color is not None:
|
||||
print(f'\t\t.foliage({color(foliage_color)})', file=f)
|
||||
if grass_color is not None:
|
||||
print(f'\t\t.grass({color(grass_color)})', file=f)
|
||||
if grass_color_modifier is not None:
|
||||
print(f'\t\t.modify({modify(grass_color_modifier)})', file=f)
|
||||
if water_color is not None and water_color != 0x3f76e4:
|
||||
print(f'\t\t.water({color(water_color)})', file=f)
|
||||
|
||||
print('\t),', file=f)
|
||||
|
||||
with open(sys.argv[2], 'w') as f:
|
||||
print('//! Biome data', file=f);
|
||||
print('//!', file=f);
|
||||
print('//! This file is generated using resource/biomes.py, do not edit', file=f);
|
||||
print('', file=f)
|
||||
print('use super::*;', file=f)
|
||||
print('use BiomeGrassColorModifier::*;', file=f)
|
||||
print('', file=f)
|
||||
print('/// List if known biomes and their properties', file=f);
|
||||
print('pub const BIOMES: &[(&str, Biome)] = &[', file=f)
|
||||
|
||||
for name in sorted(biomes):
|
||||
gen_biome(name, biomes[name], f)
|
||||
|
||||
print('];', file=f)
|
File diff suppressed because it is too large
Load diff
|
@ -11,7 +11,7 @@ if len(sys.argv) != 4:
|
|||
sys.exit('Usage: extract.py <blocks.json> <asset directory> <colors.json>')
|
||||
|
||||
def mean_color(texture):
|
||||
path = os.path.join(sys.argv[2], texture + '.png')
|
||||
path = os.path.join(sys.argv[2], 'assets/minecraft/textures/block', texture + '.png')
|
||||
im = Image.open(path)
|
||||
|
||||
data = im.convert('RGBA').getdata()
|
||||
|
@ -35,7 +35,7 @@ with open(sys.argv[1]) as f:
|
|||
output = {}
|
||||
|
||||
for name, info in blocks.items():
|
||||
id = 'minecraft:' + name
|
||||
id = name
|
||||
|
||||
output[id] = {
|
||||
'color': {'r': 0, 'g': 0, 'b': 0},
|
||||
|
@ -45,20 +45,30 @@ for name, info in blocks.items():
|
|||
'birch': False,
|
||||
'spruce': False,
|
||||
'water': False,
|
||||
'wall_sign': False,
|
||||
'sign_material': None,
|
||||
}
|
||||
|
||||
if info is None:
|
||||
continue
|
||||
|
||||
color = mean_color(info.get('texture', name))
|
||||
texture = info.get('texture', name)
|
||||
|
||||
color = None
|
||||
if texture:
|
||||
color = mean_color(texture)
|
||||
if color:
|
||||
output[id]['color'] = color
|
||||
output[id]['opaque'] = True
|
||||
output[id]['grass'] = info.get('grass', False)
|
||||
output[id]['foliage'] = info.get('foliage', False)
|
||||
output[id]['birch'] = info.get('birch', False)
|
||||
output[id]['spruce'] = info.get('spruce', False)
|
||||
output[id]['water'] = info.get('water', False)
|
||||
|
||||
output[id]['grass'] = info.get('grass', False)
|
||||
output[id]['foliage'] = info.get('foliage', False)
|
||||
output[id]['birch'] = info.get('birch', False)
|
||||
output[id]['spruce'] = info.get('spruce', False)
|
||||
output[id]['water'] = info.get('water', False)
|
||||
output[id]['wall_sign'] = info.get('wall_sign', False)
|
||||
|
||||
output[id]['sign_material'] = info.get('sign_material')
|
||||
|
||||
with open(sys.argv[3], 'w') as f:
|
||||
json.dump(output, f)
|
||||
|
|
|
@ -6,7 +6,7 @@ import sys
|
|||
|
||||
|
||||
if len(sys.argv) != 3:
|
||||
sys.exit('Usage: extract.py <colors.json> <BlockType.inc.cpp>')
|
||||
sys.exit('Usage: generate.py <colors.json> <block_types.rs>')
|
||||
|
||||
with open(sys.argv[1]) as f:
|
||||
colors = json.load(f)
|
||||
|
@ -14,29 +14,47 @@ with open(sys.argv[1]) as f:
|
|||
output = {}
|
||||
|
||||
with open(sys.argv[2], 'w') as f:
|
||||
print('//! Block type information', file=f);
|
||||
print('//!', file=f);
|
||||
print('//! This file is generated using resource/generate.py, do not edit', file=f);
|
||||
print('', file=f)
|
||||
print('use enumflags2::make_bitflags;', file=f);
|
||||
print('', file=f)
|
||||
print('use super::*;', file=f)
|
||||
print('', file=f)
|
||||
print('/// List if known block types and their properties', file=f);
|
||||
print('pub const BLOCK_TYPES: &[(&str, ConstBlockType)] = &[', file=f)
|
||||
|
||||
for name, info in colors.items():
|
||||
flags = []
|
||||
if info['opaque']:
|
||||
flags.append('BLOCK_OPAQUE')
|
||||
flags.append('Opaque')
|
||||
if info['grass']:
|
||||
flags.append('BLOCK_GRASS')
|
||||
flags.append('Grass')
|
||||
if info['foliage']:
|
||||
flags.append('BLOCK_FOLIAGE')
|
||||
flags.append('Foliage')
|
||||
if info['birch']:
|
||||
flags.append('BLOCK_BIRCH')
|
||||
flags.append('Birch')
|
||||
if info['spruce']:
|
||||
flags.append('BLOCK_SPRUCE')
|
||||
flags.append('Spruce')
|
||||
if info['water']:
|
||||
flags.append('BLOCK_WATER')
|
||||
if flags:
|
||||
flags = '|'.join(flags)
|
||||
else:
|
||||
flags = '0'
|
||||
flags.append('Water')
|
||||
if info['wall_sign']:
|
||||
flags.append('WallSign')
|
||||
flags = 'make_bitflags!(BlockFlag::{' + '|'.join(flags) + '})'
|
||||
|
||||
print('{"%s", {%s, {%u, %u, %u}}},' % (
|
||||
name,
|
||||
sign_material = 'None'
|
||||
if info['sign_material']:
|
||||
sign_material = 'Some("%s")' % info['sign_material']
|
||||
|
||||
print('\t("%s", ConstBlockType { ' % name, file=f)
|
||||
print('\t\tblock_color: BlockColor { flags: %s, color: Color([%u, %u, %u]) },' % (
|
||||
flags,
|
||||
info['color']['r'],
|
||||
info['color']['g'],
|
||||
info['color']['b'],
|
||||
), file=f)
|
||||
print('\t\tsign_material: %s,' % sign_material, file=f)
|
||||
print('}),', file=f)
|
||||
|
||||
print('];', file=f)
|
||||
|
|
91
resource/sign_textures.py
Executable file
91
resource/sign_textures.py
Executable file
|
@ -0,0 +1,91 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
from PIL import Image
|
||||
|
||||
MATERIALS = [
|
||||
'acacia',
|
||||
'bamboo',
|
||||
'birch',
|
||||
'cherry',
|
||||
'crimson',
|
||||
'dark_oak',
|
||||
'jungle',
|
||||
'mangrove',
|
||||
'oak',
|
||||
'pale_oak',
|
||||
'spruce',
|
||||
'warped',
|
||||
]
|
||||
|
||||
in_dir = sys.argv[1]
|
||||
out_dir = sys.argv[2]
|
||||
|
||||
def sign_bg_image(material):
|
||||
in_path = f'{in_dir}/assets/minecraft/textures/entity/signs/{material}.png'
|
||||
out_path = f'{out_dir}/bg/{material}_sign.png'
|
||||
out_path_wall = f'{out_dir}/bg/{material}_wall_sign.png'
|
||||
|
||||
in_image = Image.open(in_path)
|
||||
|
||||
out_image = Image.new('RGBA', (24, 26))
|
||||
out_image.paste(in_image.crop((2, 2, 26, 14)), (0, 0))
|
||||
out_image.paste(in_image.crop((2, 16, 4, 30)), (11, 12))
|
||||
out_image.save(out_path)
|
||||
|
||||
out_image = Image.new('RGBA', (24, 12))
|
||||
out_image.paste(in_image.crop((2, 2, 26, 14)), (0, 0))
|
||||
out_image.save(out_path_wall)
|
||||
|
||||
def hanging_sign_bg_image(material):
|
||||
in_path = f'{in_dir}/assets/minecraft/textures/gui/hanging_signs/{material}.png'
|
||||
out_path = f'{out_dir}/bg/{material}_hanging_sign.png'
|
||||
out_path_wall = f'{out_dir}/bg/{material}_hanging_wall_sign.png'
|
||||
|
||||
in_image = Image.open(in_path)
|
||||
|
||||
out_image = Image.new('RGBA', (16, 14))
|
||||
out_image.paste(in_image.crop((0, 2, 16, 16)), (0, 0))
|
||||
out_image.save(out_path)
|
||||
|
||||
shutil.copyfile(in_path, out_path_wall)
|
||||
|
||||
|
||||
def sign_icon_image(material):
|
||||
in_path = f'{in_dir}/assets/minecraft/textures/item/{material}_sign.png'
|
||||
out_path = f'{out_dir}/icon/{material}_sign.png'
|
||||
out_path_wall = f'{out_dir}/icon/{material}_wall_sign.png'
|
||||
|
||||
in_image = Image.open(in_path)
|
||||
|
||||
out_image = Image.new('RGBA', (13, 14))
|
||||
out_image.paste(in_image.crop((2, 2, 15, 16)), (0, 0))
|
||||
out_image.save(out_path)
|
||||
|
||||
out_image = Image.new('RGBA', (13, 9))
|
||||
out_image.paste(in_image.crop((2, 2, 15, 11)), (0, 0))
|
||||
out_image.save(out_path_wall)
|
||||
|
||||
|
||||
def hanging_sign_icon_image(material):
|
||||
in_path = f'{in_dir}/assets/minecraft/textures/item/{material}_hanging_sign.png'
|
||||
out_path = f'{out_dir}/icon/{material}_hanging_sign.png'
|
||||
out_path_wall = f'{out_dir}/icon/{material}_hanging_wall_sign.png'
|
||||
|
||||
in_image = Image.open(in_path)
|
||||
|
||||
out_image = Image.new('RGBA', (14, 12))
|
||||
out_image.paste(in_image.crop((1, 3, 15, 15)), (0, 0))
|
||||
out_image.save(out_path)
|
||||
|
||||
out_image = Image.new('RGBA', (14, 14))
|
||||
out_image.paste(in_image.crop((1, 1, 15, 15)), (0, 0))
|
||||
out_image.save(out_path_wall)
|
||||
|
||||
for material in MATERIALS:
|
||||
sign_bg_image(material)
|
||||
hanging_sign_bg_image(material)
|
||||
sign_icon_image(material)
|
||||
hanging_sign_icon_image(material)
|
1
rustfmt.toml
Normal file
1
rustfmt.toml
Normal file
|
@ -0,0 +1 @@
|
|||
hard_tabs = true
|
|
@ -1,71 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
|
||||
class Buffer {
|
||||
private:
|
||||
const uint8_t *data;
|
||||
size_t len;
|
||||
|
||||
public:
|
||||
static uint16_t parse16(const uint8_t *buf) {
|
||||
return (buf[0] << 8) | buf[1];
|
||||
}
|
||||
|
||||
static uint32_t parse32(const uint8_t *buf) {
|
||||
return (uint32_t(buf[0]) << 24) | (uint32_t(buf[1]) << 16) | (uint32_t(buf[2]) << 8) | uint32_t(buf[3]);
|
||||
}
|
||||
|
||||
static uint64_t parse64(const uint8_t *buf) {
|
||||
return (uint64_t(buf[0]) << 56) | (uint64_t(buf[1]) << 48) | (uint64_t(buf[2]) << 40) | (uint64_t(buf[3]) << 32)
|
||||
| (uint64_t(buf[4]) << 24) | (uint64_t(buf[5]) << 16) | (uint64_t(buf[6]) << 8) | uint64_t(buf[7]);
|
||||
}
|
||||
|
||||
|
||||
Buffer(const uint8_t *data0, size_t len0) : data(data0), len(len0) {}
|
||||
|
||||
size_t getRemaining() const {
|
||||
return len;
|
||||
}
|
||||
|
||||
const uint8_t * get(size_t n) {
|
||||
if (n > len)
|
||||
throw std::runtime_error("Buffer::get(): buffer underrun");
|
||||
|
||||
data += n;
|
||||
len -= n;
|
||||
return (data - n);
|
||||
}
|
||||
|
||||
uint8_t get8() {
|
||||
return *get(1);
|
||||
}
|
||||
|
||||
uint16_t get16() {
|
||||
return parse16(get(2));
|
||||
}
|
||||
|
||||
uint32_t get32() {
|
||||
return parse32(get(4));
|
||||
}
|
||||
|
||||
uint64_t get64() {
|
||||
return parse64(get(8));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
add_compile_options(-std=c++11 -Wall)
|
||||
|
||||
add_executable(MinedMap
|
||||
MinedMap.cpp
|
||||
GZip.cpp
|
||||
Info.cpp
|
||||
PNG.cpp
|
||||
|
||||
NBT/Tag.cpp
|
||||
Resource/Biome.cpp
|
||||
Resource/BlockType.cpp
|
||||
World/Chunk.cpp
|
||||
World/ChunkData.cpp
|
||||
World/Level.cpp
|
||||
World/Region.cpp
|
||||
World/Section.cpp
|
||||
)
|
||||
target_link_libraries(MinedMap PkgConfig::ZLIB PkgConfig::PNG)
|
||||
|
||||
add_executable(nbtdump
|
||||
nbtdump.cpp
|
||||
GZip.cpp
|
||||
NBT/Tag.cpp
|
||||
)
|
||||
target_link_libraries(nbtdump PkgConfig::ZLIB)
|
||||
|
||||
add_executable(regiondump
|
||||
regiondump.cpp
|
||||
GZip.cpp
|
||||
NBT/Tag.cpp
|
||||
World/ChunkData.cpp
|
||||
World/Region.cpp
|
||||
)
|
||||
target_link_libraries(regiondump PkgConfig::ZLIB)
|
||||
|
||||
install(TARGETS MinedMap RUNTIME DESTINATION bin)
|
49
src/GZip.cpp
49
src/GZip.cpp
|
@ -1,49 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#include "GZip.hpp"
|
||||
|
||||
#include <system_error>
|
||||
#include <stdexcept>
|
||||
#include <zlib.h>
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
|
||||
std::vector<uint8_t> readGZip(const char *filename) {
|
||||
std::vector<uint8_t> buffer;
|
||||
size_t len = 0;
|
||||
|
||||
gzFile f = gzopen(filename, "rb");
|
||||
if (!f)
|
||||
throw std::system_error(
|
||||
errno, std::generic_category(),
|
||||
(std::string("unable to open file ") + filename).c_str()
|
||||
);
|
||||
|
||||
while (true) {
|
||||
if ((buffer.size() - len) < 4096)
|
||||
buffer.resize(buffer.size() + 4096);
|
||||
|
||||
int r = gzread(f, buffer.data()+len, buffer.size()-len);
|
||||
if (r < 0)
|
||||
throw std::system_error(errno, std::generic_category(), "error reading GZip file");
|
||||
|
||||
if (!r)
|
||||
break;
|
||||
|
||||
len += r;
|
||||
}
|
||||
|
||||
gzclose_r(f);
|
||||
|
||||
buffer.resize(len);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
}
|
18
src/GZip.hpp
18
src/GZip.hpp
|
@ -1,18 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
|
||||
std::vector<uint8_t> readGZip(const char *filename);
|
||||
|
||||
}
|
90
src/Info.cpp
90
src/Info.cpp
|
@ -1,90 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#include "Info.hpp"
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
|
||||
void Info::writeJSON(const char *filename) const {
|
||||
const std::string tmpfile = std::string(filename) + ".tmp";
|
||||
|
||||
FILE *f = std::fopen(tmpfile.c_str(), "w");
|
||||
if (!f) {
|
||||
std::fprintf(stderr, "Unable to open %s: %s\n", tmpfile.c_str(), std::strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
std::fprintf(f, "{");
|
||||
std::fprintf(f, "\"mipmaps\":[");
|
||||
|
||||
bool first_level = true;
|
||||
for (const auto &level : levels) {
|
||||
if (!first_level)
|
||||
std::fprintf(f, ",");
|
||||
first_level = false;
|
||||
|
||||
int minX, maxX, minZ, maxZ;
|
||||
std::tie(minX, maxX, minZ, maxZ) = level.bounds;
|
||||
|
||||
std::fprintf(f, "{");
|
||||
std::fprintf(f, "\"bounds\":{");
|
||||
std::fprintf(f, "\"minX\":%i,", minX);
|
||||
std::fprintf(f, "\"maxX\":%i,", maxX);
|
||||
std::fprintf(f, "\"minZ\":%i,", minZ);
|
||||
std::fprintf(f, "\"maxZ\":%i", maxZ);
|
||||
std::fprintf(f, "},");
|
||||
std::fprintf(f, "\"regions\":{");
|
||||
|
||||
bool first_z = true;
|
||||
for (const auto &item : level.regions) {
|
||||
if (!first_z)
|
||||
std::fprintf(f, ",");
|
||||
first_z = false;
|
||||
|
||||
int z = item.first;
|
||||
const std::set<int> &z_regions = item.second;
|
||||
|
||||
std::fprintf(f, "\"%d\":[", z);
|
||||
|
||||
bool first_x = true;
|
||||
for (int x : z_regions) {
|
||||
if (!first_x)
|
||||
std::fprintf(f, ",");
|
||||
first_x = false;
|
||||
|
||||
|
||||
std::fprintf(f, "%d", x);
|
||||
}
|
||||
|
||||
std::fprintf(f, "]");
|
||||
}
|
||||
|
||||
std::fprintf(f, "}}");
|
||||
}
|
||||
|
||||
std::fprintf(f, "],");
|
||||
std::fprintf(f, "\"spawn\":{");
|
||||
std::fprintf(f, "\"x\":%li,", (long)spawnX);
|
||||
std::fprintf(f, "\"z\":%li", (long)spawnZ);
|
||||
std::fprintf(f, "}");
|
||||
std::fprintf(f, "}");
|
||||
|
||||
std::fclose(f);
|
||||
|
||||
if (std::rename(tmpfile.c_str(), filename) < 0) {
|
||||
std::fprintf(stderr, "Unable to save %s: %s\n", filename, std::strerror(errno));
|
||||
std::remove(tmpfile.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
84
src/Info.hpp
84
src/Info.hpp
|
@ -1,84 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
|
||||
class Info {
|
||||
public:
|
||||
typedef std::function<void (int, int)> RegionVisitor;
|
||||
|
||||
struct Level {
|
||||
std::map<int, std::set<int>> regions;
|
||||
std::tuple<int, int, int, int> bounds;
|
||||
};
|
||||
|
||||
private:
|
||||
std::vector<Level> levels;
|
||||
|
||||
int32_t spawnX, spawnZ;
|
||||
|
||||
public:
|
||||
Info() : spawnX(0), spawnZ(0) {
|
||||
addMipmapLevel();
|
||||
}
|
||||
|
||||
std::tuple<int, int, int, int> getBounds(size_t level) const {
|
||||
return levels[level].bounds;
|
||||
}
|
||||
|
||||
void addRegion(int x, int z, size_t level) {
|
||||
auto &the_level = levels[level];
|
||||
auto z_regions = the_level.regions.emplace(
|
||||
std::piecewise_construct,
|
||||
std::make_tuple(z),
|
||||
std::make_tuple()).first;
|
||||
z_regions->second.insert(x);
|
||||
|
||||
std::tuple<int, int, int, int> &b = the_level.bounds;
|
||||
|
||||
if (x < std::get<0>(b)) std::get<0>(b) = x;
|
||||
if (x > std::get<1>(b)) std::get<1>(b) = x;
|
||||
if (z < std::get<2>(b)) std::get<2>(b) = z;
|
||||
if (z > std::get<3>(b)) std::get<3>(b) = z;
|
||||
}
|
||||
|
||||
void addMipmapLevel() {
|
||||
levels.emplace_back(Level {
|
||||
.regions = {},
|
||||
.bounds = {INT_MAX, INT_MIN, INT_MAX, INT_MIN},
|
||||
});
|
||||
}
|
||||
|
||||
void visitRegions(size_t level, const RegionVisitor &visitor) const {
|
||||
for (const auto &item : levels[level].regions) {
|
||||
int z = item.first;
|
||||
for (int x : item.second)
|
||||
visitor(x, z);
|
||||
}
|
||||
}
|
||||
|
||||
void setSpawn(const std::pair<int32_t, int32_t> &v) {
|
||||
std::tie(spawnX, spawnZ) = v;
|
||||
}
|
||||
|
||||
void writeJSON(const char *filename) const;
|
||||
};
|
||||
|
||||
}
|
507
src/MinedMap.cpp
507
src/MinedMap.cpp
|
@ -1,507 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015-2021, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#include "Info.hpp"
|
||||
#include "PNG.hpp"
|
||||
#include "World/Level.hpp"
|
||||
#include "World/Region.hpp"
|
||||
|
||||
#include <cerrno>
|
||||
#include <cinttypes>
|
||||
#include <climits>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
#include <stdexcept>
|
||||
#include <sstream>
|
||||
#include <system_error>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
|
||||
static const int BIOME_SMOOTH = 3;
|
||||
static const uint32_t DIM = World::Region::SIZE*World::Chunk::SIZE;
|
||||
|
||||
|
||||
static void addChunkBiome(uint8_t biomemap[DIM*DIM], chunk_idx_t X, chunk_idx_t Z, const World::ChunkData *data) {
|
||||
World::Chunk chunk(data);
|
||||
World::Chunk::Heightmap layer = chunk.getTopLayer(0);
|
||||
|
||||
for (block_idx_t x = 0; x < World::Chunk::SIZE; x++) {
|
||||
for (block_idx_t z = 0; z < World::Chunk::SIZE; z++) {
|
||||
size_t i = (Z*World::Chunk::SIZE+z)*DIM + X*World::Chunk::SIZE+x;
|
||||
const World::Chunk::Height &height = layer.v[x][z];
|
||||
biomemap[i] = chunk.getBiome(x, height.y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t biomeAt(int16_t x, int16_t z, const std::unique_ptr<uint8_t[]> biomemaps[3][3]) {
|
||||
size_t a = 1, b = 1;
|
||||
|
||||
if (x < 0) {
|
||||
a--;
|
||||
x += DIM;
|
||||
} else if (x >= (int32_t)DIM) {
|
||||
a++;
|
||||
x -= DIM;
|
||||
}
|
||||
if (z < 0) {
|
||||
b--;
|
||||
z += DIM;
|
||||
} else if (z >= (int32_t)DIM) {
|
||||
b++;
|
||||
z -= DIM;
|
||||
}
|
||||
|
||||
return biomemaps[a][b].get()[z*DIM + x];
|
||||
}
|
||||
|
||||
static Resource::Color collectColors(
|
||||
region_block_idx_t x, region_block_idx_t z,
|
||||
const World::Block &block, const std::unique_ptr<uint8_t[]> biomemaps[3][3]
|
||||
) {
|
||||
std::unordered_map<uint8_t, unsigned> biomes;
|
||||
for (int16_t dx = -BIOME_SMOOTH; dx <= BIOME_SMOOTH; dx++) {
|
||||
for (int16_t dz = -BIOME_SMOOTH; dz <= BIOME_SMOOTH; dz++) {
|
||||
if (std::abs(dx) + std::abs(dz) > BIOME_SMOOTH)
|
||||
continue;
|
||||
|
||||
uint8_t biome = biomeAt(x+dx, z+dz, biomemaps);
|
||||
if (biomes.count(biome))
|
||||
biomes[biome]++;
|
||||
else
|
||||
biomes[biome] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
Resource::FloatColor c = {};
|
||||
unsigned total = 0;
|
||||
|
||||
for (const auto &e : biomes) {
|
||||
uint8_t biome = e.first;
|
||||
unsigned count = e.second;
|
||||
|
||||
if (biome == 0xff)
|
||||
continue;
|
||||
|
||||
c = c + count * block.getColor(biome);
|
||||
total += count;
|
||||
}
|
||||
|
||||
if (!total)
|
||||
return block.getColor(0);
|
||||
|
||||
return (1.0f / total) * c;
|
||||
}
|
||||
|
||||
static void addChunk(Resource::Color image[DIM*DIM], uint8_t lightmap[2*DIM*DIM], chunk_idx_t X, chunk_idx_t Z,
|
||||
const World::ChunkData *data, const std::unique_ptr<uint8_t[]> biomemaps[3][3]
|
||||
) {
|
||||
World::Chunk chunk(data);
|
||||
World::Chunk::Heightmap layer = chunk.getTopLayer(World::Chunk::WITH_DEPTH);
|
||||
|
||||
for (block_idx_t x = 0; x < World::Chunk::SIZE; x++) {
|
||||
for (block_idx_t z = 0; z < World::Chunk::SIZE; z++) {
|
||||
size_t i = (Z*World::Chunk::SIZE+z)*DIM + X*World::Chunk::SIZE+x;
|
||||
const World::Chunk::Height &height = layer.v[x][z];
|
||||
World::Block block = chunk.getBlock(x, height, z);
|
||||
|
||||
if (!block.isVisible())
|
||||
continue;
|
||||
|
||||
image[i] = collectColors(X*World::Chunk::SIZE+x, Z*World::Chunk::SIZE+z, block, biomemaps);
|
||||
lightmap[2*i+1] = (1 - block.blockLight/15.f)*192;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int64_t readStamp(const std::string &filename) {
|
||||
int64_t v = INT64_MIN;
|
||||
|
||||
std::FILE *f = std::fopen((filename + ".stamp").c_str(), "r");
|
||||
if (f) {
|
||||
if (std::fscanf(f, "%" SCNd64, &v) != 1) {
|
||||
// Ignore errors
|
||||
}
|
||||
std::fclose(f);
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
static void writeStamp(const std::string &filename, int64_t v) {
|
||||
std::FILE *f = std::fopen((filename + ".stamp").c_str(), "w");
|
||||
if (f) {
|
||||
std::fprintf(f, "%" PRId64, v);
|
||||
std::fclose(f);
|
||||
}
|
||||
}
|
||||
|
||||
static bool writeImage(const std::string &output, const uint8_t *data, PNG::Format format, int64_t t) {
|
||||
const std::string tmpfile = output + ".tmp";
|
||||
|
||||
size_t len = PNG::formatBytes(format)*DIM*DIM;
|
||||
bool changed = true;
|
||||
|
||||
try {
|
||||
std::unique_ptr<uint8_t[]> old(new uint8_t[len]);
|
||||
PNG::read(output.c_str(), old.get(), DIM, DIM, format);
|
||||
|
||||
if (std::memcmp(data, old.get(), len) == 0)
|
||||
changed = false;
|
||||
} catch (const std::exception& ex) {
|
||||
}
|
||||
|
||||
try {
|
||||
if (changed) {
|
||||
PNG::write(tmpfile.c_str(), data, DIM, DIM, format);
|
||||
|
||||
if (std::rename(tmpfile.c_str(), output.c_str()) < 0) {
|
||||
std::fprintf(stderr, "Unable to save %s: %s\n", output.c_str(), std::strerror(errno));
|
||||
std::remove(tmpfile.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
writeStamp(output, t);
|
||||
} catch (const std::exception& ex) {
|
||||
std::remove(tmpfile.c_str());
|
||||
throw;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
static int64_t getModTime(const std::string &file) {
|
||||
struct stat s;
|
||||
if (stat(file.c_str(), &s) < 0) {
|
||||
if (errno != ENOENT)
|
||||
std::fprintf(stderr, "Unable to stat %s: %s\n", file.c_str(), std::strerror(errno));
|
||||
return INT64_MIN;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
return (int64_t)s.st_mtime * 1000000;
|
||||
#else
|
||||
return (int64_t)s.st_mtim.tv_sec * 1000000 + s.st_mtim.tv_nsec / 1000;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool checkRegion(int64_t changed, const std::string &file) {
|
||||
struct stat s;
|
||||
if (stat(file.c_str(), &s) < 0)
|
||||
return true;
|
||||
|
||||
int64_t outtime = readStamp(file);
|
||||
if (changed <= outtime) {
|
||||
std::printf("%s is up-to-date.\n", file.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static std::string format(const T &v) {
|
||||
std::ostringstream s;
|
||||
|
||||
s << v;
|
||||
return s.str();
|
||||
}
|
||||
|
||||
static std::string formatTileName(int x, int z, const std::string &ext) {
|
||||
std::ostringstream s;
|
||||
|
||||
s << "r." << x << "." << z << "." << ext;
|
||||
return s.str();
|
||||
}
|
||||
|
||||
static bool checkFilename(const char *name, int *x, int *z) {
|
||||
if (std::sscanf(name, "r.%i.%i.mca", x, z) != 2)
|
||||
return false;
|
||||
|
||||
return (std::string(name) == formatTileName(*x, *z, "mca"));
|
||||
}
|
||||
|
||||
static void makeDir(const std::string &name) {
|
||||
if (
|
||||
mkdir(
|
||||
name.c_str()
|
||||
#ifndef _WIN32
|
||||
, 0777
|
||||
#endif
|
||||
) < 0 && errno != EEXIST
|
||||
)
|
||||
throw std::system_error(errno, std::generic_category(), "unable to create directory " + name);
|
||||
}
|
||||
|
||||
static void makeBiome(const std::string ®iondir, const std::string &outputdir, int x, int z) {
|
||||
std::string inname = formatTileName(x, z, "mca");
|
||||
std::string outname = formatTileName(x, z, "png");
|
||||
std::string input = regiondir + "/" + inname, output = outputdir + "/biome/" + outname;
|
||||
|
||||
int64_t intime = getModTime(input);
|
||||
if (intime == INT64_MIN)
|
||||
return;
|
||||
|
||||
if (!checkRegion(intime, output))
|
||||
return;
|
||||
|
||||
std::printf("Generating %s from %s... ", output.c_str(), input.c_str());
|
||||
std::fflush(stdout);
|
||||
|
||||
try {
|
||||
std::unique_ptr<uint8_t[]> biomemap(new uint8_t[DIM*DIM]);
|
||||
std::memset(biomemap.get(), 0xff, DIM*DIM);
|
||||
|
||||
World::Region::visitChunks(input.c_str(), [&] (chunk_idx_t X, chunk_idx_t Z, const World::ChunkData *chunk) {
|
||||
addChunkBiome(biomemap.get(), X, Z, chunk);
|
||||
});
|
||||
|
||||
bool changed = writeImage(output, biomemap.get(), PNG::GRAY, intime);
|
||||
std::printf("%s.\n", changed ? "done" : "unchanged");
|
||||
} catch (const std::exception& ex) {
|
||||
std::fprintf(stderr, "Failed to generate %s: %s\n", output.c_str(), ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
static void makeBiomes(const std::string ®iondir, const std::string &outputdir, const Info *info) {
|
||||
info->visitRegions(0, [&] (int x, int z) {
|
||||
makeBiome(regiondir, outputdir, x, z);
|
||||
});
|
||||
}
|
||||
|
||||
static void makeMap(const std::string ®iondir, const std::string &outputdir, int x, int z) {
|
||||
std::string inname = formatTileName(x, z, "mca");
|
||||
std::string outname = formatTileName(x, z, "png");
|
||||
std::string input = regiondir + "/" + inname;
|
||||
std::string output = outputdir + "/map/0/" + outname, output_light = outputdir + "/light/0/" + outname;
|
||||
std::string biomenames[3][3];
|
||||
|
||||
int64_t intime = getModTime(input);
|
||||
if (intime == INT64_MIN)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
biomenames[i][j] = outputdir + "/biome/" + formatTileName(x + i - 1, z + j - 1, "png");
|
||||
intime = std::max(intime, getModTime(biomenames[i][j]));
|
||||
}
|
||||
}
|
||||
|
||||
if (!checkRegion(intime, output))
|
||||
return;
|
||||
|
||||
std::printf("Generating %s from %s... ", output.c_str(), input.c_str());
|
||||
std::fflush(stdout);
|
||||
|
||||
try {
|
||||
std::unique_ptr<uint8_t[]> biomemaps[3][3];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
biomemaps[i][j].reset(new uint8_t[DIM*DIM]);
|
||||
std::memset(biomemaps[i][j].get(), 0, DIM*DIM);
|
||||
|
||||
try {
|
||||
PNG::read(biomenames[i][j].c_str(), biomemaps[i][j].get(), DIM, DIM, PNG::GRAY);
|
||||
} catch (const std::exception& ex) {}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Resource::Color[]> image(new Resource::Color[DIM*DIM]);
|
||||
|
||||
std::unique_ptr<uint8_t[]> lightmap(new uint8_t[2*DIM*DIM]);
|
||||
std::memset(lightmap.get(), 0, 2*DIM*DIM);
|
||||
|
||||
World::Region::visitChunks(input.c_str(), [&] (chunk_idx_t X, chunk_idx_t Z, const World::ChunkData *chunk) {
|
||||
addChunk(image.get(), lightmap.get(), X, Z, chunk, biomemaps);
|
||||
});
|
||||
|
||||
bool changed = writeImage(output, reinterpret_cast<const uint8_t*>(image.get()), PNG::RGB_ALPHA, intime);
|
||||
changed = writeImage(output_light, lightmap.get(), PNG::GRAY_ALPHA, intime) || changed;
|
||||
std::printf("%s.\n", changed ? "done" : "unchanged");
|
||||
} catch (const std::exception& ex) {
|
||||
std::fprintf(stderr, "Failed to generate %s: %s\n", output.c_str(), ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
static void makeMaps(const std::string ®iondir, const std::string &outputdir, const Info *info) {
|
||||
info->visitRegions(0, [&] (int x, int z) {
|
||||
makeMap(regiondir, outputdir, x, z);
|
||||
});
|
||||
}
|
||||
|
||||
static bool makeMipmap(const std::string &dir, size_t level, int x, int z, PNG::Format imageFormat) {
|
||||
bool ret = false;
|
||||
|
||||
std::string indir = dir + "/" + format(level-1) + "/";
|
||||
std::string outdir = dir + "/" + format(level) + "/";
|
||||
|
||||
const std::string nw_str = indir + formatTileName(x*2, z*2, "png");
|
||||
const std::string ne_str = indir + formatTileName(x*2+1, z*2, "png");
|
||||
const std::string sw_str = indir + formatTileName(x*2, z*2+1, "png");
|
||||
const std::string se_str = indir + formatTileName(x*2+1, z*2+1, "png");
|
||||
|
||||
const char *nw = nw_str.c_str();
|
||||
const char *ne = ne_str.c_str();
|
||||
const char *sw = sw_str.c_str();
|
||||
const char *se = se_str.c_str();
|
||||
|
||||
int64_t t = INT64_MIN;
|
||||
unsigned count = 0;
|
||||
for (auto name : {&nw, &ne, &sw, &se}) {
|
||||
struct stat s;
|
||||
if (stat(*name, &s) < 0) {
|
||||
*name = nullptr;
|
||||
continue;
|
||||
}
|
||||
|
||||
int64_t t_part = readStamp(*name);
|
||||
if (t_part > t)
|
||||
t = t_part;
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
std::string output = outdir + formatTileName(x, z, "png");
|
||||
|
||||
{
|
||||
struct stat s;
|
||||
if (stat(output.c_str(), &s) == 0) {
|
||||
ret = true;
|
||||
|
||||
int64_t outtime = readStamp(output);
|
||||
if (t <= outtime)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (!count)
|
||||
return ret;
|
||||
|
||||
const std::string tmpfile = output + ".tmp";
|
||||
|
||||
try {
|
||||
PNG::mipmap(tmpfile.c_str(), DIM, DIM, imageFormat, nw, ne, sw, se);
|
||||
|
||||
if (std::rename(tmpfile.c_str(), output.c_str()) < 0) {
|
||||
std::fprintf(stderr, "Unable to save %s: %s\n", output.c_str(), std::strerror(errno));
|
||||
std::remove(tmpfile.c_str());
|
||||
}
|
||||
|
||||
writeStamp(output, t);
|
||||
} catch (const std::exception& ex) {
|
||||
std::remove(tmpfile.c_str());
|
||||
throw;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int floored_half(int a) {
|
||||
return (a - (a < 0)) / 2;
|
||||
}
|
||||
|
||||
static void makeMipmaps(const std::string &dir, Info *info) {
|
||||
for (size_t level = 0; ; level++) {
|
||||
int minX, maxX, minZ, maxZ;
|
||||
std::tie(minX, maxX, minZ, maxZ) = info->getBounds(level);
|
||||
|
||||
if (minX >= -1 && maxX <= 0 && minZ >= -1 && maxZ <= 0)
|
||||
break;
|
||||
|
||||
info->addMipmapLevel();
|
||||
makeDir(dir + "/map/" + format(level + 1));
|
||||
makeDir(dir + "/light/" + format(level + 1));
|
||||
|
||||
info->visitRegions(level, [&] (int x, int z) {
|
||||
info->addRegion(floored_half(x), floored_half(z), level + 1);
|
||||
});
|
||||
|
||||
info->visitRegions(level + 1, [&] (int x, int z) {
|
||||
if (makeMipmap(dir + "/map", level + 1, x, z, PNG::RGB_ALPHA))
|
||||
info->addRegion(x, z, level + 1);
|
||||
|
||||
makeMipmap(dir + "/light", level + 1, x, z, PNG::GRAY_ALPHA);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static Info collectInfo(const std::string ®iondir) {
|
||||
DIR *dir = opendir(regiondir.c_str());
|
||||
if (!dir)
|
||||
throw std::system_error(errno, std::generic_category(), "Unable to read input directory");
|
||||
|
||||
Info info;
|
||||
|
||||
struct dirent *entry;
|
||||
while ((entry = readdir(dir)) != nullptr) {
|
||||
int x, z;
|
||||
if (!checkFilename(entry->d_name, &x, &z))
|
||||
continue;
|
||||
|
||||
info.addRegion(x, z, 0);
|
||||
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
static void doLevel(const std::string &inputdir, const std::string &outputdir) {
|
||||
const std::string regiondir = inputdir + "/region";
|
||||
|
||||
makeDir(outputdir + "/biome");
|
||||
makeDir(outputdir + "/map");
|
||||
makeDir(outputdir + "/map/0");
|
||||
makeDir(outputdir + "/light");
|
||||
makeDir(outputdir + "/light/0");
|
||||
|
||||
Info info = collectInfo(regiondir);
|
||||
|
||||
std::printf("Updating biome data...\n");
|
||||
makeBiomes(regiondir, outputdir, &info);
|
||||
|
||||
std::printf("Updating map data...\n");
|
||||
makeMaps(regiondir, outputdir, &info);
|
||||
|
||||
World::Level level((inputdir + "/level.dat").c_str());
|
||||
info.setSpawn(level.getSpawn());
|
||||
|
||||
std::printf("Updating mipmaps...\n");
|
||||
makeMipmaps(outputdir, &info);
|
||||
|
||||
info.writeJSON((outputdir + "/info.json").c_str());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc < 3) {
|
||||
std::fprintf(stderr, "Usage: %s <data directory> <output directory>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
MinedMap::doLevel(argv[1], argv[2]);
|
||||
} catch (const std::runtime_error& ex) {
|
||||
std::fprintf(stderr, "Error: %s\n", ex.what());
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tag.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace NBT {
|
||||
|
||||
class ByteArrayTag : public Tag {
|
||||
private:
|
||||
uint32_t len;
|
||||
const uint8_t *ptr;
|
||||
|
||||
public:
|
||||
static const MakeType<ByteArrayTag> Type;
|
||||
|
||||
|
||||
ByteArrayTag(Buffer *buffer) {
|
||||
len = buffer->get32();
|
||||
ptr = buffer->get(len);
|
||||
}
|
||||
|
||||
virtual const TagType & getType() const {
|
||||
return Type;
|
||||
}
|
||||
|
||||
virtual void print(std::ostream& os, const std::string &indent) const {
|
||||
os << "(" << len << ") [" << std::endl;
|
||||
|
||||
std::string inner = indent + " ";
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
uint8_t v = ptr[i];
|
||||
|
||||
os << inner
|
||||
<< (unsigned)v << " / "
|
||||
<< (int)(int8_t)v << " / "
|
||||
<< std::hex << "0x" << (unsigned)v << std::dec
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
os << indent << "]";
|
||||
}
|
||||
|
||||
uint32_t getLength() const {
|
||||
return len;
|
||||
}
|
||||
|
||||
const uint8_t * getPointer() const {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
uint8_t getValue(size_t i) const {
|
||||
return ptr[i];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tag.hpp"
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace NBT {
|
||||
|
||||
class ByteTag : public Tag {
|
||||
private:
|
||||
uint8_t value;
|
||||
|
||||
public:
|
||||
static const MakeType<ByteTag> Type;
|
||||
|
||||
|
||||
ByteTag(Buffer *buffer) {
|
||||
value = buffer->get8();
|
||||
}
|
||||
|
||||
virtual const TagType & getType() const {
|
||||
return Type;
|
||||
}
|
||||
|
||||
virtual void print(std::ostream& os, const std::string &) const {
|
||||
os << (unsigned)getValue() << " / "
|
||||
<< (int)(int8_t)getValue() << " / "
|
||||
<< std::hex << "0x" << (unsigned)getValue() << std::dec;
|
||||
}
|
||||
|
||||
uint8_t getValue() const {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "EndTag.hpp"
|
||||
#include "Tag.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace NBT {
|
||||
|
||||
class CompoundTag : public Tag, public std::unordered_map<std::string, std::shared_ptr<const Tag>> {
|
||||
public:
|
||||
static const MakeType<CompoundTag> Type;
|
||||
|
||||
|
||||
CompoundTag(Buffer *buffer) {
|
||||
while (true) {
|
||||
std::pair<std::string, std::shared_ptr<const Tag>> v = Tag::readNamedTag(buffer);
|
||||
if (v.second->getType() == EndTag::Type)
|
||||
break;
|
||||
|
||||
insert(std::move(v));
|
||||
}
|
||||
}
|
||||
|
||||
virtual const TagType & getType() const {
|
||||
return Type;
|
||||
}
|
||||
|
||||
virtual void print(std::ostream& os, const std::string &indent) const {
|
||||
os << "{" << std::endl;
|
||||
|
||||
std::string inner = indent + " ";
|
||||
|
||||
for (const auto &item : *this) {
|
||||
os << inner << item.first << ": " << item.second->getType() << " ";
|
||||
item.second->print(os, inner);
|
||||
os << std::endl;
|
||||
}
|
||||
|
||||
os << indent << "}";
|
||||
}
|
||||
|
||||
template<typename T> std::shared_ptr<const T> get(const std::string &key) const {
|
||||
auto it = find(key);
|
||||
if (it == end())
|
||||
return std::shared_ptr<const T>();
|
||||
|
||||
return std::dynamic_pointer_cast<const T>(it->second);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tag.hpp"
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace NBT {
|
||||
|
||||
class DoubleTag : public Tag {
|
||||
private:
|
||||
const uint8_t *ptr;
|
||||
|
||||
public:
|
||||
static const MakeType<DoubleTag> Type;
|
||||
|
||||
|
||||
DoubleTag(Buffer *buffer) {
|
||||
ptr = buffer->get(8);
|
||||
}
|
||||
|
||||
virtual const TagType & getType() const {
|
||||
return Type;
|
||||
}
|
||||
|
||||
virtual void print(std::ostream& os, const std::string &) const {
|
||||
union {
|
||||
uint64_t i;
|
||||
double d;
|
||||
};
|
||||
|
||||
i = Buffer::parse64(ptr);
|
||||
os << d;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tag.hpp"
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace NBT {
|
||||
|
||||
class EndTag : public Tag {
|
||||
public:
|
||||
static const MakeType<EndTag> Type;
|
||||
|
||||
|
||||
EndTag(Buffer *) {}
|
||||
|
||||
virtual const TagType & getType() const {
|
||||
return Type;
|
||||
}
|
||||
|
||||
virtual void print(std::ostream&, const std::string &) const {
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tag.hpp"
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace NBT {
|
||||
|
||||
class FloatTag : public Tag {
|
||||
const uint8_t *ptr;
|
||||
|
||||
public:
|
||||
static const MakeType<FloatTag> Type;
|
||||
|
||||
|
||||
FloatTag(Buffer *buffer) {
|
||||
ptr = buffer->get(4);
|
||||
}
|
||||
|
||||
virtual const TagType & getType() const {
|
||||
return Type;
|
||||
}
|
||||
|
||||
virtual void print(std::ostream& os, const std::string &) const {
|
||||
union {
|
||||
uint32_t i;
|
||||
float f;
|
||||
};
|
||||
|
||||
i = Buffer::parse32(ptr);
|
||||
os << f;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tag.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace NBT {
|
||||
|
||||
class IntArrayTag : public Tag {
|
||||
private:
|
||||
uint32_t len;
|
||||
const uint8_t *ptr;
|
||||
|
||||
public:
|
||||
static const MakeType<IntArrayTag> Type;
|
||||
|
||||
|
||||
IntArrayTag(Buffer *buffer) {
|
||||
len = buffer->get32();
|
||||
ptr = buffer->get(4*len);
|
||||
}
|
||||
|
||||
virtual const TagType & getType() const {
|
||||
return Type;
|
||||
}
|
||||
|
||||
virtual void print(std::ostream& os, const std::string &indent) const {
|
||||
os << "(" << len << ") [" << std::endl;
|
||||
|
||||
std::string inner = indent + " ";
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
uint32_t v = getValue(i);
|
||||
|
||||
os << inner
|
||||
<< v << " / "
|
||||
<< (int32_t)v << " / "
|
||||
<< std::hex << "0x" << v << std::dec
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
os << indent << "]";
|
||||
}
|
||||
|
||||
uint32_t getLength() const {
|
||||
return len;
|
||||
}
|
||||
|
||||
const uint8_t * getPointer() const {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
uint32_t getValue(size_t i) const {
|
||||
return Buffer::parse32(&ptr[4*i]);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tag.hpp"
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace NBT {
|
||||
|
||||
class IntTag : public Tag {
|
||||
private:
|
||||
const uint8_t *ptr;
|
||||
|
||||
public:
|
||||
static const MakeType<IntTag> Type;
|
||||
|
||||
|
||||
IntTag(Buffer *buffer) {
|
||||
ptr = buffer->get(4);
|
||||
}
|
||||
|
||||
virtual const TagType & getType() const {
|
||||
return Type;
|
||||
}
|
||||
|
||||
virtual void print(std::ostream& os, const std::string &) const {
|
||||
os << getValue() << " / "
|
||||
<< (int32_t)getValue() << " / "
|
||||
<< std::hex << "0x" << getValue() << std::dec;
|
||||
}
|
||||
|
||||
uint32_t getValue() const {
|
||||
return Buffer::parse32(ptr);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tag.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace NBT {
|
||||
|
||||
class ListTag : public Tag, public std::vector<std::shared_ptr<const Tag>> {
|
||||
private:
|
||||
const TagType *subtype;
|
||||
|
||||
public:
|
||||
static const MakeType<ListTag> Type;
|
||||
|
||||
|
||||
ListTag(Buffer *buffer) {
|
||||
subtype = &getTypeById(buffer->get8());
|
||||
|
||||
uint32_t len = buffer->get32();
|
||||
|
||||
for (uint32_t i = 0; i < len; i++)
|
||||
push_back(subtype->read(buffer));
|
||||
}
|
||||
|
||||
virtual const TagType & getType() const {
|
||||
return Type;
|
||||
}
|
||||
|
||||
virtual const TagType & getSubtype() const {
|
||||
return *subtype;
|
||||
}
|
||||
|
||||
virtual void print(std::ostream& os, const std::string &indent) const {
|
||||
os << getSubtype() << " [" << std::endl;
|
||||
|
||||
std::string inner = indent + " ";
|
||||
|
||||
for (const auto &item : *this) {
|
||||
os << inner;
|
||||
item->print(os, inner);
|
||||
os << std::endl;
|
||||
}
|
||||
|
||||
os << indent << "]";
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015-2018, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tag.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace NBT {
|
||||
|
||||
class LongArrayTag : public Tag {
|
||||
private:
|
||||
uint32_t len;
|
||||
const uint8_t *ptr;
|
||||
|
||||
public:
|
||||
static const MakeType<LongArrayTag> Type;
|
||||
|
||||
|
||||
LongArrayTag(Buffer *buffer) {
|
||||
len = buffer->get32();
|
||||
ptr = buffer->get(8*len);
|
||||
}
|
||||
|
||||
virtual const TagType & getType() const {
|
||||
return Type;
|
||||
}
|
||||
|
||||
virtual void print(std::ostream& os, const std::string &indent) const {
|
||||
os << "(" << len << ") [" << std::endl;
|
||||
|
||||
std::string inner = indent + " ";
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
uint64_t v = Buffer::parse64(&ptr[8*i]);
|
||||
|
||||
os << inner
|
||||
<< v << " / "
|
||||
<< (int64_t)v << " / "
|
||||
<< std::hex << "0x" << v << std::dec
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
os << indent << "]";
|
||||
}
|
||||
|
||||
uint32_t getLength() const {
|
||||
return len;
|
||||
}
|
||||
|
||||
const uint8_t * getPointer() const {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
uint64_t getValue(size_t i) const {
|
||||
return Buffer::parse64(&ptr[8*i]);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tag.hpp"
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace NBT {
|
||||
|
||||
class LongTag : public Tag {
|
||||
private:
|
||||
const uint8_t *ptr;
|
||||
|
||||
public:
|
||||
static const MakeType<LongTag> Type;
|
||||
|
||||
|
||||
LongTag(Buffer *buffer) {
|
||||
ptr = buffer->get(8);
|
||||
}
|
||||
|
||||
virtual const TagType & getType() const {
|
||||
return Type;
|
||||
}
|
||||
|
||||
virtual void print(std::ostream& os, const std::string &) const {
|
||||
os << getValue() << " / "
|
||||
<< (int64_t)getValue() << " / "
|
||||
<< std::hex << "0x" << getValue() << std::dec;
|
||||
}
|
||||
|
||||
uint64_t getValue() const {
|
||||
return Buffer::parse64(ptr);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tag.hpp"
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace NBT {
|
||||
|
||||
class ShortTag : public Tag {
|
||||
private:
|
||||
const uint8_t *ptr;
|
||||
|
||||
public:
|
||||
static const MakeType<ShortTag> Type;
|
||||
|
||||
|
||||
ShortTag(Buffer *buffer) {
|
||||
ptr = buffer->get(2);
|
||||
}
|
||||
|
||||
virtual const TagType & getType() const {
|
||||
return Type;
|
||||
}
|
||||
|
||||
virtual void print(std::ostream& os, const std::string &) const {
|
||||
os << getValue() << " / "
|
||||
<< (int16_t)getValue() << " / "
|
||||
<< std::hex << "0x" << getValue() << std::dec;
|
||||
}
|
||||
|
||||
uint16_t getValue() const {
|
||||
return Buffer::parse16(ptr);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tag.hpp"
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace NBT {
|
||||
|
||||
class StringTag : public Tag {
|
||||
private:
|
||||
uint16_t len;
|
||||
const uint8_t *ptr;
|
||||
|
||||
public:
|
||||
static const MakeType<StringTag> Type;
|
||||
|
||||
|
||||
StringTag(Buffer *buffer) {
|
||||
len = buffer->get16();
|
||||
ptr = buffer->get(len);
|
||||
}
|
||||
|
||||
virtual const TagType & getType() const {
|
||||
return Type;
|
||||
}
|
||||
|
||||
std::string getValue() const {
|
||||
return std::string(reinterpret_cast<const char *>(ptr), len);
|
||||
}
|
||||
|
||||
virtual void print(std::ostream& os, const std::string &) const {
|
||||
os << "\"" << getValue() << "\"";
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#include "Tag.hpp"
|
||||
|
||||
#include "EndTag.hpp"
|
||||
#include "ByteTag.hpp"
|
||||
#include "ShortTag.hpp"
|
||||
#include "IntTag.hpp"
|
||||
#include "LongTag.hpp"
|
||||
#include "FloatTag.hpp"
|
||||
#include "DoubleTag.hpp"
|
||||
#include "ByteArrayTag.hpp"
|
||||
#include "StringTag.hpp"
|
||||
#include "ListTag.hpp"
|
||||
#include "CompoundTag.hpp"
|
||||
#include "IntArrayTag.hpp"
|
||||
#include "LongArrayTag.hpp"
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace NBT {
|
||||
|
||||
const Tag::MakeType<EndTag> EndTag::Type("End");
|
||||
const Tag::MakeType<ByteTag> ByteTag::Type("Byte");
|
||||
const Tag::MakeType<ShortTag> ShortTag::Type("Short");
|
||||
const Tag::MakeType<IntTag> IntTag::Type("Int");
|
||||
const Tag::MakeType<LongTag> LongTag::Type("Long");
|
||||
const Tag::MakeType<FloatTag> FloatTag::Type("Float");
|
||||
const Tag::MakeType<DoubleTag> DoubleTag::Type("Double");
|
||||
const Tag::MakeType<ByteArrayTag> ByteArrayTag::Type("ByteArray");
|
||||
const Tag::MakeType<StringTag> StringTag::Type("String");
|
||||
const Tag::MakeType<ListTag> ListTag::Type("List");
|
||||
const Tag::MakeType<CompoundTag> CompoundTag::Type("Compound");
|
||||
const Tag::MakeType<IntArrayTag> IntArrayTag::Type("IntArray");
|
||||
const Tag::MakeType<LongArrayTag> LongArrayTag::Type("LongArray");
|
||||
|
||||
|
||||
const std::vector<const TagType *> Tag::types = {
|
||||
&EndTag::Type,
|
||||
&ByteTag::Type,
|
||||
&ShortTag::Type,
|
||||
&IntTag::Type,
|
||||
&LongTag::Type,
|
||||
&FloatTag::Type,
|
||||
&DoubleTag::Type,
|
||||
&ByteArrayTag::Type,
|
||||
&StringTag::Type,
|
||||
&ListTag::Type,
|
||||
&CompoundTag::Type,
|
||||
&IntArrayTag::Type,
|
||||
&LongArrayTag::Type,
|
||||
};
|
||||
|
||||
|
||||
std::pair<std::string, std::shared_ptr<const Tag>> Tag::readNamedTag(Buffer *buffer) {
|
||||
const TagType &type = getTypeById(buffer->get8());
|
||||
if (type == EndTag::Type)
|
||||
return std::make_pair("", std::make_shared<EndTag>(buffer));
|
||||
|
||||
uint16_t len = buffer->get16();
|
||||
std::string name(reinterpret_cast<const char*>(buffer->get(len)), len);
|
||||
|
||||
return std::make_pair(name, type.read(buffer));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <vector>
|
||||
|
||||
#include "../Buffer.hpp"
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace NBT {
|
||||
|
||||
class Tag;
|
||||
|
||||
class TagType {
|
||||
public:
|
||||
TagType() = default;
|
||||
TagType(const TagType&) = delete;
|
||||
TagType & operator=(const TagType&) = delete;
|
||||
|
||||
virtual const char * getName() const = 0;
|
||||
virtual std::shared_ptr<const Tag> read(Buffer *buffer) const = 0;
|
||||
|
||||
bool operator==(const TagType &type) const {
|
||||
return this == &type;
|
||||
}
|
||||
};
|
||||
|
||||
class Tag {
|
||||
private:
|
||||
static const std::vector<const TagType *> types;
|
||||
|
||||
protected:
|
||||
template<typename T>
|
||||
class MakeType : public TagType {
|
||||
private:
|
||||
const char *name;
|
||||
|
||||
public:
|
||||
MakeType(const char *name0) : name(name0) {}
|
||||
|
||||
virtual const char * getName() const {
|
||||
return name;
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<const Tag> read(Buffer *buffer) const {
|
||||
return std::make_shared<T>(buffer);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
static const TagType & getTypeById(uint8_t id) {
|
||||
return *types.at(id);
|
||||
}
|
||||
|
||||
public:
|
||||
static std::pair<std::string, std::shared_ptr<const Tag>> readNamedTag(Buffer *buffer);
|
||||
|
||||
virtual const TagType & getType() const = 0;
|
||||
virtual void print(std::ostream& os, const std::string &indent) const = 0;
|
||||
|
||||
virtual ~Tag() {}
|
||||
};
|
||||
|
||||
static inline std::ostream& operator<<(std::ostream& os, const TagType &type) {
|
||||
return os << type.getName();
|
||||
}
|
||||
|
||||
static inline std::ostream& operator<<(std::ostream& os, const Tag &tag) {
|
||||
os << tag.getType() << " ";
|
||||
tag.print(os, "");
|
||||
return os;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
143
src/PNG.cpp
143
src/PNG.cpp
|
@ -1,143 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#include "PNG.hpp"
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <system_error>
|
||||
|
||||
#include <png.h>
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace PNG {
|
||||
|
||||
static const int formatColorTypes[] = {
|
||||
[RGB_ALPHA] = PNG_COLOR_TYPE_RGB_ALPHA,
|
||||
[GRAY_ALPHA] = PNG_COLOR_TYPE_GRAY_ALPHA,
|
||||
[GRAY] = PNG_COLOR_TYPE_GRAY,
|
||||
};
|
||||
|
||||
void write(const char *filename, const uint8_t *data, size_t width, size_t height, Format format) {
|
||||
std::FILE *f = std::fopen(filename, "wb");
|
||||
if (!f)
|
||||
throw std::system_error(errno, std::generic_category(), "unable to open PNG file");
|
||||
|
||||
png_structp png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
if (!png_ptr)
|
||||
throw std::runtime_error("unable to create PNG write struct");
|
||||
|
||||
png_infop info_ptr = png_create_info_struct(png_ptr);
|
||||
if (!info_ptr) {
|
||||
png_destroy_write_struct(&png_ptr, nullptr);
|
||||
throw std::runtime_error("unable to create PNG info struct");
|
||||
}
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr))) {
|
||||
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||
std::fclose(f);
|
||||
throw std::runtime_error("unable to write PNG file");
|
||||
}
|
||||
|
||||
png_init_io(png_ptr, f);
|
||||
|
||||
png_set_IHDR(png_ptr, info_ptr, width, height, 8, formatColorTypes[format],
|
||||
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
||||
|
||||
uint8_t *row_pointers[height];
|
||||
for (size_t i = 0; i < height; i++)
|
||||
row_pointers[i] = const_cast<uint8_t*>(&data[formatBytes(format)*i*width]);
|
||||
|
||||
png_set_rows(png_ptr, info_ptr, row_pointers);
|
||||
png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, nullptr);
|
||||
|
||||
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||
std::fclose(f);
|
||||
}
|
||||
|
||||
void read(const char *filename, uint8_t *data, size_t width, size_t height, Format format) {
|
||||
std::FILE *f = std::fopen(filename, "rb");
|
||||
if (!f)
|
||||
throw std::system_error(errno, std::generic_category(), "unable to open PNG file");
|
||||
|
||||
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
if (!png_ptr)
|
||||
throw std::runtime_error("unable to create PNG read struct");
|
||||
|
||||
png_infop info_ptr = png_create_info_struct(png_ptr);
|
||||
if (!info_ptr) {
|
||||
png_destroy_read_struct(&png_ptr, nullptr, nullptr);
|
||||
throw std::runtime_error("unable to create PNG info struct");
|
||||
}
|
||||
|
||||
png_infop end_info = png_create_info_struct(png_ptr);
|
||||
if (!end_info) {
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
|
||||
throw std::runtime_error("unable to create PNG info struct");
|
||||
}
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr))) {
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
|
||||
fclose(f);
|
||||
throw std::runtime_error("unable to read PNG file");
|
||||
}
|
||||
|
||||
png_init_io(png_ptr, f);
|
||||
|
||||
png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, nullptr);
|
||||
|
||||
if (png_get_image_width(png_ptr, info_ptr) != width
|
||||
|| png_get_image_height(png_ptr, info_ptr) != height
|
||||
|| png_get_bit_depth(png_ptr, info_ptr) != 8
|
||||
|| png_get_color_type(png_ptr, info_ptr) != formatColorTypes[format])
|
||||
longjmp(png_jmpbuf(png_ptr), 1);
|
||||
|
||||
uint8_t **row_pointers = png_get_rows(png_ptr, info_ptr);
|
||||
for (size_t i = 0; i < height; i++)
|
||||
std::memcpy(&data[formatBytes(format)*i*width], row_pointers[i], formatBytes(format)*width);
|
||||
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
|
||||
std::fclose(f);
|
||||
}
|
||||
|
||||
static void readScaled(uint8_t *data, size_t offset_w, size_t offset_h, const char *file, size_t width, size_t height, Format format) {
|
||||
if (!file)
|
||||
return;
|
||||
|
||||
size_t b = formatBytes(format);
|
||||
|
||||
std::unique_ptr<uint8_t[]> input(new uint8_t[b*width*height]);
|
||||
read(file, input.get(), width, height, format);
|
||||
|
||||
for (size_t h = 0; h < width/2; h++) {
|
||||
for (size_t w = 0; w < width/2; w++) {
|
||||
for (size_t c = 0; c < b; c++) {
|
||||
size_t i = 2*b*(width*h + w) + c;
|
||||
data[b*(width*(offset_h+h) + offset_w+w) + c] = (input[i] + input[i+b] + input[i+b*width] + input[i+b*width+b])/4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mipmap(const char *output, size_t width, size_t height, Format format, const char *nw, const char *ne, const char *sw, const char *se) {
|
||||
size_t size = formatBytes(format)*width*height;
|
||||
std::unique_ptr<uint8_t[]> data(new uint8_t[size]);
|
||||
std::memset(data.get(), 0, size);
|
||||
|
||||
readScaled(data.get(), 0, 0, nw, width, height, format);
|
||||
readScaled(data.get(), width/2, 0, ne, width, height, format);
|
||||
readScaled(data.get(), 0, height/2, sw, width, height, format);
|
||||
readScaled(data.get(), width/2, height/2, se, width, height, format);
|
||||
|
||||
write(output, data.get(), width, height, format);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
38
src/PNG.hpp
38
src/PNG.hpp
|
@ -1,38 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace PNG {
|
||||
|
||||
enum Format {
|
||||
RGB_ALPHA,
|
||||
GRAY_ALPHA,
|
||||
GRAY,
|
||||
};
|
||||
|
||||
static inline size_t formatBytes(Format format) {
|
||||
const size_t data[] = {
|
||||
[RGB_ALPHA] = 4,
|
||||
[GRAY_ALPHA] = 2,
|
||||
[GRAY] = 1,
|
||||
};
|
||||
|
||||
return data[format];
|
||||
}
|
||||
|
||||
void write(const char *filename, const uint8_t *data, size_t width, size_t height, Format format);
|
||||
void read(const char *filename, uint8_t *data, size_t width, size_t height, Format format);
|
||||
void mipmap(const char *output, size_t width, size_t height, Format format, const char *nw, const char *ne, const char *sw, const char *se);
|
||||
|
||||
}
|
||||
}
|
|
@ -1,444 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015-2021, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
Copyright (c) 2019, Roman Shishkin <spark@uwtech.org>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#include "Biome.hpp"
|
||||
|
||||
#include "BlockType.hpp"
|
||||
|
||||
namespace MinedMap {
|
||||
namespace Resource {
|
||||
|
||||
static FloatColor colorFromParams(float temp, float rain, bool grass) {
|
||||
const FloatColor grassColors[3] = {
|
||||
{0.502f, 0.706f, 0.592f}, // lower right
|
||||
{0.247f, 0.012f, -0.259f}, // lower left - lower right
|
||||
{-0.471f, 0.086f, -0.133f}, // upper left - lower left
|
||||
};
|
||||
const FloatColor foliageColors[3] = {
|
||||
{0.376f, 0.631f, 0.482f}, // lower right
|
||||
{0.306f, 0.012f, -0.317f}, // lower left - lower right
|
||||
{-0.580f, 0.106f, -0.165f}, // upper left - lower left
|
||||
};
|
||||
|
||||
const FloatColor *colors = grass ? grassColors : foliageColors;
|
||||
|
||||
return colors[0] + temp*colors[1] + rain*colors[2];
|
||||
}
|
||||
|
||||
|
||||
FloatColor Biome::getGrassColor(float temp, float rain) const {
|
||||
return colorFromParams(temp, rain, true);
|
||||
}
|
||||
|
||||
FloatColor Biome::getFoliageColor(float temp, float rain) const {
|
||||
return colorFromParams(temp, rain, false);
|
||||
}
|
||||
|
||||
|
||||
FloatColor Biome::getBlockColor(const BlockType *type, y_idx_t height) const {
|
||||
FloatColor c = {
|
||||
float(type->color.r),
|
||||
float(type->color.g),
|
||||
float(type->color.b),
|
||||
};
|
||||
|
||||
float t = clamp(temp - std::max(0.0f, (int(height)-64)/600.0f), 0, 1);
|
||||
float r = clamp(rain, 0, 1) * t;
|
||||
|
||||
if (type->flags & BLOCK_GRASS)
|
||||
c *= getGrassColor(t, r);
|
||||
if (type->flags & BLOCK_FOLIAGE)
|
||||
c *= getFoliageColor(t, r);
|
||||
if (type->flags & BLOCK_BIRCH)
|
||||
c *= FloatColor {0.380f, 0.600f, 0.380f};
|
||||
if (type->flags & BLOCK_SPRUCE)
|
||||
c *= FloatColor {0.502f, 0.655f, 0.333f};
|
||||
if (type->flags & BLOCK_WATER)
|
||||
c *= getWaterColor();
|
||||
|
||||
float h = 0.5f + height * 0.005f;
|
||||
|
||||
c.r = clamp(c.r * h, 0, 255);
|
||||
c.g = clamp(c.g * h, 0, 255);
|
||||
c.b = clamp(c.b * h, 0, 255);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
class SwampBiome : public Biome {
|
||||
protected:
|
||||
virtual FloatColor getGrassColor(float, float) const {
|
||||
return {0.417f, 0.439f, 0.224f};
|
||||
}
|
||||
virtual FloatColor getFoliageColor(float temp, float rain) const {
|
||||
return getGrassColor(temp, rain);
|
||||
}
|
||||
|
||||
public:
|
||||
SwampBiome(float temp0, float rain0, FloatColor water0) :
|
||||
Biome(temp0, rain0, water0) {}
|
||||
};
|
||||
|
||||
class DarkForestBiome : public Biome {
|
||||
private:
|
||||
const FloatColor darkGreen = {0.157f, 0.204f, 0.039f};
|
||||
|
||||
protected:
|
||||
virtual FloatColor getGrassColor(float temp, float rain) const {
|
||||
return 0.5 * (darkGreen + colorFromParams(temp, rain, true));
|
||||
}
|
||||
|
||||
virtual FloatColor getFoliageColor(float temp, float rain) const {
|
||||
return 0.5 * (darkGreen + colorFromParams(temp, rain, false));
|
||||
}
|
||||
|
||||
public:
|
||||
DarkForestBiome(float temp0, float rain0) : Biome(temp0, rain0) {}
|
||||
};
|
||||
|
||||
class BadlandsBiome : public Biome {
|
||||
protected:
|
||||
virtual FloatColor getGrassColor(float, float) const {
|
||||
return {0.565f, 0.506f, 0.302f};
|
||||
}
|
||||
virtual FloatColor getFoliageColor(float, float) const {
|
||||
return {0.620f, 0.506f, 0.302f};
|
||||
}
|
||||
|
||||
public:
|
||||
BadlandsBiome(float temp0, float rain0) : Biome(temp0, rain0) {}
|
||||
};
|
||||
|
||||
|
||||
/* Values from https://github.com/erich666/Mineways/blob/master/Win/biomes.cpp */
|
||||
|
||||
static const Biome BiomeDefault(0.5f, 0.5f);
|
||||
static const Biome BiomePlains(0.8f, 0.4f);
|
||||
static const Biome BiomeDesert(2.0f, 0.0f);
|
||||
static const Biome BiomeMountains(0.2f, 0.3f);
|
||||
static const Biome BiomeForest(0.7f, 0.8f);
|
||||
static const Biome BiomeTaiga(0.25f, 0.8f);
|
||||
static const SwampBiome BiomeSwamp(0.8f, 0.9f, {0.380f, 0.482f, 0.392f});
|
||||
static const Biome BiomeFrozen(0.0f, 0.5f);
|
||||
static const Biome BiomeMushroomFields(0.9f, 1.0f);
|
||||
static const Biome BiomeJungle(0.95f, 0.9f);
|
||||
static const Biome BiomeJungleEdge(0.95f, 0.8f);
|
||||
static const Biome BiomeSnowyBeach(0.05f, 0.3f);
|
||||
static const Biome BiomeBirchForest(0.6f, 0.6f);
|
||||
static const DarkForestBiome BiomeDarkForest(0.7f, 0.8f);
|
||||
static const Biome BiomeSnowyTaiga(-0.5f, 0.4f);
|
||||
static const Biome BiomeGiantTreeTaiga(0.3f, 0.8f);
|
||||
static const Biome BiomeSavanna(1.2f, 0.0f);
|
||||
static const Biome BiomeSavannaPlateau(1.0f, 0.0f);
|
||||
static const Biome BiomeShatteredSavanna(1.1f, 0.0f);
|
||||
static const BadlandsBiome BiomeBadlands(2.0f, 0.0f);
|
||||
|
||||
static const Biome BiomeFrozenOcean(0.0f, 0.5f, {0.224f, 0.220f, 0.788f});
|
||||
static const Biome BiomeWarmOcean(0.8f, 0.5f, {0.263f, 0.835f, 0.933f});
|
||||
static const Biome BiomeLukewarmOcean(0.8f, 0.5f, {0.271f, 0.678f, 0.949f});
|
||||
static const Biome BiomeColdOcean(0.8f, 0.5f, {0.239f, 0.341f, 0.839f});
|
||||
|
||||
static const Biome BiomeMeadow(0.5f, 0.8f);
|
||||
static const Biome BiomeGrove(-0.2f, 0.8f);
|
||||
static const Biome BiomeJaggedPeaks(-0.7f, 0.9f);
|
||||
static const Biome BiomeStonyPeaks(1.0f, 0.3f);
|
||||
static const Biome BiomeSnowySlopes(-0.3f, 0.9f);
|
||||
|
||||
const Biome *const Biome::Default = &BiomeDefault;
|
||||
|
||||
/* Minecraft 1.18 does not use numerical IDs for biomes anymore.
|
||||
* Previously unused biome IDs are assigned to the new biome types of
|
||||
* Minecraft 1.18 for storage in MinedMap's biome data cache. */
|
||||
|
||||
const Biome *const Biome::Biomes[256] = {
|
||||
/* 0 */ &BiomeDefault, /* Ocean */
|
||||
/* 1 */ &BiomePlains,
|
||||
/* 2 */ &BiomeDesert,
|
||||
/* 3 */ &BiomeMountains,
|
||||
/* 4 */ &BiomeForest,
|
||||
/* 5 */ &BiomeTaiga,
|
||||
/* 6 */ &BiomeSwamp,
|
||||
/* 7 */ &BiomeDefault, /* River */
|
||||
/* 8 */ &BiomeDesert, /* Nether */
|
||||
/* 9 */ &BiomeDefault, /* The End */
|
||||
/* 10 */ &BiomeFrozenOcean,
|
||||
/* 11 */ &BiomeFrozenOcean, /* Frozen River */
|
||||
/* 12 */ &BiomeFrozen, /* Snowy Tundra */
|
||||
/* 13 */ &BiomeFrozen, /* Snowy Mountains */
|
||||
/* 14 */ &BiomeMushroomFields,
|
||||
/* 15 */ &BiomeMushroomFields, /* Mushroom Field Shore */
|
||||
/* 16 */ &BiomePlains, /* Beach */
|
||||
/* 17 */ &BiomeDesert, /* Desert Hills */
|
||||
/* 18 */ &BiomeForest, /* Wooded Hiils */
|
||||
/* 19 */ &BiomeTaiga, /* Taiga Hills */
|
||||
/* 20 */ &BiomeMountains, /* Moutain Edge */
|
||||
/* 21 */ &BiomeJungle,
|
||||
/* 22 */ &BiomeJungle, /* Jungle Hills */
|
||||
/* 23 */ &BiomeJungleEdge,
|
||||
/* 24 */ &BiomeDefault, /* Deep Ocean */
|
||||
/* 25 */ &BiomeMountains, /* Stone Shore */
|
||||
/* 26 */ &BiomeSnowyBeach,
|
||||
/* 27 */ &BiomeBirchForest,
|
||||
/* 28 */ &BiomeBirchForest, /* Birch Forest Hills */
|
||||
/* 29 */ &BiomeDarkForest,
|
||||
/* 30 */ &BiomeSnowyTaiga,
|
||||
/* 31 */ &BiomeSnowyTaiga, /* Snowy Taiga Hills */
|
||||
/* 32 */ &BiomeGiantTreeTaiga,
|
||||
/* 33 */ &BiomeGiantTreeTaiga, /* Giant Tree Taiga Hills */
|
||||
/* 34 */ &BiomeMountains, /* Wooded Mountains */
|
||||
/* 35 */ &BiomeSavanna,
|
||||
/* 36 */ &BiomeSavanna, /* Savanna Plateau */
|
||||
/* 37 */ &BiomeBadlands,
|
||||
/* 38 */ &BiomeBadlands, /* Wooded Badlands Plateau */
|
||||
/* 39 */ &BiomeBadlands, /* Badlands Plateau */
|
||||
/* 40 */ &BiomeDefault, /* Small End Islands */
|
||||
/* 41 */ &BiomeDefault, /* End Midlands */
|
||||
/* 42 */ &BiomeDefault, /* End Highlands */
|
||||
/* 43 */ &BiomeDefault, /* End Barrens */
|
||||
/* 44 */ &BiomeWarmOcean,
|
||||
/* 45 */ &BiomeLukewarmOcean,
|
||||
/* 46 */ &BiomeColdOcean,
|
||||
/* 47 */ &BiomeWarmOcean, /* Deep Warm Ocean */
|
||||
/* 48 */ &BiomeLukewarmOcean, /* Deep Lukewarm Ocean */
|
||||
/* 49 */ &BiomeColdOcean, /* Deep Cold Ocean */
|
||||
/* 50 */ &BiomeFrozenOcean, /* Deep Frozen Ocean */
|
||||
/* 51 */ &BiomeMeadow, /* MinedMap assignment */
|
||||
/* 52 */ &BiomeGrove, /* MinedMap assignment */
|
||||
/* 53 */ &BiomeJaggedPeaks, /* MinedMap assignment */
|
||||
/* 54 */ &BiomeStonyPeaks, /* MinedMap assignment */
|
||||
/* 55 */ &BiomeSnowySlopes, /* MinedMap assignment */
|
||||
/* 56 */ nullptr,
|
||||
/* 57 */ nullptr,
|
||||
/* 58 */ nullptr,
|
||||
/* 59 */ nullptr,
|
||||
/* 60 */ nullptr,
|
||||
/* 61 */ nullptr,
|
||||
/* 62 */ nullptr,
|
||||
/* 63 */ nullptr,
|
||||
/* 64 */ nullptr,
|
||||
/* 65 */ nullptr,
|
||||
/* 66 */ nullptr,
|
||||
/* 67 */ nullptr,
|
||||
/* 68 */ nullptr,
|
||||
/* 69 */ nullptr,
|
||||
/* 70 */ nullptr,
|
||||
/* 71 */ nullptr,
|
||||
/* 72 */ nullptr,
|
||||
/* 73 */ nullptr,
|
||||
/* 74 */ nullptr,
|
||||
/* 75 */ nullptr,
|
||||
/* 76 */ nullptr,
|
||||
/* 77 */ nullptr,
|
||||
/* 78 */ nullptr,
|
||||
/* 79 */ nullptr,
|
||||
/* 80 */ nullptr,
|
||||
/* 81 */ nullptr,
|
||||
/* 82 */ nullptr,
|
||||
/* 83 */ nullptr,
|
||||
/* 84 */ nullptr,
|
||||
/* 85 */ nullptr,
|
||||
/* 86 */ nullptr,
|
||||
/* 87 */ nullptr,
|
||||
/* 88 */ nullptr,
|
||||
/* 89 */ nullptr,
|
||||
/* 90 */ nullptr,
|
||||
/* 91 */ nullptr,
|
||||
/* 92 */ nullptr,
|
||||
/* 93 */ nullptr,
|
||||
/* 94 */ nullptr,
|
||||
/* 95 */ nullptr,
|
||||
/* 96 */ nullptr,
|
||||
/* 97 */ nullptr,
|
||||
/* 98 */ nullptr,
|
||||
/* 99 */ nullptr,
|
||||
/* 100 */ nullptr,
|
||||
/* 101 */ nullptr,
|
||||
/* 102 */ nullptr,
|
||||
/* 103 */ nullptr,
|
||||
/* 104 */ nullptr,
|
||||
/* 105 */ nullptr,
|
||||
/* 106 */ nullptr,
|
||||
/* 107 */ nullptr,
|
||||
/* 108 */ nullptr,
|
||||
/* 109 */ nullptr,
|
||||
/* 110 */ nullptr,
|
||||
/* 111 */ nullptr,
|
||||
/* 112 */ nullptr,
|
||||
/* 113 */ nullptr,
|
||||
/* 114 */ nullptr,
|
||||
/* 115 */ nullptr,
|
||||
/* 116 */ nullptr,
|
||||
/* 117 */ nullptr,
|
||||
/* 118 */ nullptr,
|
||||
/* 119 */ nullptr,
|
||||
/* 120 */ nullptr,
|
||||
/* 121 */ nullptr,
|
||||
/* 122 */ nullptr,
|
||||
/* 123 */ nullptr,
|
||||
/* 124 */ nullptr,
|
||||
/* 125 */ nullptr,
|
||||
/* 126 */ nullptr,
|
||||
/* 127 */ &BiomeDefault, /* The Void */
|
||||
/* 128 */ nullptr,
|
||||
/* 129 */ &BiomeDefault, /* Sunflower Plains */
|
||||
/* 130 */ &BiomeDesert, /* Desert Lakes */
|
||||
/* 131 */ &BiomeMountains, /* Gravelly Mountains */
|
||||
/* 132 */ &BiomeForest, /* Flower Forest */
|
||||
/* 133 */ &BiomeTaiga, /* Taiga Mountains */
|
||||
/* 134 */ &BiomeSwamp, /* Swamp Hills */
|
||||
/* 135 */ nullptr,
|
||||
/* 136 */ nullptr,
|
||||
/* 137 */ nullptr,
|
||||
/* 138 */ nullptr,
|
||||
/* 139 */ nullptr,
|
||||
/* 140 */ &BiomeFrozen, /* Ice Spikes */
|
||||
/* 141 */ nullptr,
|
||||
/* 142 */ nullptr,
|
||||
/* 143 */ nullptr,
|
||||
/* 144 */ nullptr,
|
||||
/* 145 */ nullptr,
|
||||
/* 146 */ nullptr,
|
||||
/* 147 */ nullptr,
|
||||
/* 148 */ nullptr,
|
||||
/* 149 */ &BiomeJungle, /* Modified Jungle */
|
||||
/* 150 */ nullptr,
|
||||
/* 151 */ &BiomeJungleEdge, /* Modified Jungle Edge */
|
||||
/* 152 */ nullptr,
|
||||
/* 153 */ nullptr,
|
||||
/* 154 */ nullptr,
|
||||
/* 155 */ &BiomeBirchForest, /* Tall Birch Forest */
|
||||
/* 156 */ &BiomeBirchForest, /* Tall Birch Hills */
|
||||
/* 157 */ &BiomeDarkForest, /* Dark Forest Hills */
|
||||
/* 158 */ &BiomeSnowyTaiga, /* Snowy Taiga Mountains */
|
||||
/* 159 */ nullptr,
|
||||
/* 160 */ &BiomeTaiga, /* Giant Spruce Taiga */
|
||||
/* 161 */ &BiomeTaiga, /* Giant Spruce Taiga Hills */
|
||||
/* 162 */ &BiomeMountains, /* Gravelly Mountains+ */
|
||||
/* 163 */ &BiomeShatteredSavanna,
|
||||
/* 164 */ &BiomeSavannaPlateau, /* Shattered Savanna Plateau */
|
||||
/* 165 */ &BiomeBadlands, /* Eroded Badlands */
|
||||
/* 166 */ &BiomeBadlands, /* Modified Wooded Badlands Plateau */
|
||||
/* 167 */ &BiomeBadlands, /* Modified Badlands Plateau */
|
||||
/* 168 */ &BiomeJungle, /* Bamboo Jungle */
|
||||
/* 169 */ &BiomeJungle, /* Bamboo Jungle Hills */
|
||||
/* 170 */ &BiomeDesert, /* Soul Sand Valley */
|
||||
/* 171 */ &BiomeDesert, /* Crimson Forest */
|
||||
/* 172 */ &BiomeDesert, /* Warped Forest */
|
||||
/* 173 */ &BiomeDesert, /* Basalt Deltas */
|
||||
/* 174 */ &BiomePlains, /* Dripstone Caves */
|
||||
/* 175 */ &BiomeDefault, /* Lush Caves */
|
||||
};
|
||||
|
||||
/* It is unclear which of the renamed/merged biome IDs can appear in practice,
|
||||
* but it shouldn't hurt to support them anyways */
|
||||
|
||||
const std::unordered_map<std::string, uint8_t> Biome::Names = {
|
||||
{ "minecraft:badlands", 37 },
|
||||
{ "minecraft:badlands_plateau", 39 }, /* 1.18: Merged into badlands */
|
||||
{ "minecraft:bamboo_jungle", 168 },
|
||||
{ "minecraft:bamboo_jungle_hills", 169 }, /* 1.18: Merged into bamboo_jungle */
|
||||
{ "minecraft:basalt_deltas", 173 },
|
||||
{ "minecraft:beach", 16 },
|
||||
{ "minecraft:birch_forest", 27 },
|
||||
{ "minecraft:birch_forest_hills", 28 }, /* 1.18: Merged into birch_forest */
|
||||
{ "minecraft:cold_ocean", 46 },
|
||||
{ "minecraft:crimson_forest", 171 },
|
||||
{ "minecraft:dark_forest", 29 },
|
||||
{ "minecraft:dark_forest_hills", 157 }, /* 1.18: Merged into dark_forest */
|
||||
{ "minecraft:deep_cold_ocean", 49 },
|
||||
{ "minecraft:deep_frozen_ocean", 50 },
|
||||
{ "minecraft:deep_lukewarm_ocean", 48 },
|
||||
{ "minecraft:deep_ocean", 24 },
|
||||
{ "minecraft:deep_warm_ocean", 47 }, /* 1.18: Merged into warm_ocean */
|
||||
{ "minecraft:desert", 2 },
|
||||
{ "minecraft:desert_hills", 17 }, /* 1.18: Merged into desert */
|
||||
{ "minecraft:desert_lakes", 130 }, /* 1.18: Merged into desert */
|
||||
{ "minecraft:dripstone_caves", 174 },
|
||||
{ "minecraft:end_barrens", 43 },
|
||||
{ "minecraft:end_highlands", 42 },
|
||||
{ "minecraft:end_midlands", 41 },
|
||||
{ "minecraft:eroded_badlands", 165 },
|
||||
{ "minecraft:extreme_hills", 3 }, /* 1.18: Renamed to windswept_hills (after rename from mountains) */
|
||||
{ "minecraft:flower_forest", 132 },
|
||||
{ "minecraft:forest", 4 },
|
||||
{ "minecraft:frozen_ocean", 10 },
|
||||
{ "minecraft:frozen_peaks", 53 }, /* 1.18: New */
|
||||
{ "minecraft:frozen_river", 11 },
|
||||
{ "minecraft:giant_spruce_taiga", 160 }, /* 1.18: Renamed to old_growth_spruce_taiga */
|
||||
{ "minecraft:giant_spruce_taiga_hills", 161 }, /* 1.18: Merged into giant_spruce_taiga */
|
||||
{ "minecraft:giant_tree_taiga", 32 }, /* 1.18: Renamed to old_growth_pine_taiga */
|
||||
{ "minecraft:giant_tree_taiga_hills", 33 }, /* 1.18: Merged into giant_tree_taiga */
|
||||
{ "minecraft:gravelly_mountains", 131 }, /* 1.18: Renamed to windswept_gravelly_hills */
|
||||
{ "minecraft:grove", 52 }, /* 1.18: New */
|
||||
{ "minecraft:ice_spikes", 140 },
|
||||
{ "minecraft:jagged_peaks", 53 }, /* 1.18: New */
|
||||
{ "minecraft:jungle", 21 },
|
||||
{ "minecraft:jungle_edge", 23 }, /* 1.18: Renamed to sparse_jungle */
|
||||
{ "minecraft:jungle_hills", 22 }, /* 1.18: Merged into jungle */
|
||||
{ "minecraft:lukewarm_ocean", 45 },
|
||||
{ "minecraft:lush_caves", 175 },
|
||||
{ "minecraft:meadow", 51 }, /* 1.18: New */
|
||||
{ "minecraft:modified_badlands_plateau", 167 }, /* 1.18: Merged into badlands */
|
||||
{ "minecraft:modified_gravelly_mountains", 162 }, /* 1.18: Merged into gravelly_mountains */
|
||||
{ "minecraft:modified_jungle", 149 }, /* 1.18: Merged into jungle */
|
||||
{ "minecraft:modified_jungle_edge", 151 }, /* 1.18: Merged into jungle_edge */
|
||||
{ "minecraft:modified_wooded_badlands_plateau", 166 }, /* 1.18: Merged into wooded_badlands */
|
||||
{ "minecraft:mountain_edge", 20 }, /* 1.18: Merged into mountains */
|
||||
{ "minecraft:mountains", 3 }, /* 1.18: Renamed to windswept_hills */
|
||||
{ "minecraft:mushroom_field_shore", 15 }, /* 1.18: Merged into mushroom_fields */
|
||||
{ "minecraft:mushroom_fields", 14 },
|
||||
{ "minecraft:nether_wastes", 8 },
|
||||
{ "minecraft:ocean", 0 },
|
||||
{ "minecraft:old_growth_birch_forest", 155 },
|
||||
{ "minecraft:old_growth_pine_taiga", 32 },
|
||||
{ "minecraft:old_growth_spruce_taiga", 160 },
|
||||
{ "minecraft:plains", 1 },
|
||||
{ "minecraft:river", 7 },
|
||||
{ "minecraft:savanna", 35 },
|
||||
{ "minecraft:savanna_plateau", 36 },
|
||||
{ "minecraft:shattered_savanna", 163 },
|
||||
{ "minecraft:shattered_savanna_plateau", 164 }, /* 1.18: Merged into shattered_savanna */
|
||||
{ "minecraft:small_end_islands", 40 },
|
||||
{ "minecraft:snowy_beach", 26 },
|
||||
{ "minecraft:snowy_mountains", 13 }, /* 1.18: Merged into snowy_tundra */
|
||||
{ "minecraft:snowy_plains", 12 },
|
||||
{ "minecraft:snowy_slopes", 55 },
|
||||
{ "minecraft:snowy_taiga", 30 },
|
||||
{ "minecraft:snowy_taiga_hills", 31 }, /* 1.18: Merged into snowy_taiga */
|
||||
{ "minecraft:snowy_taiga_mountains", 158 }, /* 1.18: Merged into snowy_taiga */
|
||||
{ "minecraft:snowy_tundra", 12 },
|
||||
{ "minecraft:soul_sand_valley", 170 },
|
||||
{ "minecraft:sparse_jungle", 23 },
|
||||
{ "minecraft:stone_shore", 25 },
|
||||
{ "minecraft:stony_peaks", 54 }, /* 1.18: New */
|
||||
{ "minecraft:stony_shore", 25 },
|
||||
{ "minecraft:sunflower_plains", 129 },
|
||||
{ "minecraft:swamp", 6 },
|
||||
{ "minecraft:swamp_hills", 134 }, /* 1.18: Merged into swamp */
|
||||
{ "minecraft:taiga", 5 },
|
||||
{ "minecraft:taiga_hills", 19 }, /* 1.18: Merged into taiga */
|
||||
{ "minecraft:taiga_mountains", 133 }, /* 1.18: Merged into taiga */
|
||||
{ "minecraft:tall_birch_forest", 155 }, /* 1.18: Renamed to old_growth_birch_forest */
|
||||
{ "minecraft:tall_birch_hills", 156 }, /* 1.18: Merged into tall_birch_forest */
|
||||
{ "minecraft:the_end", 9 },
|
||||
{ "minecraft:the_void", 127 },
|
||||
{ "minecraft:warm_ocean", 44 },
|
||||
{ "minecraft:warped_forest", 172 },
|
||||
{ "minecraft:windswept_forest", 34 },
|
||||
{ "minecraft:windswept_gravelly_hills", 131 },
|
||||
{ "minecraft:windswept_hills", 3 },
|
||||
{ "minecraft:windswept_savanna", 163 },
|
||||
{ "minecraft:wooded_badlands", 38 },
|
||||
{ "minecraft:wooded_badlands_plateau", 38 }, /* 1.18: Renamed to wooded_badlands */
|
||||
{ "minecraft:wooded_hills", 18 }, /* 1.18: Merged into forest */
|
||||
{ "minecraft:wooded_mountains", 34 /* 1.18: Renamed to windswept_forest */},
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015-2021, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Color.hpp"
|
||||
#include "../Util.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace MinedMap {
|
||||
namespace Resource {
|
||||
|
||||
class BlockType;
|
||||
|
||||
class Biome {
|
||||
private:
|
||||
float temp, rain;
|
||||
FloatColor water;
|
||||
|
||||
protected:
|
||||
virtual FloatColor getGrassColor(float temp, float rain) const;
|
||||
virtual FloatColor getFoliageColor(float temp, float rain) const;
|
||||
|
||||
FloatColor getWaterColor() const { return water; };
|
||||
|
||||
public:
|
||||
static const Biome *const Default;
|
||||
static const Biome *const Biomes[256];
|
||||
static const std::unordered_map<std::string, uint8_t> Names;
|
||||
|
||||
Biome(float temp0, float rain0, FloatColor water0 = {0.247f, 0.463f, 0.894f})
|
||||
: temp(temp0), rain(rain0), water(water0) {}
|
||||
|
||||
FloatColor getBlockColor(const BlockType *type, y_idx_t height) const;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015-2018, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#include "BlockType.hpp"
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace Resource {
|
||||
|
||||
const std::unordered_map<std::string, BlockType> BlockType::Types = {
|
||||
|
||||
#include "BlockType.inc.cpp"
|
||||
|
||||
};
|
||||
|
||||
struct LegacyBlockType {
|
||||
const char *data[16];
|
||||
};
|
||||
|
||||
static constexpr LegacyBlockType simple(const char *t) {
|
||||
return {
|
||||
t, t, t, t,
|
||||
t, t, t, t,
|
||||
t, t, t, t,
|
||||
t, t, t, t,
|
||||
};
|
||||
}
|
||||
|
||||
static const LegacyBlockType LEGACY_BLOCK_TYPE_DATA[256] = {
|
||||
|
||||
#include "LegacyBlockType.inc.cpp"
|
||||
|
||||
};
|
||||
|
||||
|
||||
const BlockType * BlockType::lookup(const std::string &name) {
|
||||
auto it = Types.find(name);
|
||||
if (it == Types.end())
|
||||
return nullptr;
|
||||
|
||||
return &it->second;
|
||||
}
|
||||
|
||||
static LegacyPalette makeLegacyPalette() {
|
||||
const std::string name_prefix("minecraft:");
|
||||
|
||||
LegacyPalette palette = {};
|
||||
for (size_t type = 0; type < 256; type++) {
|
||||
for (size_t data = 0; data < 16; data++) {
|
||||
const char *name = LEGACY_BLOCK_TYPE_DATA[type].data[data];
|
||||
if (!name)
|
||||
continue;
|
||||
|
||||
palette.types[type][data] = BlockType::lookup(name_prefix + name);
|
||||
}
|
||||
}
|
||||
|
||||
return palette;
|
||||
}
|
||||
|
||||
const LegacyPalette LEGACY_BLOCK_TYPES = makeLegacyPalette();
|
||||
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace MinedMap {
|
||||
namespace Resource {
|
||||
|
||||
#define BLOCK_OPAQUE (1u << 0)
|
||||
#define BLOCK_GRASS (1u << 1)
|
||||
#define BLOCK_FOLIAGE (1u << 2)
|
||||
#define BLOCK_BIRCH (1u << 3)
|
||||
#define BLOCK_SPRUCE (1u << 4)
|
||||
#define BLOCK_WATER (1u << 5)
|
||||
|
||||
class BlockType {
|
||||
private:
|
||||
static const std::unordered_map<std::string, BlockType> Types;
|
||||
|
||||
public:
|
||||
static const BlockType * lookup(const std::string &name);
|
||||
|
||||
uint8_t flags;
|
||||
struct {
|
||||
uint8_t r, g, b;
|
||||
} color;
|
||||
};
|
||||
|
||||
|
||||
struct LegacyPalette {
|
||||
const BlockType *types[256][16];
|
||||
};
|
||||
|
||||
extern const LegacyPalette LEGACY_BLOCK_TYPES;
|
||||
|
||||
}
|
||||
}
|
|
@ -1,903 +0,0 @@
|
|||
{"minecraft:acacia_button", {0, {0, 0, 0}}},
|
||||
{"minecraft:acacia_door", {BLOCK_OPAQUE, {167, 95, 60}}},
|
||||
{"minecraft:acacia_fence", {BLOCK_OPAQUE, {168, 90, 50}}},
|
||||
{"minecraft:acacia_fence_gate", {BLOCK_OPAQUE, {168, 90, 50}}},
|
||||
{"minecraft:acacia_leaves", {BLOCK_OPAQUE|BLOCK_FOLIAGE, {149, 148, 148}}},
|
||||
{"minecraft:acacia_log", {BLOCK_OPAQUE, {150, 88, 55}}},
|
||||
{"minecraft:acacia_planks", {BLOCK_OPAQUE, {168, 90, 50}}},
|
||||
{"minecraft:acacia_pressure_plate", {BLOCK_OPAQUE, {168, 90, 50}}},
|
||||
{"minecraft:acacia_sapling", {BLOCK_OPAQUE, {118, 117, 23}}},
|
||||
{"minecraft:acacia_sign", {BLOCK_OPAQUE, {168, 90, 50}}},
|
||||
{"minecraft:acacia_slab", {BLOCK_OPAQUE, {168, 90, 50}}},
|
||||
{"minecraft:acacia_stairs", {BLOCK_OPAQUE, {168, 90, 50}}},
|
||||
{"minecraft:acacia_trapdoor", {BLOCK_OPAQUE, {156, 87, 51}}},
|
||||
{"minecraft:acacia_wall_sign", {0, {0, 0, 0}}},
|
||||
{"minecraft:acacia_wood", {BLOCK_OPAQUE, {103, 96, 86}}},
|
||||
{"minecraft:activator_rail", {BLOCK_OPAQUE, {115, 87, 74}}},
|
||||
{"minecraft:air", {0, {0, 0, 0}}},
|
||||
{"minecraft:allium", {0, {0, 0, 0}}},
|
||||
{"minecraft:amethyst_block", {BLOCK_OPAQUE, {133, 97, 191}}},
|
||||
{"minecraft:amethyst_cluster", {BLOCK_OPAQUE, {163, 126, 207}}},
|
||||
{"minecraft:ancient_debris", {BLOCK_OPAQUE, {94, 66, 58}}},
|
||||
{"minecraft:andesite", {BLOCK_OPAQUE, {136, 136, 136}}},
|
||||
{"minecraft:andesite_slab", {BLOCK_OPAQUE, {136, 136, 136}}},
|
||||
{"minecraft:andesite_stairs", {BLOCK_OPAQUE, {136, 136, 136}}},
|
||||
{"minecraft:andesite_wall", {BLOCK_OPAQUE, {136, 136, 136}}},
|
||||
{"minecraft:anvil", {BLOCK_OPAQUE, {72, 72, 72}}},
|
||||
{"minecraft:attached_melon_stem", {BLOCK_OPAQUE|BLOCK_GRASS, {141, 142, 141}}},
|
||||
{"minecraft:attached_pumpkin_stem", {BLOCK_OPAQUE|BLOCK_GRASS, {139, 139, 139}}},
|
||||
{"minecraft:azalea", {BLOCK_OPAQUE, {101, 124, 47}}},
|
||||
{"minecraft:azalea_leaves", {BLOCK_OPAQUE, {90, 114, 44}}},
|
||||
{"minecraft:azure_bluet", {0, {0, 0, 0}}},
|
||||
{"minecraft:bamboo", {BLOCK_OPAQUE, {93, 144, 19}}},
|
||||
{"minecraft:bamboo_sapling", {0, {0, 0, 0}}},
|
||||
{"minecraft:barrel", {BLOCK_OPAQUE, {134, 100, 58}}},
|
||||
{"minecraft:barrier", {0, {0, 0, 0}}},
|
||||
{"minecraft:basalt", {BLOCK_OPAQUE, {80, 81, 86}}},
|
||||
{"minecraft:beacon", {BLOCK_OPAQUE, {117, 220, 215}}},
|
||||
{"minecraft:bedrock", {BLOCK_OPAQUE, {85, 85, 85}}},
|
||||
{"minecraft:bee_nest", {BLOCK_OPAQUE, {202, 160, 74}}},
|
||||
{"minecraft:beehive", {BLOCK_OPAQUE, {180, 146, 90}}},
|
||||
{"minecraft:beetroots", {BLOCK_OPAQUE, {93, 91, 30}}},
|
||||
{"minecraft:bell", {BLOCK_OPAQUE, {253, 235, 110}}},
|
||||
{"minecraft:big_dripleaf", {BLOCK_OPAQUE, {111, 141, 51}}},
|
||||
{"minecraft:big_dripleaf_stem", {0, {0, 0, 0}}},
|
||||
{"minecraft:birch_button", {0, {0, 0, 0}}},
|
||||
{"minecraft:birch_door", {BLOCK_OPAQUE, {220, 209, 176}}},
|
||||
{"minecraft:birch_fence", {BLOCK_OPAQUE, {192, 175, 121}}},
|
||||
{"minecraft:birch_fence_gate", {BLOCK_OPAQUE, {192, 175, 121}}},
|
||||
{"minecraft:birch_leaves", {BLOCK_OPAQUE|BLOCK_BIRCH, {130, 129, 130}}},
|
||||
{"minecraft:birch_log", {BLOCK_OPAQUE, {193, 179, 135}}},
|
||||
{"minecraft:birch_planks", {BLOCK_OPAQUE, {192, 175, 121}}},
|
||||
{"minecraft:birch_pressure_plate", {BLOCK_OPAQUE, {192, 175, 121}}},
|
||||
{"minecraft:birch_sapling", {BLOCK_OPAQUE, {127, 160, 79}}},
|
||||
{"minecraft:birch_sign", {BLOCK_OPAQUE, {192, 175, 121}}},
|
||||
{"minecraft:birch_slab", {BLOCK_OPAQUE, {192, 175, 121}}},
|
||||
{"minecraft:birch_stairs", {BLOCK_OPAQUE, {192, 175, 121}}},
|
||||
{"minecraft:birch_trapdoor", {BLOCK_OPAQUE, {207, 194, 157}}},
|
||||
{"minecraft:birch_wall_sign", {0, {0, 0, 0}}},
|
||||
{"minecraft:birch_wood", {BLOCK_OPAQUE, {216, 215, 210}}},
|
||||
{"minecraft:black_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:black_bed", {0, {0, 0, 0}}},
|
||||
{"minecraft:black_candle", {0, {0, 0, 0}}},
|
||||
{"minecraft:black_candle_cake", {BLOCK_OPAQUE, {248, 222, 214}}},
|
||||
{"minecraft:black_carpet", {BLOCK_OPAQUE, {20, 21, 25}}},
|
||||
{"minecraft:black_concrete", {BLOCK_OPAQUE, {8, 10, 15}}},
|
||||
{"minecraft:black_concrete_powder", {BLOCK_OPAQUE, {25, 26, 31}}},
|
||||
{"minecraft:black_glazed_terracotta", {BLOCK_OPAQUE, {67, 30, 32}}},
|
||||
{"minecraft:black_shulker_box", {BLOCK_OPAQUE, {25, 25, 29}}},
|
||||
{"minecraft:black_stained_glass", {BLOCK_OPAQUE, {25, 25, 25}}},
|
||||
{"minecraft:black_stained_glass_pane", {BLOCK_OPAQUE, {24, 24, 24}}},
|
||||
{"minecraft:black_terracotta", {BLOCK_OPAQUE, {37, 22, 16}}},
|
||||
{"minecraft:black_wall_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:black_wool", {BLOCK_OPAQUE, {20, 21, 25}}},
|
||||
{"minecraft:blackstone", {BLOCK_OPAQUE, {42, 36, 41}}},
|
||||
{"minecraft:blackstone_slab", {BLOCK_OPAQUE, {42, 36, 41}}},
|
||||
{"minecraft:blackstone_stairs", {BLOCK_OPAQUE, {42, 36, 41}}},
|
||||
{"minecraft:blackstone_wall", {BLOCK_OPAQUE, {42, 36, 41}}},
|
||||
{"minecraft:blast_furnace", {BLOCK_OPAQUE, {80, 80, 81}}},
|
||||
{"minecraft:blue_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:blue_bed", {0, {0, 0, 0}}},
|
||||
{"minecraft:blue_candle", {0, {0, 0, 0}}},
|
||||
{"minecraft:blue_candle_cake", {BLOCK_OPAQUE, {248, 222, 214}}},
|
||||
{"minecraft:blue_carpet", {BLOCK_OPAQUE, {53, 57, 157}}},
|
||||
{"minecraft:blue_concrete", {BLOCK_OPAQUE, {44, 46, 143}}},
|
||||
{"minecraft:blue_concrete_powder", {BLOCK_OPAQUE, {70, 73, 166}}},
|
||||
{"minecraft:blue_glazed_terracotta", {BLOCK_OPAQUE, {47, 64, 139}}},
|
||||
{"minecraft:blue_ice", {BLOCK_OPAQUE, {116, 167, 253}}},
|
||||
{"minecraft:blue_orchid", {0, {0, 0, 0}}},
|
||||
{"minecraft:blue_shulker_box", {BLOCK_OPAQUE, {43, 45, 140}}},
|
||||
{"minecraft:blue_stained_glass", {BLOCK_OPAQUE, {51, 76, 178}}},
|
||||
{"minecraft:blue_stained_glass_pane", {BLOCK_OPAQUE, {48, 73, 171}}},
|
||||
{"minecraft:blue_terracotta", {BLOCK_OPAQUE, {74, 59, 91}}},
|
||||
{"minecraft:blue_wall_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:blue_wool", {BLOCK_OPAQUE, {53, 57, 157}}},
|
||||
{"minecraft:bone_block", {BLOCK_OPAQUE, {209, 206, 179}}},
|
||||
{"minecraft:bookshelf", {BLOCK_OPAQUE, {162, 130, 78}}},
|
||||
{"minecraft:brain_coral", {0, {0, 0, 0}}},
|
||||
{"minecraft:brain_coral_block", {BLOCK_OPAQUE, {207, 91, 159}}},
|
||||
{"minecraft:brain_coral_fan", {0, {0, 0, 0}}},
|
||||
{"minecraft:brain_coral_wall_fan", {0, {0, 0, 0}}},
|
||||
{"minecraft:brewing_stand", {BLOCK_OPAQUE, {123, 101, 81}}},
|
||||
{"minecraft:brick_slab", {BLOCK_OPAQUE, {150, 97, 83}}},
|
||||
{"minecraft:brick_stairs", {BLOCK_OPAQUE, {150, 97, 83}}},
|
||||
{"minecraft:brick_wall", {BLOCK_OPAQUE, {150, 97, 83}}},
|
||||
{"minecraft:bricks", {BLOCK_OPAQUE, {150, 97, 83}}},
|
||||
{"minecraft:brown_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:brown_bed", {0, {0, 0, 0}}},
|
||||
{"minecraft:brown_candle", {0, {0, 0, 0}}},
|
||||
{"minecraft:brown_candle_cake", {BLOCK_OPAQUE, {248, 222, 214}}},
|
||||
{"minecraft:brown_carpet", {BLOCK_OPAQUE, {114, 71, 40}}},
|
||||
{"minecraft:brown_concrete", {BLOCK_OPAQUE, {96, 59, 31}}},
|
||||
{"minecraft:brown_concrete_powder", {BLOCK_OPAQUE, {125, 84, 53}}},
|
||||
{"minecraft:brown_glazed_terracotta", {BLOCK_OPAQUE, {119, 106, 85}}},
|
||||
{"minecraft:brown_mushroom", {0, {0, 0, 0}}},
|
||||
{"minecraft:brown_mushroom_block", {BLOCK_OPAQUE, {149, 111, 81}}},
|
||||
{"minecraft:brown_shulker_box", {BLOCK_OPAQUE, {106, 66, 35}}},
|
||||
{"minecraft:brown_stained_glass", {BLOCK_OPAQUE, {102, 76, 51}}},
|
||||
{"minecraft:brown_stained_glass_pane", {BLOCK_OPAQUE, {97, 73, 48}}},
|
||||
{"minecraft:brown_terracotta", {BLOCK_OPAQUE, {77, 51, 35}}},
|
||||
{"minecraft:brown_wall_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:brown_wool", {BLOCK_OPAQUE, {114, 71, 40}}},
|
||||
{"minecraft:bubble_column", {BLOCK_OPAQUE|BLOCK_WATER, {177, 177, 177}}},
|
||||
{"minecraft:bubble_coral", {0, {0, 0, 0}}},
|
||||
{"minecraft:bubble_coral_block", {BLOCK_OPAQUE, {165, 26, 162}}},
|
||||
{"minecraft:bubble_coral_fan", {0, {0, 0, 0}}},
|
||||
{"minecraft:bubble_coral_wall_fan", {0, {0, 0, 0}}},
|
||||
{"minecraft:budding_amethyst", {BLOCK_OPAQUE, {132, 96, 186}}},
|
||||
{"minecraft:cactus", {BLOCK_OPAQUE, {85, 127, 43}}},
|
||||
{"minecraft:cake", {BLOCK_OPAQUE, {248, 222, 214}}},
|
||||
{"minecraft:calcite", {BLOCK_OPAQUE, {223, 224, 220}}},
|
||||
{"minecraft:campfire", {BLOCK_OPAQUE, {110, 88, 54}}},
|
||||
{"minecraft:candle", {0, {0, 0, 0}}},
|
||||
{"minecraft:candle_cake", {BLOCK_OPAQUE, {248, 222, 214}}},
|
||||
{"minecraft:carrots", {BLOCK_OPAQUE, {81, 124, 37}}},
|
||||
{"minecraft:cartography_table", {BLOCK_OPAQUE, {103, 87, 67}}},
|
||||
{"minecraft:carved_pumpkin", {BLOCK_OPAQUE, {198, 118, 24}}},
|
||||
{"minecraft:cauldron", {BLOCK_OPAQUE, {73, 72, 74}}},
|
||||
{"minecraft:cave_air", {0, {0, 0, 0}}},
|
||||
{"minecraft:cave_vines", {BLOCK_OPAQUE, {90, 109, 40}}},
|
||||
{"minecraft:cave_vines_plant", {BLOCK_OPAQUE, {88, 101, 38}}},
|
||||
{"minecraft:chain", {0, {0, 0, 0}}},
|
||||
{"minecraft:chain_command_block", {BLOCK_OPAQUE, {131, 161, 147}}},
|
||||
{"minecraft:chest", {BLOCK_OPAQUE, {162, 130, 78}}},
|
||||
{"minecraft:chipped_anvil", {BLOCK_OPAQUE, {72, 72, 72}}},
|
||||
{"minecraft:chiseled_deepslate", {BLOCK_OPAQUE, {54, 54, 54}}},
|
||||
{"minecraft:chiseled_nether_bricks", {BLOCK_OPAQUE, {47, 23, 28}}},
|
||||
{"minecraft:chiseled_polished_blackstone", {BLOCK_OPAQUE, {53, 48, 56}}},
|
||||
{"minecraft:chiseled_quartz_block", {BLOCK_OPAQUE, {231, 226, 218}}},
|
||||
{"minecraft:chiseled_red_sandstone", {BLOCK_OPAQUE, {181, 97, 31}}},
|
||||
{"minecraft:chiseled_sandstone", {BLOCK_OPAQUE, {223, 214, 170}}},
|
||||
{"minecraft:chiseled_stone_bricks", {BLOCK_OPAQUE, {119, 118, 119}}},
|
||||
{"minecraft:chorus_flower", {BLOCK_OPAQUE, {151, 120, 151}}},
|
||||
{"minecraft:chorus_plant", {BLOCK_OPAQUE, {93, 57, 93}}},
|
||||
{"minecraft:clay", {BLOCK_OPAQUE, {160, 166, 179}}},
|
||||
{"minecraft:coal_block", {BLOCK_OPAQUE, {16, 15, 15}}},
|
||||
{"minecraft:coal_ore", {BLOCK_OPAQUE, {105, 105, 105}}},
|
||||
{"minecraft:coarse_dirt", {BLOCK_OPAQUE, {119, 85, 59}}},
|
||||
{"minecraft:cobbled_deepslate", {BLOCK_OPAQUE, {77, 77, 80}}},
|
||||
{"minecraft:cobbled_deepslate_slab", {BLOCK_OPAQUE, {77, 77, 80}}},
|
||||
{"minecraft:cobbled_deepslate_stairs", {BLOCK_OPAQUE, {77, 77, 80}}},
|
||||
{"minecraft:cobbled_deepslate_wall", {BLOCK_OPAQUE, {77, 77, 80}}},
|
||||
{"minecraft:cobblestone", {BLOCK_OPAQUE, {127, 127, 127}}},
|
||||
{"minecraft:cobblestone_slab", {BLOCK_OPAQUE, {127, 127, 127}}},
|
||||
{"minecraft:cobblestone_stairs", {BLOCK_OPAQUE, {127, 127, 127}}},
|
||||
{"minecraft:cobblestone_wall", {BLOCK_OPAQUE, {127, 127, 127}}},
|
||||
{"minecraft:cobweb", {BLOCK_OPAQUE, {228, 233, 234}}},
|
||||
{"minecraft:cocoa", {BLOCK_OPAQUE, {156, 94, 43}}},
|
||||
{"minecraft:command_block", {BLOCK_OPAQUE, {181, 136, 108}}},
|
||||
{"minecraft:comparator", {BLOCK_OPAQUE, {166, 161, 159}}},
|
||||
{"minecraft:composter", {BLOCK_OPAQUE, {88, 61, 23}}},
|
||||
{"minecraft:conduit", {BLOCK_OPAQUE, {159, 139, 113}}},
|
||||
{"minecraft:copper_block", {BLOCK_OPAQUE, {192, 107, 79}}},
|
||||
{"minecraft:copper_ore", {BLOCK_OPAQUE, {124, 125, 120}}},
|
||||
{"minecraft:cornflower", {0, {0, 0, 0}}},
|
||||
{"minecraft:cracked_deepslate_bricks", {BLOCK_OPAQUE, {64, 64, 65}}},
|
||||
{"minecraft:cracked_deepslate_tiles", {BLOCK_OPAQUE, {52, 52, 52}}},
|
||||
{"minecraft:cracked_nether_bricks", {BLOCK_OPAQUE, {40, 20, 23}}},
|
||||
{"minecraft:cracked_polished_blackstone_bricks", {BLOCK_OPAQUE, {44, 37, 43}}},
|
||||
{"minecraft:cracked_stone_bricks", {BLOCK_OPAQUE, {118, 117, 118}}},
|
||||
{"minecraft:crafting_table", {BLOCK_OPAQUE, {119, 73, 42}}},
|
||||
{"minecraft:creeper_head", {0, {0, 0, 0}}},
|
||||
{"minecraft:creeper_wall_head", {0, {0, 0, 0}}},
|
||||
{"minecraft:crimson_button", {0, {0, 0, 0}}},
|
||||
{"minecraft:crimson_door", {BLOCK_OPAQUE, {114, 54, 79}}},
|
||||
{"minecraft:crimson_fence", {BLOCK_OPAQUE, {101, 48, 70}}},
|
||||
{"minecraft:crimson_fence_gate", {BLOCK_OPAQUE, {101, 48, 70}}},
|
||||
{"minecraft:crimson_fungus", {0, {0, 0, 0}}},
|
||||
{"minecraft:crimson_hyphae", {BLOCK_OPAQUE, {92, 25, 29}}},
|
||||
{"minecraft:crimson_nylium", {BLOCK_OPAQUE, {130, 31, 31}}},
|
||||
{"minecraft:crimson_planks", {BLOCK_OPAQUE, {101, 48, 70}}},
|
||||
{"minecraft:crimson_pressure_plate", {BLOCK_OPAQUE, {101, 48, 70}}},
|
||||
{"minecraft:crimson_roots", {BLOCK_OPAQUE, {126, 8, 41}}},
|
||||
{"minecraft:crimson_sign", {BLOCK_OPAQUE, {101, 48, 70}}},
|
||||
{"minecraft:crimson_slab", {BLOCK_OPAQUE, {101, 48, 70}}},
|
||||
{"minecraft:crimson_stairs", {BLOCK_OPAQUE, {101, 48, 70}}},
|
||||
{"minecraft:crimson_stem", {BLOCK_OPAQUE, {112, 49, 70}}},
|
||||
{"minecraft:crimson_trapdoor", {BLOCK_OPAQUE, {103, 50, 72}}},
|
||||
{"minecraft:crimson_wall_sign", {0, {0, 0, 0}}},
|
||||
{"minecraft:crying_obsidian", {BLOCK_OPAQUE, {32, 10, 60}}},
|
||||
{"minecraft:cut_copper", {BLOCK_OPAQUE, {191, 106, 80}}},
|
||||
{"minecraft:cut_copper_slab", {BLOCK_OPAQUE, {191, 106, 80}}},
|
||||
{"minecraft:cut_copper_stairs", {BLOCK_OPAQUE, {191, 106, 80}}},
|
||||
{"minecraft:cut_red_sandstone", {BLOCK_OPAQUE, {181, 97, 31}}},
|
||||
{"minecraft:cut_red_sandstone_slab", {BLOCK_OPAQUE, {181, 97, 31}}},
|
||||
{"minecraft:cut_sandstone", {BLOCK_OPAQUE, {223, 214, 170}}},
|
||||
{"minecraft:cut_sandstone_slab", {BLOCK_OPAQUE, {223, 214, 170}}},
|
||||
{"minecraft:cyan_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:cyan_bed", {0, {0, 0, 0}}},
|
||||
{"minecraft:cyan_candle", {0, {0, 0, 0}}},
|
||||
{"minecraft:cyan_candle_cake", {BLOCK_OPAQUE, {248, 222, 214}}},
|
||||
{"minecraft:cyan_carpet", {BLOCK_OPAQUE, {21, 137, 145}}},
|
||||
{"minecraft:cyan_concrete", {BLOCK_OPAQUE, {21, 119, 136}}},
|
||||
{"minecraft:cyan_concrete_powder", {BLOCK_OPAQUE, {36, 147, 157}}},
|
||||
{"minecraft:cyan_glazed_terracotta", {BLOCK_OPAQUE, {52, 118, 125}}},
|
||||
{"minecraft:cyan_shulker_box", {BLOCK_OPAQUE, {20, 121, 135}}},
|
||||
{"minecraft:cyan_stained_glass", {BLOCK_OPAQUE, {76, 127, 153}}},
|
||||
{"minecraft:cyan_stained_glass_pane", {BLOCK_OPAQUE, {73, 122, 147}}},
|
||||
{"minecraft:cyan_terracotta", {BLOCK_OPAQUE, {86, 91, 91}}},
|
||||
{"minecraft:cyan_wall_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:cyan_wool", {BLOCK_OPAQUE, {21, 137, 145}}},
|
||||
{"minecraft:damaged_anvil", {BLOCK_OPAQUE, {72, 72, 72}}},
|
||||
{"minecraft:dandelion", {0, {0, 0, 0}}},
|
||||
{"minecraft:dark_oak_button", {0, {0, 0, 0}}},
|
||||
{"minecraft:dark_oak_door", {BLOCK_OPAQUE, {76, 51, 25}}},
|
||||
{"minecraft:dark_oak_fence", {BLOCK_OPAQUE, {66, 43, 20}}},
|
||||
{"minecraft:dark_oak_fence_gate", {BLOCK_OPAQUE, {66, 43, 20}}},
|
||||
{"minecraft:dark_oak_leaves", {BLOCK_OPAQUE|BLOCK_FOLIAGE, {150, 150, 150}}},
|
||||
{"minecraft:dark_oak_log", {BLOCK_OPAQUE, {67, 45, 22}}},
|
||||
{"minecraft:dark_oak_planks", {BLOCK_OPAQUE, {66, 43, 20}}},
|
||||
{"minecraft:dark_oak_pressure_plate", {BLOCK_OPAQUE, {66, 43, 20}}},
|
||||
{"minecraft:dark_oak_sapling", {BLOCK_OPAQUE, {61, 90, 30}}},
|
||||
{"minecraft:dark_oak_sign", {BLOCK_OPAQUE, {66, 43, 20}}},
|
||||
{"minecraft:dark_oak_slab", {BLOCK_OPAQUE, {66, 43, 20}}},
|
||||
{"minecraft:dark_oak_stairs", {BLOCK_OPAQUE, {66, 43, 20}}},
|
||||
{"minecraft:dark_oak_trapdoor", {BLOCK_OPAQUE, {75, 49, 23}}},
|
||||
{"minecraft:dark_oak_wall_sign", {0, {0, 0, 0}}},
|
||||
{"minecraft:dark_oak_wood", {BLOCK_OPAQUE, {60, 46, 26}}},
|
||||
{"minecraft:dark_prismarine", {BLOCK_OPAQUE, {51, 91, 75}}},
|
||||
{"minecraft:dark_prismarine_slab", {BLOCK_OPAQUE, {51, 91, 75}}},
|
||||
{"minecraft:dark_prismarine_stairs", {BLOCK_OPAQUE, {51, 91, 75}}},
|
||||
{"minecraft:daylight_detector", {BLOCK_OPAQUE, {130, 116, 94}}},
|
||||
{"minecraft:dead_brain_coral", {0, {0, 0, 0}}},
|
||||
{"minecraft:dead_brain_coral_block", {BLOCK_OPAQUE, {124, 117, 114}}},
|
||||
{"minecraft:dead_brain_coral_fan", {0, {0, 0, 0}}},
|
||||
{"minecraft:dead_brain_coral_wall_fan", {0, {0, 0, 0}}},
|
||||
{"minecraft:dead_bubble_coral", {0, {0, 0, 0}}},
|
||||
{"minecraft:dead_bubble_coral_block", {BLOCK_OPAQUE, {131, 123, 119}}},
|
||||
{"minecraft:dead_bubble_coral_fan", {0, {0, 0, 0}}},
|
||||
{"minecraft:dead_bubble_coral_wall_fan", {0, {0, 0, 0}}},
|
||||
{"minecraft:dead_bush", {BLOCK_OPAQUE, {107, 78, 40}}},
|
||||
{"minecraft:dead_fire_coral", {0, {0, 0, 0}}},
|
||||
{"minecraft:dead_fire_coral_block", {BLOCK_OPAQUE, {131, 123, 119}}},
|
||||
{"minecraft:dead_fire_coral_fan", {0, {0, 0, 0}}},
|
||||
{"minecraft:dead_fire_coral_wall_fan", {0, {0, 0, 0}}},
|
||||
{"minecraft:dead_horn_coral", {0, {0, 0, 0}}},
|
||||
{"minecraft:dead_horn_coral_block", {BLOCK_OPAQUE, {133, 126, 122}}},
|
||||
{"minecraft:dead_horn_coral_fan", {0, {0, 0, 0}}},
|
||||
{"minecraft:dead_horn_coral_wall_fan", {0, {0, 0, 0}}},
|
||||
{"minecraft:dead_tube_coral", {0, {0, 0, 0}}},
|
||||
{"minecraft:dead_tube_coral_block", {BLOCK_OPAQUE, {130, 123, 119}}},
|
||||
{"minecraft:dead_tube_coral_fan", {0, {0, 0, 0}}},
|
||||
{"minecraft:dead_tube_coral_wall_fan", {0, {0, 0, 0}}},
|
||||
{"minecraft:deepslate", {BLOCK_OPAQUE, {80, 80, 82}}},
|
||||
{"minecraft:deepslate_brick_slab", {BLOCK_OPAQUE, {70, 70, 71}}},
|
||||
{"minecraft:deepslate_brick_stairs", {BLOCK_OPAQUE, {70, 70, 71}}},
|
||||
{"minecraft:deepslate_brick_wall", {BLOCK_OPAQUE, {70, 70, 71}}},
|
||||
{"minecraft:deepslate_bricks", {BLOCK_OPAQUE, {70, 70, 71}}},
|
||||
{"minecraft:deepslate_coal_ore", {BLOCK_OPAQUE, {74, 74, 76}}},
|
||||
{"minecraft:deepslate_copper_ore", {BLOCK_OPAQUE, {92, 93, 89}}},
|
||||
{"minecraft:deepslate_diamond_ore", {BLOCK_OPAQUE, {83, 106, 106}}},
|
||||
{"minecraft:deepslate_emerald_ore", {BLOCK_OPAQUE, {78, 104, 87}}},
|
||||
{"minecraft:deepslate_gold_ore", {BLOCK_OPAQUE, {115, 102, 78}}},
|
||||
{"minecraft:deepslate_iron_ore", {BLOCK_OPAQUE, {106, 99, 94}}},
|
||||
{"minecraft:deepslate_lapis_ore", {BLOCK_OPAQUE, {79, 90, 115}}},
|
||||
{"minecraft:deepslate_redstone_ore", {BLOCK_OPAQUE, {104, 73, 74}}},
|
||||
{"minecraft:deepslate_tile_slab", {BLOCK_OPAQUE, {54, 54, 55}}},
|
||||
{"minecraft:deepslate_tile_stairs", {BLOCK_OPAQUE, {54, 54, 55}}},
|
||||
{"minecraft:deepslate_tile_wall", {BLOCK_OPAQUE, {54, 54, 55}}},
|
||||
{"minecraft:deepslate_tiles", {BLOCK_OPAQUE, {54, 54, 55}}},
|
||||
{"minecraft:detector_rail", {BLOCK_OPAQUE, {123, 104, 90}}},
|
||||
{"minecraft:diamond_block", {BLOCK_OPAQUE, {98, 237, 228}}},
|
||||
{"minecraft:diamond_ore", {BLOCK_OPAQUE, {121, 141, 140}}},
|
||||
{"minecraft:diorite", {BLOCK_OPAQUE, {188, 188, 188}}},
|
||||
{"minecraft:diorite_slab", {BLOCK_OPAQUE, {188, 188, 188}}},
|
||||
{"minecraft:diorite_stairs", {BLOCK_OPAQUE, {188, 188, 188}}},
|
||||
{"minecraft:diorite_wall", {BLOCK_OPAQUE, {188, 188, 188}}},
|
||||
{"minecraft:dirt", {BLOCK_OPAQUE, {134, 96, 67}}},
|
||||
{"minecraft:dirt_path", {BLOCK_OPAQUE, {148, 121, 65}}},
|
||||
{"minecraft:dispenser", {BLOCK_OPAQUE, {110, 109, 109}}},
|
||||
{"minecraft:dragon_egg", {BLOCK_OPAQUE, {12, 9, 15}}},
|
||||
{"minecraft:dragon_head", {0, {0, 0, 0}}},
|
||||
{"minecraft:dragon_wall_head", {0, {0, 0, 0}}},
|
||||
{"minecraft:dried_kelp_block", {BLOCK_OPAQUE, {50, 58, 38}}},
|
||||
{"minecraft:dripstone_block", {BLOCK_OPAQUE, {134, 107, 92}}},
|
||||
{"minecraft:dropper", {BLOCK_OPAQUE, {110, 109, 109}}},
|
||||
{"minecraft:emerald_block", {BLOCK_OPAQUE, {42, 203, 87}}},
|
||||
{"minecraft:emerald_ore", {BLOCK_OPAQUE, {108, 136, 115}}},
|
||||
{"minecraft:enchanting_table", {BLOCK_OPAQUE, {128, 75, 85}}},
|
||||
{"minecraft:end_gateway", {BLOCK_OPAQUE, {15, 10, 24}}},
|
||||
{"minecraft:end_portal", {BLOCK_OPAQUE, {15, 10, 24}}},
|
||||
{"minecraft:end_portal_frame", {BLOCK_OPAQUE, {91, 120, 97}}},
|
||||
{"minecraft:end_rod", {0, {0, 0, 0}}},
|
||||
{"minecraft:end_stone", {BLOCK_OPAQUE, {219, 222, 158}}},
|
||||
{"minecraft:end_stone_brick_slab", {BLOCK_OPAQUE, {218, 224, 162}}},
|
||||
{"minecraft:end_stone_brick_stairs", {BLOCK_OPAQUE, {218, 224, 162}}},
|
||||
{"minecraft:end_stone_brick_wall", {BLOCK_OPAQUE, {218, 224, 162}}},
|
||||
{"minecraft:end_stone_bricks", {BLOCK_OPAQUE, {218, 224, 162}}},
|
||||
{"minecraft:ender_chest", {BLOCK_OPAQUE, {15, 10, 24}}},
|
||||
{"minecraft:exposed_copper", {BLOCK_OPAQUE, {161, 125, 103}}},
|
||||
{"minecraft:exposed_cut_copper", {BLOCK_OPAQUE, {154, 121, 101}}},
|
||||
{"minecraft:exposed_cut_copper_slab", {BLOCK_OPAQUE, {154, 121, 101}}},
|
||||
{"minecraft:exposed_cut_copper_stairs", {BLOCK_OPAQUE, {154, 121, 101}}},
|
||||
{"minecraft:farmland", {BLOCK_OPAQUE, {81, 44, 15}}},
|
||||
{"minecraft:fern", {0, {0, 0, 0}}},
|
||||
{"minecraft:fire", {BLOCK_OPAQUE, {211, 140, 53}}},
|
||||
{"minecraft:fire_coral", {0, {0, 0, 0}}},
|
||||
{"minecraft:fire_coral_block", {BLOCK_OPAQUE, {163, 35, 46}}},
|
||||
{"minecraft:fire_coral_fan", {0, {0, 0, 0}}},
|
||||
{"minecraft:fire_coral_wall_fan", {0, {0, 0, 0}}},
|
||||
{"minecraft:fletching_table", {BLOCK_OPAQUE, {197, 180, 133}}},
|
||||
{"minecraft:flower_pot", {BLOCK_OPAQUE, {124, 68, 53}}},
|
||||
{"minecraft:flowering_azalea", {BLOCK_OPAQUE, {112, 121, 64}}},
|
||||
{"minecraft:flowering_azalea_leaves", {BLOCK_OPAQUE, {99, 111, 60}}},
|
||||
{"minecraft:frosted_ice", {BLOCK_OPAQUE, {140, 181, 252}}},
|
||||
{"minecraft:furnace", {BLOCK_OPAQUE, {110, 109, 109}}},
|
||||
{"minecraft:gilded_blackstone", {BLOCK_OPAQUE, {55, 42, 38}}},
|
||||
{"minecraft:glass", {BLOCK_OPAQUE, {175, 213, 219}}},
|
||||
{"minecraft:glass_pane", {BLOCK_OPAQUE, {170, 210, 217}}},
|
||||
{"minecraft:glow_item_frame", {0, {0, 0, 0}}},
|
||||
{"minecraft:glow_lichen", {0, {0, 0, 0}}},
|
||||
{"minecraft:glowstone", {BLOCK_OPAQUE, {171, 131, 84}}},
|
||||
{"minecraft:gold_block", {BLOCK_OPAQUE, {246, 208, 61}}},
|
||||
{"minecraft:gold_ore", {BLOCK_OPAQUE, {145, 133, 106}}},
|
||||
{"minecraft:granite", {BLOCK_OPAQUE, {149, 103, 85}}},
|
||||
{"minecraft:granite_slab", {BLOCK_OPAQUE, {149, 103, 85}}},
|
||||
{"minecraft:granite_stairs", {BLOCK_OPAQUE, {149, 103, 85}}},
|
||||
{"minecraft:granite_wall", {BLOCK_OPAQUE, {149, 103, 85}}},
|
||||
{"minecraft:grass", {0, {0, 0, 0}}},
|
||||
{"minecraft:grass_block", {BLOCK_OPAQUE|BLOCK_GRASS, {147, 147, 147}}},
|
||||
{"minecraft:grass_path", {BLOCK_OPAQUE, {148, 121, 65}}},
|
||||
{"minecraft:gravel", {BLOCK_OPAQUE, {131, 127, 126}}},
|
||||
{"minecraft:gray_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:gray_bed", {0, {0, 0, 0}}},
|
||||
{"minecraft:gray_candle", {0, {0, 0, 0}}},
|
||||
{"minecraft:gray_candle_cake", {BLOCK_OPAQUE, {248, 222, 214}}},
|
||||
{"minecraft:gray_carpet", {BLOCK_OPAQUE, {62, 68, 71}}},
|
||||
{"minecraft:gray_concrete", {BLOCK_OPAQUE, {54, 57, 61}}},
|
||||
{"minecraft:gray_concrete_powder", {BLOCK_OPAQUE, {76, 81, 84}}},
|
||||
{"minecraft:gray_glazed_terracotta", {BLOCK_OPAQUE, {83, 90, 93}}},
|
||||
{"minecraft:gray_shulker_box", {BLOCK_OPAQUE, {55, 58, 62}}},
|
||||
{"minecraft:gray_stained_glass", {BLOCK_OPAQUE, {76, 76, 76}}},
|
||||
{"minecraft:gray_stained_glass_pane", {BLOCK_OPAQUE, {73, 73, 73}}},
|
||||
{"minecraft:gray_terracotta", {BLOCK_OPAQUE, {57, 42, 35}}},
|
||||
{"minecraft:gray_wall_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:gray_wool", {BLOCK_OPAQUE, {62, 68, 71}}},
|
||||
{"minecraft:green_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:green_bed", {0, {0, 0, 0}}},
|
||||
{"minecraft:green_candle", {0, {0, 0, 0}}},
|
||||
{"minecraft:green_candle_cake", {BLOCK_OPAQUE, {248, 222, 214}}},
|
||||
{"minecraft:green_carpet", {BLOCK_OPAQUE, {84, 109, 27}}},
|
||||
{"minecraft:green_concrete", {BLOCK_OPAQUE, {73, 91, 36}}},
|
||||
{"minecraft:green_concrete_powder", {BLOCK_OPAQUE, {97, 119, 44}}},
|
||||
{"minecraft:green_glazed_terracotta", {BLOCK_OPAQUE, {117, 142, 67}}},
|
||||
{"minecraft:green_shulker_box", {BLOCK_OPAQUE, {79, 100, 31}}},
|
||||
{"minecraft:green_stained_glass", {BLOCK_OPAQUE, {102, 127, 51}}},
|
||||
{"minecraft:green_stained_glass_pane", {BLOCK_OPAQUE, {97, 122, 48}}},
|
||||
{"minecraft:green_terracotta", {BLOCK_OPAQUE, {76, 83, 42}}},
|
||||
{"minecraft:green_wall_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:green_wool", {BLOCK_OPAQUE, {84, 109, 27}}},
|
||||
{"minecraft:grindstone", {BLOCK_OPAQUE, {142, 142, 142}}},
|
||||
{"minecraft:hanging_roots", {BLOCK_OPAQUE, {161, 115, 91}}},
|
||||
{"minecraft:hay_block", {BLOCK_OPAQUE, {165, 139, 12}}},
|
||||
{"minecraft:heavy_weighted_pressure_plate", {BLOCK_OPAQUE, {220, 220, 220}}},
|
||||
{"minecraft:honey_block", {BLOCK_OPAQUE, {251, 185, 52}}},
|
||||
{"minecraft:honeycomb_block", {BLOCK_OPAQUE, {229, 148, 29}}},
|
||||
{"minecraft:hopper", {BLOCK_OPAQUE, {75, 74, 75}}},
|
||||
{"minecraft:horn_coral", {0, {0, 0, 0}}},
|
||||
{"minecraft:horn_coral_block", {BLOCK_OPAQUE, {216, 199, 66}}},
|
||||
{"minecraft:horn_coral_fan", {0, {0, 0, 0}}},
|
||||
{"minecraft:horn_coral_wall_fan", {0, {0, 0, 0}}},
|
||||
{"minecraft:ice", {BLOCK_OPAQUE, {145, 183, 253}}},
|
||||
{"minecraft:infested_chiseled_stone_bricks", {BLOCK_OPAQUE, {119, 118, 119}}},
|
||||
{"minecraft:infested_cobblestone", {BLOCK_OPAQUE, {127, 127, 127}}},
|
||||
{"minecraft:infested_cracked_stone_bricks", {BLOCK_OPAQUE, {118, 117, 118}}},
|
||||
{"minecraft:infested_deepslate", {BLOCK_OPAQUE, {80, 80, 82}}},
|
||||
{"minecraft:infested_mossy_stone_bricks", {BLOCK_OPAQUE, {115, 121, 105}}},
|
||||
{"minecraft:infested_stone", {BLOCK_OPAQUE, {125, 125, 125}}},
|
||||
{"minecraft:infested_stone_bricks", {BLOCK_OPAQUE, {122, 121, 122}}},
|
||||
{"minecraft:iron_bars", {BLOCK_OPAQUE, {136, 139, 135}}},
|
||||
{"minecraft:iron_block", {BLOCK_OPAQUE, {220, 220, 220}}},
|
||||
{"minecraft:iron_door", {BLOCK_OPAQUE, {194, 193, 193}}},
|
||||
{"minecraft:iron_ore", {BLOCK_OPAQUE, {136, 129, 122}}},
|
||||
{"minecraft:iron_trapdoor", {BLOCK_OPAQUE, {202, 202, 202}}},
|
||||
{"minecraft:item_frame", {0, {0, 0, 0}}},
|
||||
{"minecraft:jack_o_lantern", {BLOCK_OPAQUE, {214, 152, 52}}},
|
||||
{"minecraft:jigsaw", {BLOCK_OPAQUE, {80, 69, 81}}},
|
||||
{"minecraft:jukebox", {BLOCK_OPAQUE, {93, 64, 47}}},
|
||||
{"minecraft:jungle_button", {0, {0, 0, 0}}},
|
||||
{"minecraft:jungle_door", {BLOCK_OPAQUE, {163, 119, 84}}},
|
||||
{"minecraft:jungle_fence", {BLOCK_OPAQUE, {160, 115, 80}}},
|
||||
{"minecraft:jungle_fence_gate", {BLOCK_OPAQUE, {160, 115, 80}}},
|
||||
{"minecraft:jungle_leaves", {BLOCK_OPAQUE|BLOCK_FOLIAGE, {156, 154, 143}}},
|
||||
{"minecraft:jungle_log", {BLOCK_OPAQUE, {149, 109, 70}}},
|
||||
{"minecraft:jungle_planks", {BLOCK_OPAQUE, {160, 115, 80}}},
|
||||
{"minecraft:jungle_pressure_plate", {BLOCK_OPAQUE, {160, 115, 80}}},
|
||||
{"minecraft:jungle_sapling", {BLOCK_OPAQUE, {47, 81, 16}}},
|
||||
{"minecraft:jungle_sign", {BLOCK_OPAQUE, {160, 115, 80}}},
|
||||
{"minecraft:jungle_slab", {BLOCK_OPAQUE, {160, 115, 80}}},
|
||||
{"minecraft:jungle_stairs", {BLOCK_OPAQUE, {160, 115, 80}}},
|
||||
{"minecraft:jungle_trapdoor", {BLOCK_OPAQUE, {152, 110, 77}}},
|
||||
{"minecraft:jungle_wall_sign", {0, {0, 0, 0}}},
|
||||
{"minecraft:jungle_wood", {BLOCK_OPAQUE, {85, 67, 25}}},
|
||||
{"minecraft:kelp", {0, {0, 0, 0}}},
|
||||
{"minecraft:kelp_plant", {BLOCK_OPAQUE, {86, 130, 42}}},
|
||||
{"minecraft:ladder", {0, {0, 0, 0}}},
|
||||
{"minecraft:lantern", {BLOCK_OPAQUE, {106, 91, 83}}},
|
||||
{"minecraft:lapis_block", {BLOCK_OPAQUE, {30, 67, 140}}},
|
||||
{"minecraft:lapis_ore", {BLOCK_OPAQUE, {107, 117, 141}}},
|
||||
{"minecraft:large_amethyst_bud", {0, {0, 0, 0}}},
|
||||
{"minecraft:large_fern", {BLOCK_OPAQUE|BLOCK_GRASS, {125, 125, 125}}},
|
||||
{"minecraft:lava", {BLOCK_OPAQUE, {212, 90, 18}}},
|
||||
{"minecraft:lava_cauldron", {BLOCK_OPAQUE, {73, 72, 74}}},
|
||||
{"minecraft:lectern", {BLOCK_OPAQUE, {173, 137, 83}}},
|
||||
{"minecraft:lever", {0, {0, 0, 0}}},
|
||||
{"minecraft:light", {0, {0, 0, 0}}},
|
||||
{"minecraft:light_blue_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:light_blue_bed", {0, {0, 0, 0}}},
|
||||
{"minecraft:light_blue_candle", {0, {0, 0, 0}}},
|
||||
{"minecraft:light_blue_candle_cake", {BLOCK_OPAQUE, {248, 222, 214}}},
|
||||
{"minecraft:light_blue_carpet", {BLOCK_OPAQUE, {58, 175, 217}}},
|
||||
{"minecraft:light_blue_concrete", {BLOCK_OPAQUE, {35, 137, 198}}},
|
||||
{"minecraft:light_blue_concrete_powder", {BLOCK_OPAQUE, {74, 180, 213}}},
|
||||
{"minecraft:light_blue_glazed_terracotta", {BLOCK_OPAQUE, {94, 164, 208}}},
|
||||
{"minecraft:light_blue_shulker_box", {BLOCK_OPAQUE, {49, 163, 212}}},
|
||||
{"minecraft:light_blue_stained_glass", {BLOCK_OPAQUE, {102, 153, 216}}},
|
||||
{"minecraft:light_blue_stained_glass_pane", {BLOCK_OPAQUE, {97, 147, 208}}},
|
||||
{"minecraft:light_blue_terracotta", {BLOCK_OPAQUE, {113, 108, 137}}},
|
||||
{"minecraft:light_blue_wall_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:light_blue_wool", {BLOCK_OPAQUE, {58, 175, 217}}},
|
||||
{"minecraft:light_gray_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:light_gray_bed", {0, {0, 0, 0}}},
|
||||
{"minecraft:light_gray_candle", {0, {0, 0, 0}}},
|
||||
{"minecraft:light_gray_candle_cake", {BLOCK_OPAQUE, {248, 222, 214}}},
|
||||
{"minecraft:light_gray_carpet", {BLOCK_OPAQUE, {142, 142, 134}}},
|
||||
{"minecraft:light_gray_concrete", {BLOCK_OPAQUE, {125, 125, 115}}},
|
||||
{"minecraft:light_gray_concrete_powder", {BLOCK_OPAQUE, {154, 154, 148}}},
|
||||
{"minecraft:light_gray_glazed_terracotta", {BLOCK_OPAQUE, {144, 166, 167}}},
|
||||
{"minecraft:light_gray_shulker_box", {BLOCK_OPAQUE, {124, 124, 115}}},
|
||||
{"minecraft:light_gray_stained_glass", {BLOCK_OPAQUE, {153, 153, 153}}},
|
||||
{"minecraft:light_gray_stained_glass_pane", {BLOCK_OPAQUE, {147, 147, 147}}},
|
||||
{"minecraft:light_gray_terracotta", {BLOCK_OPAQUE, {135, 106, 97}}},
|
||||
{"minecraft:light_gray_wall_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:light_gray_wool", {BLOCK_OPAQUE, {142, 142, 134}}},
|
||||
{"minecraft:light_weighted_pressure_plate", {BLOCK_OPAQUE, {246, 208, 61}}},
|
||||
{"minecraft:lightning_rod", {0, {0, 0, 0}}},
|
||||
{"minecraft:lilac", {BLOCK_OPAQUE, {154, 125, 147}}},
|
||||
{"minecraft:lily_of_the_valley", {0, {0, 0, 0}}},
|
||||
{"minecraft:lily_pad", {BLOCK_OPAQUE|BLOCK_GRASS, {133, 133, 133}}},
|
||||
{"minecraft:lime_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:lime_bed", {0, {0, 0, 0}}},
|
||||
{"minecraft:lime_candle", {0, {0, 0, 0}}},
|
||||
{"minecraft:lime_candle_cake", {BLOCK_OPAQUE, {248, 222, 214}}},
|
||||
{"minecraft:lime_carpet", {BLOCK_OPAQUE, {112, 185, 25}}},
|
||||
{"minecraft:lime_concrete", {BLOCK_OPAQUE, {94, 168, 24}}},
|
||||
{"minecraft:lime_concrete_powder", {BLOCK_OPAQUE, {125, 189, 41}}},
|
||||
{"minecraft:lime_glazed_terracotta", {BLOCK_OPAQUE, {162, 197, 55}}},
|
||||
{"minecraft:lime_shulker_box", {BLOCK_OPAQUE, {99, 172, 23}}},
|
||||
{"minecraft:lime_stained_glass", {BLOCK_OPAQUE, {127, 204, 25}}},
|
||||
{"minecraft:lime_stained_glass_pane", {BLOCK_OPAQUE, {122, 196, 24}}},
|
||||
{"minecraft:lime_terracotta", {BLOCK_OPAQUE, {103, 117, 52}}},
|
||||
{"minecraft:lime_wall_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:lime_wool", {BLOCK_OPAQUE, {112, 185, 25}}},
|
||||
{"minecraft:lodestone", {BLOCK_OPAQUE, {147, 149, 152}}},
|
||||
{"minecraft:loom", {BLOCK_OPAQUE, {142, 119, 91}}},
|
||||
{"minecraft:magenta_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:magenta_bed", {0, {0, 0, 0}}},
|
||||
{"minecraft:magenta_candle", {0, {0, 0, 0}}},
|
||||
{"minecraft:magenta_candle_cake", {BLOCK_OPAQUE, {248, 222, 214}}},
|
||||
{"minecraft:magenta_carpet", {BLOCK_OPAQUE, {189, 68, 179}}},
|
||||
{"minecraft:magenta_concrete", {BLOCK_OPAQUE, {169, 48, 159}}},
|
||||
{"minecraft:magenta_concrete_powder", {BLOCK_OPAQUE, {192, 83, 184}}},
|
||||
{"minecraft:magenta_glazed_terracotta", {BLOCK_OPAQUE, {208, 100, 191}}},
|
||||
{"minecraft:magenta_shulker_box", {BLOCK_OPAQUE, {173, 54, 163}}},
|
||||
{"minecraft:magenta_stained_glass", {BLOCK_OPAQUE, {178, 76, 216}}},
|
||||
{"minecraft:magenta_stained_glass_pane", {BLOCK_OPAQUE, {171, 73, 208}}},
|
||||
{"minecraft:magenta_terracotta", {BLOCK_OPAQUE, {149, 88, 108}}},
|
||||
{"minecraft:magenta_wall_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:magenta_wool", {BLOCK_OPAQUE, {189, 68, 179}}},
|
||||
{"minecraft:magma_block", {BLOCK_OPAQUE, {142, 63, 31}}},
|
||||
{"minecraft:medium_amethyst_bud", {0, {0, 0, 0}}},
|
||||
{"minecraft:melon", {BLOCK_OPAQUE, {111, 144, 30}}},
|
||||
{"minecraft:melon_stem", {BLOCK_OPAQUE|BLOCK_GRASS, {153, 153, 153}}},
|
||||
{"minecraft:moss_block", {BLOCK_OPAQUE, {89, 109, 45}}},
|
||||
{"minecraft:moss_carpet", {BLOCK_OPAQUE, {89, 109, 45}}},
|
||||
{"minecraft:mossy_cobblestone", {BLOCK_OPAQUE, {110, 118, 94}}},
|
||||
{"minecraft:mossy_cobblestone_slab", {BLOCK_OPAQUE, {110, 118, 94}}},
|
||||
{"minecraft:mossy_cobblestone_stairs", {BLOCK_OPAQUE, {110, 118, 94}}},
|
||||
{"minecraft:mossy_cobblestone_wall", {BLOCK_OPAQUE, {110, 118, 94}}},
|
||||
{"minecraft:mossy_stone_brick_slab", {BLOCK_OPAQUE, {115, 121, 105}}},
|
||||
{"minecraft:mossy_stone_brick_stairs", {BLOCK_OPAQUE, {115, 121, 105}}},
|
||||
{"minecraft:mossy_stone_brick_wall", {BLOCK_OPAQUE, {115, 121, 105}}},
|
||||
{"minecraft:mossy_stone_bricks", {BLOCK_OPAQUE, {115, 121, 105}}},
|
||||
{"minecraft:moving_piston", {0, {0, 0, 0}}},
|
||||
{"minecraft:mushroom_stem", {BLOCK_OPAQUE, {203, 196, 185}}},
|
||||
{"minecraft:mycelium", {BLOCK_OPAQUE, {111, 98, 101}}},
|
||||
{"minecraft:nether_brick_fence", {BLOCK_OPAQUE, {44, 21, 26}}},
|
||||
{"minecraft:nether_brick_slab", {BLOCK_OPAQUE, {44, 21, 26}}},
|
||||
{"minecraft:nether_brick_stairs", {BLOCK_OPAQUE, {44, 21, 26}}},
|
||||
{"minecraft:nether_brick_wall", {BLOCK_OPAQUE, {44, 21, 26}}},
|
||||
{"minecraft:nether_bricks", {BLOCK_OPAQUE, {44, 21, 26}}},
|
||||
{"minecraft:nether_gold_ore", {BLOCK_OPAQUE, {115, 54, 42}}},
|
||||
{"minecraft:nether_portal", {BLOCK_OPAQUE, {89, 11, 192}}},
|
||||
{"minecraft:nether_quartz_ore", {BLOCK_OPAQUE, {117, 65, 62}}},
|
||||
{"minecraft:nether_sprouts", {BLOCK_OPAQUE, {19, 151, 133}}},
|
||||
{"minecraft:nether_wart", {BLOCK_OPAQUE, {111, 18, 19}}},
|
||||
{"minecraft:nether_wart_block", {BLOCK_OPAQUE, {114, 2, 2}}},
|
||||
{"minecraft:netherite_block", {BLOCK_OPAQUE, {66, 61, 63}}},
|
||||
{"minecraft:netherrack", {BLOCK_OPAQUE, {97, 38, 38}}},
|
||||
{"minecraft:note_block", {BLOCK_OPAQUE, {88, 58, 40}}},
|
||||
{"minecraft:oak_button", {0, {0, 0, 0}}},
|
||||
{"minecraft:oak_door", {BLOCK_OPAQUE, {140, 110, 66}}},
|
||||
{"minecraft:oak_fence", {BLOCK_OPAQUE, {162, 130, 78}}},
|
||||
{"minecraft:oak_fence_gate", {BLOCK_OPAQUE, {162, 130, 78}}},
|
||||
{"minecraft:oak_leaves", {BLOCK_OPAQUE|BLOCK_FOLIAGE, {144, 144, 144}}},
|
||||
{"minecraft:oak_log", {BLOCK_OPAQUE, {151, 121, 73}}},
|
||||
{"minecraft:oak_planks", {BLOCK_OPAQUE, {162, 130, 78}}},
|
||||
{"minecraft:oak_pressure_plate", {BLOCK_OPAQUE, {162, 130, 78}}},
|
||||
{"minecraft:oak_sapling", {BLOCK_OPAQUE, {77, 106, 40}}},
|
||||
{"minecraft:oak_sign", {BLOCK_OPAQUE, {162, 130, 78}}},
|
||||
{"minecraft:oak_slab", {BLOCK_OPAQUE, {162, 130, 78}}},
|
||||
{"minecraft:oak_stairs", {BLOCK_OPAQUE, {162, 130, 78}}},
|
||||
{"minecraft:oak_trapdoor", {BLOCK_OPAQUE, {124, 99, 56}}},
|
||||
{"minecraft:oak_wall_sign", {0, {0, 0, 0}}},
|
||||
{"minecraft:oak_wood", {BLOCK_OPAQUE, {109, 85, 50}}},
|
||||
{"minecraft:observer", {BLOCK_OPAQUE, {98, 98, 98}}},
|
||||
{"minecraft:obsidian", {BLOCK_OPAQUE, {15, 10, 24}}},
|
||||
{"minecraft:orange_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:orange_bed", {0, {0, 0, 0}}},
|
||||
{"minecraft:orange_candle", {0, {0, 0, 0}}},
|
||||
{"minecraft:orange_candle_cake", {BLOCK_OPAQUE, {248, 222, 214}}},
|
||||
{"minecraft:orange_carpet", {BLOCK_OPAQUE, {240, 118, 19}}},
|
||||
{"minecraft:orange_concrete", {BLOCK_OPAQUE, {224, 97, 0}}},
|
||||
{"minecraft:orange_concrete_powder", {BLOCK_OPAQUE, {227, 131, 31}}},
|
||||
{"minecraft:orange_glazed_terracotta", {BLOCK_OPAQUE, {154, 147, 91}}},
|
||||
{"minecraft:orange_shulker_box", {BLOCK_OPAQUE, {234, 106, 8}}},
|
||||
{"minecraft:orange_stained_glass", {BLOCK_OPAQUE, {216, 127, 51}}},
|
||||
{"minecraft:orange_stained_glass_pane", {BLOCK_OPAQUE, {208, 122, 48}}},
|
||||
{"minecraft:orange_terracotta", {BLOCK_OPAQUE, {161, 83, 37}}},
|
||||
{"minecraft:orange_tulip", {0, {0, 0, 0}}},
|
||||
{"minecraft:orange_wall_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:orange_wool", {BLOCK_OPAQUE, {240, 118, 19}}},
|
||||
{"minecraft:oxeye_daisy", {0, {0, 0, 0}}},
|
||||
{"minecraft:oxidized_copper", {BLOCK_OPAQUE, {82, 162, 132}}},
|
||||
{"minecraft:oxidized_cut_copper", {BLOCK_OPAQUE, {79, 153, 126}}},
|
||||
{"minecraft:oxidized_cut_copper_slab", {BLOCK_OPAQUE, {79, 153, 126}}},
|
||||
{"minecraft:oxidized_cut_copper_stairs", {BLOCK_OPAQUE, {79, 153, 126}}},
|
||||
{"minecraft:packed_ice", {BLOCK_OPAQUE, {141, 180, 250}}},
|
||||
{"minecraft:peony", {BLOCK_OPAQUE, {129, 126, 139}}},
|
||||
{"minecraft:petrified_oak_slab", {BLOCK_OPAQUE, {162, 130, 78}}},
|
||||
{"minecraft:pink_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:pink_bed", {0, {0, 0, 0}}},
|
||||
{"minecraft:pink_candle", {0, {0, 0, 0}}},
|
||||
{"minecraft:pink_candle_cake", {BLOCK_OPAQUE, {248, 222, 214}}},
|
||||
{"minecraft:pink_carpet", {BLOCK_OPAQUE, {237, 141, 172}}},
|
||||
{"minecraft:pink_concrete", {BLOCK_OPAQUE, {213, 101, 142}}},
|
||||
{"minecraft:pink_concrete_powder", {BLOCK_OPAQUE, {228, 153, 181}}},
|
||||
{"minecraft:pink_glazed_terracotta", {BLOCK_OPAQUE, {235, 154, 181}}},
|
||||
{"minecraft:pink_shulker_box", {BLOCK_OPAQUE, {230, 121, 157}}},
|
||||
{"minecraft:pink_stained_glass", {BLOCK_OPAQUE, {242, 127, 165}}},
|
||||
{"minecraft:pink_stained_glass_pane", {BLOCK_OPAQUE, {233, 122, 159}}},
|
||||
{"minecraft:pink_terracotta", {BLOCK_OPAQUE, {161, 78, 78}}},
|
||||
{"minecraft:pink_tulip", {0, {0, 0, 0}}},
|
||||
{"minecraft:pink_wall_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:pink_wool", {BLOCK_OPAQUE, {237, 141, 172}}},
|
||||
{"minecraft:piston", {BLOCK_OPAQUE, {110, 104, 96}}},
|
||||
{"minecraft:piston_head", {BLOCK_OPAQUE, {154, 127, 87}}},
|
||||
{"minecraft:player_head", {0, {0, 0, 0}}},
|
||||
{"minecraft:player_wall_head", {0, {0, 0, 0}}},
|
||||
{"minecraft:podzol", {BLOCK_OPAQUE, {91, 63, 24}}},
|
||||
{"minecraft:pointed_dripstone", {BLOCK_OPAQUE, {129, 102, 89}}},
|
||||
{"minecraft:polished_andesite", {BLOCK_OPAQUE, {132, 134, 133}}},
|
||||
{"minecraft:polished_andesite_slab", {BLOCK_OPAQUE, {132, 134, 133}}},
|
||||
{"minecraft:polished_andesite_stairs", {BLOCK_OPAQUE, {132, 134, 133}}},
|
||||
{"minecraft:polished_basalt", {BLOCK_OPAQUE, {99, 98, 100}}},
|
||||
{"minecraft:polished_blackstone", {BLOCK_OPAQUE, {53, 48, 56}}},
|
||||
{"minecraft:polished_blackstone_brick_slab", {BLOCK_OPAQUE, {48, 42, 49}}},
|
||||
{"minecraft:polished_blackstone_brick_stairs", {BLOCK_OPAQUE, {48, 42, 49}}},
|
||||
{"minecraft:polished_blackstone_brick_wall", {BLOCK_OPAQUE, {48, 42, 49}}},
|
||||
{"minecraft:polished_blackstone_bricks", {BLOCK_OPAQUE, {48, 42, 49}}},
|
||||
{"minecraft:polished_blackstone_button", {0, {0, 0, 0}}},
|
||||
{"minecraft:polished_blackstone_pressure_plate", {BLOCK_OPAQUE, {53, 48, 56}}},
|
||||
{"minecraft:polished_blackstone_slab", {BLOCK_OPAQUE, {53, 48, 56}}},
|
||||
{"minecraft:polished_blackstone_stairs", {BLOCK_OPAQUE, {53, 48, 56}}},
|
||||
{"minecraft:polished_blackstone_wall", {BLOCK_OPAQUE, {53, 48, 56}}},
|
||||
{"minecraft:polished_deepslate", {BLOCK_OPAQUE, {72, 72, 73}}},
|
||||
{"minecraft:polished_deepslate_slab", {BLOCK_OPAQUE, {72, 72, 73}}},
|
||||
{"minecraft:polished_deepslate_stairs", {BLOCK_OPAQUE, {72, 72, 73}}},
|
||||
{"minecraft:polished_deepslate_wall", {BLOCK_OPAQUE, {72, 72, 73}}},
|
||||
{"minecraft:polished_diorite", {BLOCK_OPAQUE, {192, 193, 194}}},
|
||||
{"minecraft:polished_diorite_slab", {BLOCK_OPAQUE, {192, 193, 194}}},
|
||||
{"minecraft:polished_diorite_stairs", {BLOCK_OPAQUE, {192, 193, 194}}},
|
||||
{"minecraft:polished_granite", {BLOCK_OPAQUE, {154, 106, 89}}},
|
||||
{"minecraft:polished_granite_slab", {BLOCK_OPAQUE, {154, 106, 89}}},
|
||||
{"minecraft:polished_granite_stairs", {BLOCK_OPAQUE, {154, 106, 89}}},
|
||||
{"minecraft:poppy", {0, {0, 0, 0}}},
|
||||
{"minecraft:potatoes", {BLOCK_OPAQUE, {84, 135, 47}}},
|
||||
{"minecraft:potted_acacia_sapling", {BLOCK_OPAQUE, {118, 117, 23}}},
|
||||
{"minecraft:potted_allium", {BLOCK_OPAQUE, {158, 137, 183}}},
|
||||
{"minecraft:potted_azalea_bush", {BLOCK_OPAQUE, {101, 124, 47}}},
|
||||
{"minecraft:potted_azure_bluet", {BLOCK_OPAQUE, {169, 204, 127}}},
|
||||
{"minecraft:potted_bamboo", {BLOCK_OPAQUE, {93, 144, 19}}},
|
||||
{"minecraft:potted_birch_sapling", {BLOCK_OPAQUE, {127, 160, 79}}},
|
||||
{"minecraft:potted_blue_orchid", {BLOCK_OPAQUE, {47, 162, 168}}},
|
||||
{"minecraft:potted_brown_mushroom", {BLOCK_OPAQUE, {153, 116, 92}}},
|
||||
{"minecraft:potted_cactus", {BLOCK_OPAQUE, {85, 127, 43}}},
|
||||
{"minecraft:potted_cornflower", {BLOCK_OPAQUE, {79, 121, 146}}},
|
||||
{"minecraft:potted_crimson_fungus", {BLOCK_OPAQUE, {141, 44, 29}}},
|
||||
{"minecraft:potted_crimson_roots", {BLOCK_OPAQUE, {127, 8, 41}}},
|
||||
{"minecraft:potted_dandelion", {BLOCK_OPAQUE, {147, 172, 43}}},
|
||||
{"minecraft:potted_dark_oak_sapling", {BLOCK_OPAQUE, {61, 90, 30}}},
|
||||
{"minecraft:potted_dead_bush", {BLOCK_OPAQUE, {107, 78, 40}}},
|
||||
{"minecraft:potted_fern", {BLOCK_OPAQUE|BLOCK_GRASS, {124, 124, 124}}},
|
||||
{"minecraft:potted_flowering_azalea_bush", {BLOCK_OPAQUE, {112, 121, 64}}},
|
||||
{"minecraft:potted_jungle_sapling", {BLOCK_OPAQUE, {47, 81, 16}}},
|
||||
{"minecraft:potted_lily_of_the_valley", {BLOCK_OPAQUE, {123, 174, 95}}},
|
||||
{"minecraft:potted_oak_sapling", {BLOCK_OPAQUE, {77, 106, 40}}},
|
||||
{"minecraft:potted_orange_tulip", {BLOCK_OPAQUE, {93, 142, 30}}},
|
||||
{"minecraft:potted_oxeye_daisy", {BLOCK_OPAQUE, {179, 202, 143}}},
|
||||
{"minecraft:potted_pink_tulip", {BLOCK_OPAQUE, {99, 157, 78}}},
|
||||
{"minecraft:potted_poppy", {BLOCK_OPAQUE, {128, 64, 37}}},
|
||||
{"minecraft:potted_red_mushroom", {BLOCK_OPAQUE, {216, 75, 67}}},
|
||||
{"minecraft:potted_red_tulip", {BLOCK_OPAQUE, {89, 128, 32}}},
|
||||
{"minecraft:potted_spruce_sapling", {BLOCK_OPAQUE, {44, 60, 36}}},
|
||||
{"minecraft:potted_warped_fungus", {BLOCK_OPAQUE, {74, 109, 87}}},
|
||||
{"minecraft:potted_warped_roots", {BLOCK_OPAQUE, {20, 136, 123}}},
|
||||
{"minecraft:potted_white_tulip", {BLOCK_OPAQUE, {93, 164, 71}}},
|
||||
{"minecraft:potted_wither_rose", {BLOCK_OPAQUE, {41, 44, 23}}},
|
||||
{"minecraft:powder_snow", {BLOCK_OPAQUE, {248, 253, 253}}},
|
||||
{"minecraft:powder_snow_cauldron", {BLOCK_OPAQUE, {73, 72, 74}}},
|
||||
{"minecraft:powered_rail", {BLOCK_OPAQUE, {137, 109, 74}}},
|
||||
{"minecraft:prismarine", {BLOCK_OPAQUE, {99, 156, 151}}},
|
||||
{"minecraft:prismarine_brick_slab", {BLOCK_OPAQUE, {99, 171, 158}}},
|
||||
{"minecraft:prismarine_brick_stairs", {BLOCK_OPAQUE, {99, 171, 158}}},
|
||||
{"minecraft:prismarine_bricks", {BLOCK_OPAQUE, {99, 171, 158}}},
|
||||
{"minecraft:prismarine_slab", {BLOCK_OPAQUE, {99, 156, 151}}},
|
||||
{"minecraft:prismarine_stairs", {BLOCK_OPAQUE, {99, 156, 151}}},
|
||||
{"minecraft:prismarine_wall", {BLOCK_OPAQUE, {99, 156, 151}}},
|
||||
{"minecraft:pumpkin", {BLOCK_OPAQUE, {198, 118, 24}}},
|
||||
{"minecraft:pumpkin_stem", {BLOCK_OPAQUE|BLOCK_GRASS, {154, 154, 154}}},
|
||||
{"minecraft:purple_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:purple_bed", {0, {0, 0, 0}}},
|
||||
{"minecraft:purple_candle", {0, {0, 0, 0}}},
|
||||
{"minecraft:purple_candle_cake", {BLOCK_OPAQUE, {248, 222, 214}}},
|
||||
{"minecraft:purple_carpet", {BLOCK_OPAQUE, {121, 42, 172}}},
|
||||
{"minecraft:purple_concrete", {BLOCK_OPAQUE, {100, 31, 156}}},
|
||||
{"minecraft:purple_concrete_powder", {BLOCK_OPAQUE, {131, 55, 177}}},
|
||||
{"minecraft:purple_glazed_terracotta", {BLOCK_OPAQUE, {109, 48, 152}}},
|
||||
{"minecraft:purple_shulker_box", {BLOCK_OPAQUE, {103, 32, 156}}},
|
||||
{"minecraft:purple_stained_glass", {BLOCK_OPAQUE, {127, 63, 178}}},
|
||||
{"minecraft:purple_stained_glass_pane", {BLOCK_OPAQUE, {122, 61, 171}}},
|
||||
{"minecraft:purple_terracotta", {BLOCK_OPAQUE, {118, 70, 86}}},
|
||||
{"minecraft:purple_wall_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:purple_wool", {BLOCK_OPAQUE, {121, 42, 172}}},
|
||||
{"minecraft:purpur_block", {BLOCK_OPAQUE, {169, 125, 169}}},
|
||||
{"minecraft:purpur_pillar", {BLOCK_OPAQUE, {171, 129, 171}}},
|
||||
{"minecraft:purpur_slab", {BLOCK_OPAQUE, {169, 125, 169}}},
|
||||
{"minecraft:purpur_stairs", {BLOCK_OPAQUE, {169, 125, 169}}},
|
||||
{"minecraft:quartz_block", {BLOCK_OPAQUE, {235, 229, 222}}},
|
||||
{"minecraft:quartz_bricks", {BLOCK_OPAQUE, {234, 229, 221}}},
|
||||
{"minecraft:quartz_pillar", {BLOCK_OPAQUE, {235, 230, 224}}},
|
||||
{"minecraft:quartz_slab", {BLOCK_OPAQUE, {235, 229, 222}}},
|
||||
{"minecraft:quartz_stairs", {BLOCK_OPAQUE, {235, 229, 222}}},
|
||||
{"minecraft:rail", {BLOCK_OPAQUE, {125, 111, 88}}},
|
||||
{"minecraft:raw_copper_block", {BLOCK_OPAQUE, {154, 105, 79}}},
|
||||
{"minecraft:raw_gold_block", {BLOCK_OPAQUE, {221, 169, 46}}},
|
||||
{"minecraft:raw_iron_block", {BLOCK_OPAQUE, {166, 135, 107}}},
|
||||
{"minecraft:red_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:red_bed", {0, {0, 0, 0}}},
|
||||
{"minecraft:red_candle", {0, {0, 0, 0}}},
|
||||
{"minecraft:red_candle_cake", {BLOCK_OPAQUE, {248, 222, 214}}},
|
||||
{"minecraft:red_carpet", {BLOCK_OPAQUE, {160, 39, 34}}},
|
||||
{"minecraft:red_concrete", {BLOCK_OPAQUE, {142, 32, 32}}},
|
||||
{"minecraft:red_concrete_powder", {BLOCK_OPAQUE, {168, 54, 50}}},
|
||||
{"minecraft:red_glazed_terracotta", {BLOCK_OPAQUE, {181, 59, 53}}},
|
||||
{"minecraft:red_mushroom", {0, {0, 0, 0}}},
|
||||
{"minecraft:red_mushroom_block", {BLOCK_OPAQUE, {200, 46, 45}}},
|
||||
{"minecraft:red_nether_brick_slab", {BLOCK_OPAQUE, {69, 7, 9}}},
|
||||
{"minecraft:red_nether_brick_stairs", {BLOCK_OPAQUE, {69, 7, 9}}},
|
||||
{"minecraft:red_nether_brick_wall", {BLOCK_OPAQUE, {69, 7, 9}}},
|
||||
{"minecraft:red_nether_bricks", {BLOCK_OPAQUE, {69, 7, 9}}},
|
||||
{"minecraft:red_sand", {BLOCK_OPAQUE, {190, 102, 33}}},
|
||||
{"minecraft:red_sandstone", {BLOCK_OPAQUE, {181, 97, 31}}},
|
||||
{"minecraft:red_sandstone_slab", {BLOCK_OPAQUE, {181, 97, 31}}},
|
||||
{"minecraft:red_sandstone_stairs", {BLOCK_OPAQUE, {181, 97, 31}}},
|
||||
{"minecraft:red_sandstone_wall", {BLOCK_OPAQUE, {181, 97, 31}}},
|
||||
{"minecraft:red_shulker_box", {BLOCK_OPAQUE, {140, 31, 30}}},
|
||||
{"minecraft:red_stained_glass", {BLOCK_OPAQUE, {153, 51, 51}}},
|
||||
{"minecraft:red_stained_glass_pane", {BLOCK_OPAQUE, {147, 48, 48}}},
|
||||
{"minecraft:red_terracotta", {BLOCK_OPAQUE, {143, 61, 46}}},
|
||||
{"minecraft:red_tulip", {0, {0, 0, 0}}},
|
||||
{"minecraft:red_wall_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:red_wool", {BLOCK_OPAQUE, {160, 39, 34}}},
|
||||
{"minecraft:redstone_block", {BLOCK_OPAQUE, {175, 24, 5}}},
|
||||
{"minecraft:redstone_lamp", {BLOCK_OPAQUE, {95, 54, 30}}},
|
||||
{"minecraft:redstone_ore", {BLOCK_OPAQUE, {140, 109, 109}}},
|
||||
{"minecraft:redstone_torch", {0, {0, 0, 0}}},
|
||||
{"minecraft:redstone_wall_torch", {0, {0, 0, 0}}},
|
||||
{"minecraft:redstone_wire", {BLOCK_OPAQUE, {175, 24, 5}}},
|
||||
{"minecraft:repeater", {BLOCK_OPAQUE, {160, 157, 156}}},
|
||||
{"minecraft:repeating_command_block", {BLOCK_OPAQUE, {129, 111, 176}}},
|
||||
{"minecraft:respawn_anchor", {BLOCK_OPAQUE, {75, 26, 144}}},
|
||||
{"minecraft:rooted_dirt", {BLOCK_OPAQUE, {144, 103, 76}}},
|
||||
{"minecraft:rose_bush", {BLOCK_OPAQUE, {131, 66, 37}}},
|
||||
{"minecraft:sand", {BLOCK_OPAQUE, {219, 207, 163}}},
|
||||
{"minecraft:sandstone", {BLOCK_OPAQUE, {223, 214, 170}}},
|
||||
{"minecraft:sandstone_slab", {BLOCK_OPAQUE, {223, 214, 170}}},
|
||||
{"minecraft:sandstone_stairs", {BLOCK_OPAQUE, {223, 214, 170}}},
|
||||
{"minecraft:sandstone_wall", {BLOCK_OPAQUE, {223, 214, 170}}},
|
||||
{"minecraft:scaffolding", {BLOCK_OPAQUE, {174, 134, 80}}},
|
||||
{"minecraft:sculk_sensor", {BLOCK_OPAQUE, {7, 70, 84}}},
|
||||
{"minecraft:sea_lantern", {BLOCK_OPAQUE, {172, 199, 190}}},
|
||||
{"minecraft:sea_pickle", {BLOCK_OPAQUE, {90, 97, 39}}},
|
||||
{"minecraft:seagrass", {0, {0, 0, 0}}},
|
||||
{"minecraft:shroomlight", {BLOCK_OPAQUE, {240, 146, 70}}},
|
||||
{"minecraft:shulker_box", {BLOCK_OPAQUE, {139, 96, 139}}},
|
||||
{"minecraft:sign", {BLOCK_OPAQUE, {162, 130, 78}}},
|
||||
{"minecraft:skeleton_skull", {0, {0, 0, 0}}},
|
||||
{"minecraft:skeleton_wall_skull", {0, {0, 0, 0}}},
|
||||
{"minecraft:slime_block", {BLOCK_OPAQUE, {111, 192, 91}}},
|
||||
{"minecraft:small_amethyst_bud", {0, {0, 0, 0}}},
|
||||
{"minecraft:small_dripleaf", {0, {0, 0, 0}}},
|
||||
{"minecraft:smithing_table", {BLOCK_OPAQUE, {57, 58, 70}}},
|
||||
{"minecraft:smoker", {BLOCK_OPAQUE, {85, 83, 81}}},
|
||||
{"minecraft:smooth_basalt", {BLOCK_OPAQUE, {72, 72, 78}}},
|
||||
{"minecraft:smooth_quartz", {BLOCK_OPAQUE, {235, 229, 222}}},
|
||||
{"minecraft:smooth_quartz_slab", {BLOCK_OPAQUE, {235, 229, 222}}},
|
||||
{"minecraft:smooth_quartz_stairs", {BLOCK_OPAQUE, {235, 229, 222}}},
|
||||
{"minecraft:smooth_red_sandstone", {BLOCK_OPAQUE, {181, 97, 31}}},
|
||||
{"minecraft:smooth_red_sandstone_slab", {BLOCK_OPAQUE, {181, 97, 31}}},
|
||||
{"minecraft:smooth_red_sandstone_stairs", {BLOCK_OPAQUE, {181, 97, 31}}},
|
||||
{"minecraft:smooth_sandstone", {BLOCK_OPAQUE, {223, 214, 170}}},
|
||||
{"minecraft:smooth_sandstone_slab", {BLOCK_OPAQUE, {223, 214, 170}}},
|
||||
{"minecraft:smooth_sandstone_stairs", {BLOCK_OPAQUE, {223, 214, 170}}},
|
||||
{"minecraft:smooth_stone", {BLOCK_OPAQUE, {158, 158, 158}}},
|
||||
{"minecraft:smooth_stone_slab", {BLOCK_OPAQUE, {158, 158, 158}}},
|
||||
{"minecraft:snow", {BLOCK_OPAQUE, {249, 254, 254}}},
|
||||
{"minecraft:snow_block", {BLOCK_OPAQUE, {249, 254, 254}}},
|
||||
{"minecraft:soul_campfire", {BLOCK_OPAQUE, {80, 204, 208}}},
|
||||
{"minecraft:soul_fire", {BLOCK_OPAQUE, {51, 192, 197}}},
|
||||
{"minecraft:soul_lantern", {BLOCK_OPAQUE, {71, 99, 114}}},
|
||||
{"minecraft:soul_sand", {BLOCK_OPAQUE, {81, 62, 50}}},
|
||||
{"minecraft:soul_soil", {BLOCK_OPAQUE, {75, 57, 46}}},
|
||||
{"minecraft:soul_torch", {0, {0, 0, 0}}},
|
||||
{"minecraft:soul_wall_torch", {0, {0, 0, 0}}},
|
||||
{"minecraft:spawner", {BLOCK_OPAQUE, {36, 46, 62}}},
|
||||
{"minecraft:sponge", {BLOCK_OPAQUE, {195, 192, 74}}},
|
||||
{"minecraft:spore_blossom", {BLOCK_OPAQUE, {206, 96, 158}}},
|
||||
{"minecraft:spruce_button", {0, {0, 0, 0}}},
|
||||
{"minecraft:spruce_door", {BLOCK_OPAQUE, {106, 80, 48}}},
|
||||
{"minecraft:spruce_fence", {BLOCK_OPAQUE, {114, 84, 48}}},
|
||||
{"minecraft:spruce_fence_gate", {BLOCK_OPAQUE, {114, 84, 48}}},
|
||||
{"minecraft:spruce_leaves", {BLOCK_OPAQUE|BLOCK_SPRUCE, {126, 126, 126}}},
|
||||
{"minecraft:spruce_log", {BLOCK_OPAQUE, {108, 80, 46}}},
|
||||
{"minecraft:spruce_planks", {BLOCK_OPAQUE, {114, 84, 48}}},
|
||||
{"minecraft:spruce_pressure_plate", {BLOCK_OPAQUE, {114, 84, 48}}},
|
||||
{"minecraft:spruce_sapling", {BLOCK_OPAQUE, {44, 60, 36}}},
|
||||
{"minecraft:spruce_sign", {BLOCK_OPAQUE, {114, 84, 48}}},
|
||||
{"minecraft:spruce_slab", {BLOCK_OPAQUE, {114, 84, 48}}},
|
||||
{"minecraft:spruce_stairs", {BLOCK_OPAQUE, {114, 84, 48}}},
|
||||
{"minecraft:spruce_trapdoor", {BLOCK_OPAQUE, {103, 79, 47}}},
|
||||
{"minecraft:spruce_wall_sign", {0, {0, 0, 0}}},
|
||||
{"minecraft:spruce_wood", {BLOCK_OPAQUE, {58, 37, 16}}},
|
||||
{"minecraft:sticky_piston", {BLOCK_OPAQUE, {110, 104, 96}}},
|
||||
{"minecraft:stone", {BLOCK_OPAQUE, {125, 125, 125}}},
|
||||
{"minecraft:stone_brick_slab", {BLOCK_OPAQUE, {122, 121, 122}}},
|
||||
{"minecraft:stone_brick_stairs", {BLOCK_OPAQUE, {122, 121, 122}}},
|
||||
{"minecraft:stone_brick_wall", {BLOCK_OPAQUE, {122, 121, 122}}},
|
||||
{"minecraft:stone_bricks", {BLOCK_OPAQUE, {122, 121, 122}}},
|
||||
{"minecraft:stone_button", {0, {0, 0, 0}}},
|
||||
{"minecraft:stone_pressure_plate", {BLOCK_OPAQUE, {125, 125, 125}}},
|
||||
{"minecraft:stone_slab", {BLOCK_OPAQUE, {125, 125, 125}}},
|
||||
{"minecraft:stone_stairs", {BLOCK_OPAQUE, {125, 125, 125}}},
|
||||
{"minecraft:stonecutter", {BLOCK_OPAQUE, {123, 118, 111}}},
|
||||
{"minecraft:stripped_acacia_log", {BLOCK_OPAQUE, {166, 91, 51}}},
|
||||
{"minecraft:stripped_acacia_wood", {BLOCK_OPAQUE, {174, 92, 59}}},
|
||||
{"minecraft:stripped_birch_log", {BLOCK_OPAQUE, {191, 171, 116}}},
|
||||
{"minecraft:stripped_birch_wood", {BLOCK_OPAQUE, {196, 176, 118}}},
|
||||
{"minecraft:stripped_crimson_hyphae", {BLOCK_OPAQUE, {137, 57, 90}}},
|
||||
{"minecraft:stripped_crimson_stem", {BLOCK_OPAQUE, {121, 56, 82}}},
|
||||
{"minecraft:stripped_dark_oak_log", {BLOCK_OPAQUE, {65, 44, 22}}},
|
||||
{"minecraft:stripped_dark_oak_wood", {BLOCK_OPAQUE, {72, 56, 36}}},
|
||||
{"minecraft:stripped_jungle_log", {BLOCK_OPAQUE, {165, 122, 81}}},
|
||||
{"minecraft:stripped_jungle_wood", {BLOCK_OPAQUE, {171, 132, 84}}},
|
||||
{"minecraft:stripped_oak_log", {BLOCK_OPAQUE, {160, 129, 77}}},
|
||||
{"minecraft:stripped_oak_wood", {BLOCK_OPAQUE, {177, 144, 86}}},
|
||||
{"minecraft:stripped_spruce_log", {BLOCK_OPAQUE, {105, 80, 46}}},
|
||||
{"minecraft:stripped_spruce_wood", {BLOCK_OPAQUE, {115, 89, 52}}},
|
||||
{"minecraft:stripped_warped_hyphae", {BLOCK_OPAQUE, {57, 150, 147}}},
|
||||
{"minecraft:stripped_warped_stem", {BLOCK_OPAQUE, {52, 128, 124}}},
|
||||
{"minecraft:structure_block", {BLOCK_OPAQUE, {88, 74, 90}}},
|
||||
{"minecraft:structure_void", {0, {0, 0, 0}}},
|
||||
{"minecraft:sugar_cane", {BLOCK_OPAQUE, {148, 192, 101}}},
|
||||
{"minecraft:sunflower", {BLOCK_OPAQUE, {246, 196, 54}}},
|
||||
{"minecraft:sweet_berry_bush", {BLOCK_OPAQUE, {68, 77, 50}}},
|
||||
{"minecraft:tall_grass", {BLOCK_OPAQUE|BLOCK_GRASS, {151, 149, 151}}},
|
||||
{"minecraft:tall_seagrass", {BLOCK_OPAQUE, {59, 139, 14}}},
|
||||
{"minecraft:target", {BLOCK_OPAQUE, {226, 170, 157}}},
|
||||
{"minecraft:terracotta", {BLOCK_OPAQUE, {152, 94, 67}}},
|
||||
{"minecraft:tinted_glass", {BLOCK_OPAQUE, {44, 38, 46}}},
|
||||
{"minecraft:tnt", {BLOCK_OPAQUE, {142, 62, 53}}},
|
||||
{"minecraft:torch", {0, {0, 0, 0}}},
|
||||
{"minecraft:trapped_chest", {BLOCK_OPAQUE, {162, 130, 78}}},
|
||||
{"minecraft:tripwire", {0, {0, 0, 0}}},
|
||||
{"minecraft:tripwire_hook", {0, {0, 0, 0}}},
|
||||
{"minecraft:tube_coral", {0, {0, 0, 0}}},
|
||||
{"minecraft:tube_coral_block", {BLOCK_OPAQUE, {49, 87, 206}}},
|
||||
{"minecraft:tube_coral_fan", {0, {0, 0, 0}}},
|
||||
{"minecraft:tube_coral_wall_fan", {0, {0, 0, 0}}},
|
||||
{"minecraft:tuff", {BLOCK_OPAQUE, {108, 109, 102}}},
|
||||
{"minecraft:turtle_egg", {BLOCK_OPAQUE, {228, 226, 191}}},
|
||||
{"minecraft:twisting_vines", {BLOCK_OPAQUE, {20, 143, 124}}},
|
||||
{"minecraft:twisting_vines_plant", {BLOCK_OPAQUE, {20, 135, 122}}},
|
||||
{"minecraft:vine", {BLOCK_OPAQUE|BLOCK_GRASS, {116, 116, 116}}},
|
||||
{"minecraft:void_air", {0, {0, 0, 0}}},
|
||||
{"minecraft:wall_sign", {0, {0, 0, 0}}},
|
||||
{"minecraft:wall_torch", {0, {0, 0, 0}}},
|
||||
{"minecraft:warped_button", {0, {0, 0, 0}}},
|
||||
{"minecraft:warped_door", {BLOCK_OPAQUE, {44, 126, 120}}},
|
||||
{"minecraft:warped_fence", {BLOCK_OPAQUE, {43, 104, 99}}},
|
||||
{"minecraft:warped_fence_gate", {BLOCK_OPAQUE, {43, 104, 99}}},
|
||||
{"minecraft:warped_fungus", {0, {0, 0, 0}}},
|
||||
{"minecraft:warped_hyphae", {BLOCK_OPAQUE, {58, 58, 77}}},
|
||||
{"minecraft:warped_nylium", {BLOCK_OPAQUE, {43, 114, 101}}},
|
||||
{"minecraft:warped_planks", {BLOCK_OPAQUE, {43, 104, 99}}},
|
||||
{"minecraft:warped_pressure_plate", {BLOCK_OPAQUE, {43, 104, 99}}},
|
||||
{"minecraft:warped_roots", {BLOCK_OPAQUE, {20, 138, 124}}},
|
||||
{"minecraft:warped_sign", {BLOCK_OPAQUE, {43, 104, 99}}},
|
||||
{"minecraft:warped_slab", {BLOCK_OPAQUE, {43, 104, 99}}},
|
||||
{"minecraft:warped_stairs", {BLOCK_OPAQUE, {43, 104, 99}}},
|
||||
{"minecraft:warped_stem", {BLOCK_OPAQUE, {53, 109, 110}}},
|
||||
{"minecraft:warped_trapdoor", {BLOCK_OPAQUE, {47, 119, 111}}},
|
||||
{"minecraft:warped_wall_sign", {0, {0, 0, 0}}},
|
||||
{"minecraft:warped_wart_block", {BLOCK_OPAQUE, {22, 119, 121}}},
|
||||
{"minecraft:water", {BLOCK_OPAQUE|BLOCK_WATER, {177, 177, 177}}},
|
||||
{"minecraft:water_cauldron", {BLOCK_OPAQUE, {73, 72, 74}}},
|
||||
{"minecraft:waxed_copper_block", {BLOCK_OPAQUE, {192, 107, 79}}},
|
||||
{"minecraft:waxed_cut_copper", {BLOCK_OPAQUE, {191, 106, 80}}},
|
||||
{"minecraft:waxed_cut_copper_slab", {BLOCK_OPAQUE, {191, 106, 80}}},
|
||||
{"minecraft:waxed_cut_copper_stairs", {BLOCK_OPAQUE, {191, 106, 80}}},
|
||||
{"minecraft:waxed_exposed_copper", {BLOCK_OPAQUE, {161, 125, 103}}},
|
||||
{"minecraft:waxed_exposed_cut_copper", {BLOCK_OPAQUE, {154, 121, 101}}},
|
||||
{"minecraft:waxed_exposed_cut_copper_slab", {BLOCK_OPAQUE, {154, 121, 101}}},
|
||||
{"minecraft:waxed_exposed_cut_copper_stairs", {BLOCK_OPAQUE, {154, 121, 101}}},
|
||||
{"minecraft:waxed_oxidized_copper", {BLOCK_OPAQUE, {82, 162, 132}}},
|
||||
{"minecraft:waxed_oxidized_cut_copper", {BLOCK_OPAQUE, {79, 153, 126}}},
|
||||
{"minecraft:waxed_oxidized_cut_copper_slab", {BLOCK_OPAQUE, {79, 153, 126}}},
|
||||
{"minecraft:waxed_oxidized_cut_copper_stairs", {BLOCK_OPAQUE, {79, 153, 126}}},
|
||||
{"minecraft:waxed_weathered_copper", {BLOCK_OPAQUE, {108, 153, 110}}},
|
||||
{"minecraft:waxed_weathered_cut_copper", {BLOCK_OPAQUE, {109, 145, 107}}},
|
||||
{"minecraft:waxed_weathered_cut_copper_slab", {BLOCK_OPAQUE, {109, 145, 107}}},
|
||||
{"minecraft:waxed_weathered_cut_copper_stairs", {BLOCK_OPAQUE, {109, 145, 107}}},
|
||||
{"minecraft:weathered_copper", {BLOCK_OPAQUE, {108, 153, 110}}},
|
||||
{"minecraft:weathered_cut_copper", {BLOCK_OPAQUE, {109, 145, 107}}},
|
||||
{"minecraft:weathered_cut_copper_slab", {BLOCK_OPAQUE, {109, 145, 107}}},
|
||||
{"minecraft:weathered_cut_copper_stairs", {BLOCK_OPAQUE, {109, 145, 107}}},
|
||||
{"minecraft:weeping_vines", {BLOCK_OPAQUE, {104, 1, 0}}},
|
||||
{"minecraft:weeping_vines_plant", {BLOCK_OPAQUE, {132, 16, 12}}},
|
||||
{"minecraft:wet_sponge", {BLOCK_OPAQUE, {171, 181, 70}}},
|
||||
{"minecraft:wheat", {BLOCK_OPAQUE, {166, 151, 73}}},
|
||||
{"minecraft:white_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:white_bed", {0, {0, 0, 0}}},
|
||||
{"minecraft:white_candle", {0, {0, 0, 0}}},
|
||||
{"minecraft:white_candle_cake", {BLOCK_OPAQUE, {248, 222, 214}}},
|
||||
{"minecraft:white_carpet", {BLOCK_OPAQUE, {233, 236, 236}}},
|
||||
{"minecraft:white_concrete", {BLOCK_OPAQUE, {207, 213, 214}}},
|
||||
{"minecraft:white_concrete_powder", {BLOCK_OPAQUE, {225, 227, 227}}},
|
||||
{"minecraft:white_glazed_terracotta", {BLOCK_OPAQUE, {188, 212, 202}}},
|
||||
{"minecraft:white_shulker_box", {BLOCK_OPAQUE, {215, 220, 221}}},
|
||||
{"minecraft:white_stained_glass", {BLOCK_OPAQUE, {255, 255, 255}}},
|
||||
{"minecraft:white_stained_glass_pane", {BLOCK_OPAQUE, {246, 246, 246}}},
|
||||
{"minecraft:white_terracotta", {BLOCK_OPAQUE, {209, 178, 161}}},
|
||||
{"minecraft:white_tulip", {0, {0, 0, 0}}},
|
||||
{"minecraft:white_wall_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:white_wool", {BLOCK_OPAQUE, {233, 236, 236}}},
|
||||
{"minecraft:wither_rose", {0, {0, 0, 0}}},
|
||||
{"minecraft:wither_skeleton_skull", {0, {0, 0, 0}}},
|
||||
{"minecraft:wither_skeleton_wall_skull", {0, {0, 0, 0}}},
|
||||
{"minecraft:yellow_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:yellow_bed", {0, {0, 0, 0}}},
|
||||
{"minecraft:yellow_candle", {0, {0, 0, 0}}},
|
||||
{"minecraft:yellow_candle_cake", {BLOCK_OPAQUE, {248, 222, 214}}},
|
||||
{"minecraft:yellow_carpet", {BLOCK_OPAQUE, {248, 197, 39}}},
|
||||
{"minecraft:yellow_concrete", {BLOCK_OPAQUE, {240, 175, 21}}},
|
||||
{"minecraft:yellow_concrete_powder", {BLOCK_OPAQUE, {232, 199, 54}}},
|
||||
{"minecraft:yellow_glazed_terracotta", {BLOCK_OPAQUE, {234, 192, 88}}},
|
||||
{"minecraft:yellow_shulker_box", {BLOCK_OPAQUE, {248, 188, 29}}},
|
||||
{"minecraft:yellow_stained_glass", {BLOCK_OPAQUE, {229, 229, 51}}},
|
||||
{"minecraft:yellow_stained_glass_pane", {BLOCK_OPAQUE, {221, 221, 48}}},
|
||||
{"minecraft:yellow_terracotta", {BLOCK_OPAQUE, {186, 133, 35}}},
|
||||
{"minecraft:yellow_wall_banner", {0, {0, 0, 0}}},
|
||||
{"minecraft:yellow_wool", {BLOCK_OPAQUE, {248, 197, 39}}},
|
||||
{"minecraft:zombie_head", {0, {0, 0, 0}}},
|
||||
{"minecraft:zombie_wall_head", {0, {0, 0, 0}}},
|
|
@ -1,52 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2018, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace Resource {
|
||||
|
||||
struct FloatColor {
|
||||
float r, g, b;
|
||||
};
|
||||
|
||||
static inline FloatColor operator+(const FloatColor &a, const FloatColor &b){
|
||||
return FloatColor {
|
||||
a.r+b.r,
|
||||
a.g+b.g,
|
||||
a.b+b.b,
|
||||
};
|
||||
}
|
||||
|
||||
static inline FloatColor & operator*=(FloatColor &a, const FloatColor &b) {
|
||||
a.r *= b.r;
|
||||
a.g *= b.g;
|
||||
a.b *= b.b;
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
static inline FloatColor operator*(float s, const FloatColor &c) {
|
||||
return FloatColor {
|
||||
s*c.r,
|
||||
s*c.g,
|
||||
s*c.b,
|
||||
};
|
||||
}
|
||||
|
||||
struct Color {
|
||||
uint8_t r, g, b, a;
|
||||
|
||||
Color() : r(0), g(0), b(0), a(0) {}
|
||||
Color(FloatColor c) : r(c.r), g(c.g), b(c.b), a(0xff) {}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -1,597 +0,0 @@
|
|||
/* 0 */ simple("air"),
|
||||
/* 1 */ {
|
||||
"stone",
|
||||
"granite",
|
||||
"polished_granite",
|
||||
"diorite",
|
||||
"polished_diorite",
|
||||
"andesite",
|
||||
"polished_andesite",
|
||||
},
|
||||
/* 2 */ simple("grass_block"),
|
||||
/* 3 */ {
|
||||
"dirt",
|
||||
"coarse_dirt",
|
||||
"podzol",
|
||||
},
|
||||
/* 4 */ simple("cobblestone"),
|
||||
/* 5 */ {
|
||||
"oak_planks",
|
||||
"spruce_planks",
|
||||
"birch_planks",
|
||||
"jungle_planks",
|
||||
"acacia_planks",
|
||||
"dark_oak_planks",
|
||||
},
|
||||
/* 6 */ {
|
||||
"oak_sapling",
|
||||
"spruce_sapling",
|
||||
"birch_sapling",
|
||||
"jungle_sapling",
|
||||
"acacia_sapling",
|
||||
"dark_oak_sapling",
|
||||
nullptr,
|
||||
nullptr,
|
||||
"oak_sapling",
|
||||
"spruce_sapling",
|
||||
"birch_sapling",
|
||||
"jungle_sapling",
|
||||
"acacia_sapling",
|
||||
"dark_oak_sapling",
|
||||
},
|
||||
/* 7 */ simple("bedrock"),
|
||||
/* 8 */ simple("water"),
|
||||
/* 9 */ simple("water"),
|
||||
/* 10 */ simple("lava"),
|
||||
/* 11 */ simple("lava"),
|
||||
/* 12 */ {
|
||||
"sand",
|
||||
"red_sand",
|
||||
},
|
||||
/* 13 */ simple("gravel"),
|
||||
/* 14 */ simple("gold_ore"),
|
||||
/* 15 */ simple("iron_ore"),
|
||||
/* 16 */ simple("coal_ore"),
|
||||
/* 17 */ {
|
||||
"oak_log",
|
||||
"spruce_log",
|
||||
"birch_log",
|
||||
"jungle_log",
|
||||
"oak_log",
|
||||
"spruce_log",
|
||||
"birch_log",
|
||||
"jungle_log",
|
||||
"oak_log",
|
||||
"spruce_log",
|
||||
"birch_log",
|
||||
"jungle_log",
|
||||
"oak_log",
|
||||
"spruce_log",
|
||||
"birch_log",
|
||||
"jungle_log",
|
||||
},
|
||||
/* 18 */ {
|
||||
"oak_leaves",
|
||||
"spruce_leaves",
|
||||
"birch_leaves",
|
||||
"jungle_leaves",
|
||||
"oak_leaves",
|
||||
"spruce_leaves",
|
||||
"birch_leaves",
|
||||
"jungle_leaves",
|
||||
"oak_leaves",
|
||||
"spruce_leaves",
|
||||
"birch_leaves",
|
||||
"jungle_leaves",
|
||||
"oak_leaves",
|
||||
"spruce_leaves",
|
||||
"birch_leaves",
|
||||
"jungle_leaves",
|
||||
},
|
||||
/* 19 */ simple("sponge"),
|
||||
/* 20 */ simple("glass"),
|
||||
/* 21 */ simple("lapis_ore"),
|
||||
/* 22 */ simple("lapis_block"),
|
||||
/* 23 */ simple("dispenser"),
|
||||
/* 24 */ simple("sandstone"),
|
||||
/* 25 */ simple("note_block"),
|
||||
/* 26 */ {/* bed */},
|
||||
/* 27 */ simple("powered_rail"),
|
||||
/* 28 */ simple("detector_rail"),
|
||||
/* 29 */ simple("sticky_piston"),
|
||||
/* 30 */ simple("cobweb"),
|
||||
/* 31 */ {
|
||||
"grass",
|
||||
"fern",
|
||||
},
|
||||
/* 32 */ simple("dead_bush"),
|
||||
/* 33 */ simple("piston"),
|
||||
/* 34 */ simple("piston_head"),
|
||||
/* 35 */ {
|
||||
"white_wool",
|
||||
"orange_wool",
|
||||
"magenta_wool",
|
||||
"light_blue_wool",
|
||||
"yellow_wool",
|
||||
"lime_wool",
|
||||
"pink_wool",
|
||||
"gray_wool",
|
||||
"light_gray_wool",
|
||||
"cyan_wool",
|
||||
"purple_wool",
|
||||
"blue_wool",
|
||||
"brown_wool",
|
||||
"green_wool",
|
||||
"red_wool",
|
||||
"black_wool",
|
||||
},
|
||||
/* 36 */ simple("moving_piston"),
|
||||
/* 37 */ simple("dandelion"),
|
||||
/* 38 */ {
|
||||
"poppy",
|
||||
"blue_orchid",
|
||||
"allium",
|
||||
"azure_bluet",
|
||||
"red_tulip",
|
||||
"orange_tulip",
|
||||
"white_tulip",
|
||||
"pink_tulip",
|
||||
"oxeye_daisy",
|
||||
},
|
||||
/* 39 */ simple("brown_mushroom"),
|
||||
/* 40 */ simple("red_mushroom"),
|
||||
/* 41 */ simple("gold_block"),
|
||||
/* 42 */ simple("iron_block"),
|
||||
/* 43 */ {
|
||||
"smooth_stone_slab",
|
||||
"sandstone_slab",
|
||||
"oak_slab",
|
||||
"cobblestone_slab",
|
||||
"brick_slab",
|
||||
"stone_brick_slab",
|
||||
"nether_brick_slab",
|
||||
"quartz_slab",
|
||||
},
|
||||
/* 44 */ {
|
||||
"smooth_stone_slab",
|
||||
"sandstone_slab",
|
||||
"oak_slab",
|
||||
"cobblestone_slab",
|
||||
"brick_slab",
|
||||
"stone_brick_slab",
|
||||
"nether_brick_slab",
|
||||
"quartz_slab",
|
||||
"stone_slab",
|
||||
"sandstone_slab",
|
||||
"oak_slab",
|
||||
"cobblestone_slab",
|
||||
"brick_slab",
|
||||
"stone_brick_slab",
|
||||
"nether_brick_slab",
|
||||
"quartz_slab",
|
||||
},
|
||||
/* 45 */ simple("bricks"),
|
||||
/* 46 */ simple("tnt"),
|
||||
/* 47 */ simple("bookshelf"),
|
||||
/* 48 */ simple("mossy_cobblestone"),
|
||||
/* 49 */ simple("obsidian"),
|
||||
/* 50 */ {
|
||||
nullptr,
|
||||
"wall_torch",
|
||||
"wall_torch",
|
||||
"wall_torch",
|
||||
"wall_torch",
|
||||
"torch",
|
||||
},
|
||||
/* 51 */ simple("fire"),
|
||||
/* 52 */ simple("spawner"),
|
||||
/* 53 */ simple("oak_stairs"),
|
||||
/* 54 */ simple("chest"),
|
||||
/* 55 */ simple("redstone_wire"),
|
||||
/* 56 */ simple("diamond_ore"),
|
||||
/* 57 */ simple("diamond_block"),
|
||||
/* 58 */ simple("crafting_table"),
|
||||
/* 59 */ simple("wheat"),
|
||||
/* 60 */ simple("farmland"),
|
||||
/* 61 */ simple("furnace"),
|
||||
/* 62 */ simple("furnace"),
|
||||
/* 63 */ simple("sign"),
|
||||
/* 64 */ simple("oak_door"),
|
||||
/* 65 */ simple("ladder"),
|
||||
/* 66 */ simple("rail"),
|
||||
/* 67 */ simple("cobblestone_stairs"),
|
||||
/* 68 */ simple("wall_sign"),
|
||||
/* 69 */ simple("lever"),
|
||||
/* 70 */ simple("stone_pressure_plate"),
|
||||
/* 71 */ simple("iron_door"),
|
||||
/* 72 */ simple("oak_pressure_plate"),
|
||||
/* 73 */ simple("redstone_ore"),
|
||||
/* 74 */ simple("redstone_ore"),
|
||||
/* 75 */ {
|
||||
nullptr,
|
||||
"redstone_wall_torch",
|
||||
"redstone_wall_torch",
|
||||
"redstone_wall_torch",
|
||||
"redstone_wall_torch",
|
||||
"redstone_torch",
|
||||
},
|
||||
/* 76 */ {
|
||||
nullptr,
|
||||
"redstone_wall_torch",
|
||||
"redstone_wall_torch",
|
||||
"redstone_wall_torch",
|
||||
"redstone_wall_torch",
|
||||
"redstone_torch",
|
||||
},
|
||||
/* 77 */ simple("stone_button"),
|
||||
/* 78 */ simple("snow"),
|
||||
/* 79 */ simple("ice"),
|
||||
/* 80 */ simple("snow_block"),
|
||||
/* 81 */ simple("cactus"),
|
||||
/* 82 */ simple("clay"),
|
||||
/* 83 */ simple("sugar_cane"),
|
||||
/* 84 */ simple("jukebox"),
|
||||
/* 85 */ simple("oak_fence"),
|
||||
/* 86 */ simple("pumpkin"),
|
||||
/* 87 */ simple("netherrack"),
|
||||
/* 88 */ simple("soul_sand"),
|
||||
/* 89 */ simple("glowstone"),
|
||||
/* 90 */ simple("nether_portal"),
|
||||
/* 91 */ simple("pumpkin"),
|
||||
/* 92 */ simple("cake"),
|
||||
/* 93 */ simple("repeater"),
|
||||
/* 94 */ simple("repeater"),
|
||||
/* 95 */ {
|
||||
"white_stained_glass",
|
||||
"orange_stained_glass",
|
||||
"magenta_stained_glass",
|
||||
"light_blue_stained_glass",
|
||||
"yellow_stained_glass",
|
||||
"lime_stained_glass",
|
||||
"pink_stained_glass",
|
||||
"gray_stained_glass",
|
||||
"light_gray_stained_glass",
|
||||
"cyan_stained_glass",
|
||||
"purple_stained_glass",
|
||||
"blue_stained_glass",
|
||||
"brown_stained_glass",
|
||||
"green_stained_glass",
|
||||
"red_stained_glass",
|
||||
"black_stained_glass",
|
||||
},
|
||||
/* 96 */ simple("oak_trapdoor"),
|
||||
/* 97 */ {
|
||||
"infested_stone",
|
||||
"infested_cobblestone",
|
||||
"infested_stone_bricks",
|
||||
"infested_mossy_stone_bricks",
|
||||
"infested_cracked_stone_bricks",
|
||||
"infested_chiseled_stone_bricks",
|
||||
},
|
||||
/* 98 */ {
|
||||
"stone_bricks",
|
||||
"mossy_stone_bricks",
|
||||
"cracked_stone_bricks",
|
||||
"chiseled_stone_bricks",
|
||||
},
|
||||
/* 99 */ simple("brown_mushroom_block"),
|
||||
/* 100 */ simple("red_mushroom_block"),
|
||||
/* 101 */ simple("iron_bars"),
|
||||
/* 102 */ simple("glass_pane"),
|
||||
/* 103 */ simple("melon"),
|
||||
/* 104 */ simple("pumpkin_stem"),
|
||||
/* 105 */ simple("melon_stem"),
|
||||
/* 106 */ simple("vine"),
|
||||
/* 107 */ simple("oak_fence_gate"),
|
||||
/* 108 */ simple("brick_stairs"),
|
||||
/* 109 */ simple("stone_brick_stairs"),
|
||||
/* 110 */ simple("mycelium"),
|
||||
/* 111 */ simple("lily_pad"),
|
||||
/* 112 */ simple("nether_bricks"),
|
||||
/* 113 */ simple("nether_brick_fence"),
|
||||
/* 114 */ simple("nether_brick_stairs"),
|
||||
/* 115 */ simple("nether_wart"),
|
||||
/* 116 */ simple("enchanting_table"),
|
||||
/* 117 */ simple("brewing_stand"),
|
||||
/* 118 */ simple("cauldron"),
|
||||
/* 119 */ simple("end_portal"),
|
||||
/* 120 */ simple("end_portal_frame"),
|
||||
/* 121 */ simple("end_stone"),
|
||||
/* 122 */ simple("dragon_egg"),
|
||||
/* 123 */ simple("redstone_lamp"),
|
||||
/* 124 */ simple("redstone_lamp"),
|
||||
/* 125 */ {
|
||||
"oak_slab",
|
||||
"spruce_slab",
|
||||
"birch_slab",
|
||||
"jungle_slab",
|
||||
"acacia_slab",
|
||||
"dark_oak_slab",
|
||||
},
|
||||
/* 126 */ {
|
||||
"oak_slab",
|
||||
"spruce_slab",
|
||||
"birch_slab",
|
||||
"jungle_slab",
|
||||
"acacia_slab",
|
||||
"dark_oak_slab",
|
||||
nullptr,
|
||||
nullptr,
|
||||
"oak_slab",
|
||||
"spruce_slab",
|
||||
"birch_slab",
|
||||
"jungle_slab",
|
||||
"acacia_slab",
|
||||
"dark_oak_slab",
|
||||
},
|
||||
/* 127 */ simple("cocoa"),
|
||||
/* 128 */ simple("sandstone_stairs"),
|
||||
/* 129 */ simple("emerald_ore"),
|
||||
/* 130 */ simple("ender_chest"),
|
||||
/* 131 */ simple("tripwire_hook"),
|
||||
/* 132 */ simple("tripwire"),
|
||||
/* 133 */ simple("emerald_block"),
|
||||
/* 134 */ simple("spruce_stairs"),
|
||||
/* 135 */ simple("birch_stairs"),
|
||||
/* 136 */ simple("jungle_stairs"),
|
||||
/* 137 */ simple("command_block"),
|
||||
/* 138 */ simple("beacon"),
|
||||
/* 139 */ {
|
||||
"cobblestone_wall",
|
||||
"mossy_cobblestone_wall",
|
||||
},
|
||||
/* 140 */ simple("flower_pot"),
|
||||
/* 141 */ simple("carrots"),
|
||||
/* 142 */ simple("potatoes"),
|
||||
/* 143 */ {},
|
||||
/* 144 */ {},
|
||||
/* 145 */ {
|
||||
"anvil",
|
||||
"anvil",
|
||||
"anvil",
|
||||
"anvil",
|
||||
"chipped_anvil",
|
||||
"chipped_anvil",
|
||||
"chipped_anvil",
|
||||
"chipped_anvil",
|
||||
"damaged_anvil",
|
||||
"damaged_anvil",
|
||||
"damaged_anvil",
|
||||
"damaged_anvil",
|
||||
},
|
||||
/* 146 */ simple("trapped_chest"),
|
||||
/* 147 */ simple("light_weighted_pressure_plate"),
|
||||
/* 148 */ simple("heavy_weighted_pressure_plate"),
|
||||
/* 149 */ simple("comparator"),
|
||||
/* 150 */ simple("comparator"),
|
||||
/* 151 */ simple("daylight_detector"),
|
||||
/* 152 */ simple("redstone_block"),
|
||||
/* 153 */ simple("nether_quartz_ore"),
|
||||
/* 154 */ simple("hopper"),
|
||||
/* 155 */ simple("quartz_block"),
|
||||
/* 156 */ simple("quartz_stairs"),
|
||||
/* 157 */ simple("activator_rail"),
|
||||
/* 158 */ simple("dropper"),
|
||||
/* 159 */ {
|
||||
"white_terracotta",
|
||||
"orange_terracotta",
|
||||
"magenta_terracotta",
|
||||
"light_blue_terracotta",
|
||||
"yellow_terracotta",
|
||||
"lime_terracotta",
|
||||
"pink_terracotta",
|
||||
"gray_terracotta",
|
||||
"light_gray_terracotta",
|
||||
"cyan_terracotta",
|
||||
"purple_terracotta",
|
||||
"blue_terracotta",
|
||||
"brown_terracotta",
|
||||
"green_terracotta",
|
||||
"red_terracotta",
|
||||
"black_terracotta",
|
||||
},
|
||||
/* 160 */ {
|
||||
"white_stained_glass_pane",
|
||||
"orange_stained_glass_pane",
|
||||
"magenta_stained_glass_pane",
|
||||
"light_blue_stained_glass_pane",
|
||||
"yellow_stained_glass_pane",
|
||||
"lime_stained_glass_pane",
|
||||
"pink_stained_glass_pane",
|
||||
"gray_stained_glass_pane",
|
||||
"light_gray_stained_glass_pane",
|
||||
"cyan_stained_glass_pane",
|
||||
"purple_stained_glass_pane",
|
||||
"blue_stained_glass_pane",
|
||||
"brown_stained_glass_pane",
|
||||
"green_stained_glass_pane",
|
||||
"red_stained_glass_pane",
|
||||
"black_stained_glass_pane",
|
||||
},
|
||||
/* 161 */ {
|
||||
"acacia_leaves",
|
||||
"dark_oak_leaves",
|
||||
nullptr,
|
||||
nullptr,
|
||||
"acacia_leaves",
|
||||
"dark_oak_leaves",
|
||||
nullptr,
|
||||
nullptr,
|
||||
"acacia_leaves",
|
||||
"dark_oak_leaves",
|
||||
nullptr,
|
||||
nullptr,
|
||||
"acacia_leaves",
|
||||
"dark_oak_leaves",
|
||||
},
|
||||
/* 162 */ {
|
||||
"acacia_log",
|
||||
"dark_oak_log",
|
||||
nullptr,
|
||||
nullptr,
|
||||
"acacia_log",
|
||||
"dark_oak_log",
|
||||
nullptr,
|
||||
nullptr,
|
||||
"acacia_log",
|
||||
"dark_oak_log",
|
||||
nullptr,
|
||||
nullptr,
|
||||
"acacia_log",
|
||||
"dark_oak_log",
|
||||
},
|
||||
/* 163 */ simple("acacia_stairs"),
|
||||
/* 164 */ simple("dark_oak_stairs"),
|
||||
/* 165 */ simple("slime_block"),
|
||||
/* 166 */ simple("barrier"),
|
||||
/* 167 */ simple("iron_trapdoor"),
|
||||
/* 168 */ {
|
||||
"prismarine",
|
||||
"prismarine_bricks",
|
||||
"dark_prismarine",
|
||||
},
|
||||
/* 169 */ simple("sea_lantern"),
|
||||
/* 170 */ simple("hay_block"),
|
||||
/* 171 */ {
|
||||
"white_carpet",
|
||||
"orange_carpet",
|
||||
"magenta_carpet",
|
||||
"light_blue_carpet",
|
||||
"yellow_carpet",
|
||||
"lime_carpet",
|
||||
"pink_carpet",
|
||||
"gray_carpet",
|
||||
"light_gray_carpet",
|
||||
"cyan_carpet",
|
||||
"purple_carpet",
|
||||
"blue_carpet",
|
||||
"brown_carpet",
|
||||
"green_carpet",
|
||||
"red_carpet",
|
||||
"black_carpet",
|
||||
},
|
||||
/* 172 */ simple("terracotta"),
|
||||
/* 173 */ simple("coal_block"),
|
||||
/* 174 */ simple("packed_ice"),
|
||||
/* 175 */ {
|
||||
"sunflower",
|
||||
"lilac",
|
||||
"tall_grass",
|
||||
"large_fern",
|
||||
"rose_bush",
|
||||
"peony",
|
||||
},
|
||||
/* 176 */ {/* banner */},
|
||||
/* 177 */ {/* wall banner */},
|
||||
/* 178 */ simple("daylight_detector"),
|
||||
/* 179 */ simple("red_sandstone"),
|
||||
/* 180 */ simple("red_sandstone_stairs"),
|
||||
/* 181 */ simple("red_sandstone_slab"),
|
||||
/* 182 */ simple("red_sandstone_slab"),
|
||||
/* 183 */ simple("spruce_fence_gate"),
|
||||
/* 184 */ simple("birch_fence_gate"),
|
||||
/* 185 */ simple("jungle_fence_gate"),
|
||||
/* 186 */ simple("dark_oak_fence_gate"),
|
||||
/* 187 */ simple("acacia_fence_gate"),
|
||||
/* 188 */ simple("spruce_fence"),
|
||||
/* 189 */ simple("birch_fence"),
|
||||
/* 190 */ simple("jungle_fence"),
|
||||
/* 191 */ simple("dark_oak_fence"),
|
||||
/* 192 */ simple("acacia_fence"),
|
||||
/* 193 */ simple("spruce_door"),
|
||||
/* 194 */ simple("birch_door"),
|
||||
/* 195 */ simple("jungle_door"),
|
||||
/* 196 */ simple("acacia_door"),
|
||||
/* 197 */ simple("dark_oak_door"),
|
||||
/* 198 */ simple("end_rod"),
|
||||
/* 199 */ simple("chorus_plant"),
|
||||
/* 200 */ simple("chorus_flower"),
|
||||
/* 201 */ simple("purpur_block"),
|
||||
/* 202 */ simple("purpur_pillar"),
|
||||
/* 203 */ simple("purpur_stairs"),
|
||||
/* 204 */ simple("purpur_slab"),
|
||||
/* 205 */ simple("purpur_slab"),
|
||||
/* 206 */ simple("end_stone_bricks"),
|
||||
/* 207 */ simple("beetroots"),
|
||||
/* 208 */ simple("grass_path"),
|
||||
/* 209 */ simple("end_gateway"),
|
||||
/* 210 */ simple("repeating_command_block"),
|
||||
/* 211 */ simple("chain_command_block"),
|
||||
/* 212 */ simple("frosted_ice"),
|
||||
/* 213 */ simple("magma_block"),
|
||||
/* 214 */ simple("nether_wart_block"),
|
||||
/* 215 */ simple("red_nether_bricks"),
|
||||
/* 216 */ simple("bone_block"),
|
||||
/* 217 */ simple("structure_void"),
|
||||
/* 218 */ simple("observer"),
|
||||
/* 219 */ simple("white_shulker_box"),
|
||||
/* 220 */ simple("orange_shulker_box"),
|
||||
/* 221 */ simple("magenta_shulker_box"),
|
||||
/* 222 */ simple("light_blue_shulker_box"),
|
||||
/* 223 */ simple("yellow_shulker_box"),
|
||||
/* 224 */ simple("lime_shulker_box"),
|
||||
/* 225 */ simple("pink_shulker_box"),
|
||||
/* 226 */ simple("gray_shulker_box"),
|
||||
/* 227 */ simple("light_gray_shulker_box"),
|
||||
/* 228 */ simple("cyan_shulker_box"),
|
||||
/* 229 */ simple("purple_shulker_box"),
|
||||
/* 230 */ simple("blue_shulker_box"),
|
||||
/* 231 */ simple("brown_shulker_box"),
|
||||
/* 232 */ simple("green_shulker_box"),
|
||||
/* 233 */ simple("red_shulker_box"),
|
||||
/* 234 */ simple("black_shulker_box"),
|
||||
/* 235 */ simple("white_glazed_terracotta"),
|
||||
/* 236 */ simple("orange_glazed_terracotta"),
|
||||
/* 237 */ simple("magenta_glazed_terracotta"),
|
||||
/* 238 */ simple("light_blue_glazed_terracotta"),
|
||||
/* 239 */ simple("yellow_glazed_terracotta"),
|
||||
/* 240 */ simple("lime_glazed_terracotta"),
|
||||
/* 241 */ simple("pink_glazed_terracotta"),
|
||||
/* 242 */ simple("gray_glazed_terracotta"),
|
||||
/* 243 */ simple("light_gray_glazed_terracotta"),
|
||||
/* 244 */ simple("cyan_glazed_terracotta"),
|
||||
/* 245 */ simple("purple_glazed_terracotta"),
|
||||
/* 246 */ simple("blue_glazed_terracotta"),
|
||||
/* 247 */ simple("brown_glazed_terracotta"),
|
||||
/* 248 */ simple("green_glazed_terracotta"),
|
||||
/* 249 */ simple("red_glazed_terracotta"),
|
||||
/* 250 */ simple("black_glazed_terracotta"),
|
||||
/* 251 */ {
|
||||
"white_concrete",
|
||||
"orange_concrete",
|
||||
"magenta_concrete",
|
||||
"light_blue_concrete",
|
||||
"yellow_concrete",
|
||||
"lime_concrete",
|
||||
"pink_concrete",
|
||||
"gray_concrete",
|
||||
"light_gray_concrete",
|
||||
"cyan_concrete",
|
||||
"purple_concrete",
|
||||
"blue_concrete",
|
||||
"brown_concrete",
|
||||
"green_concrete",
|
||||
"red_concrete",
|
||||
"black_concrete",
|
||||
},
|
||||
/* 252 */ {
|
||||
"white_concrete_powder",
|
||||
"orange_concrete_powder",
|
||||
"magenta_concrete_powder",
|
||||
"light_blue_concrete_powder",
|
||||
"yellow_concrete_powder",
|
||||
"lime_concrete_powder",
|
||||
"pink_concrete_powder",
|
||||
"gray_concrete_powder",
|
||||
"light_gray_concrete_powder",
|
||||
"cyan_concrete_powder",
|
||||
"purple_concrete_powder",
|
||||
"blue_concrete_powder",
|
||||
"brown_concrete_powder",
|
||||
"green_concrete_powder",
|
||||
"red_concrete_powder",
|
||||
"black_concrete_powder",
|
||||
},
|
||||
/* 253 */ {},
|
||||
/* 254 */ {},
|
||||
/* 255 */ simple("structure_block"),
|
|
@ -1,18 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
|
||||
|
||||
template<typename T> class UniqueCPtr : public std::unique_ptr<T, void (*)(void *)> {
|
||||
public:
|
||||
UniqueCPtr() : std::unique_ptr<T, void (*)(void *)>(nullptr, std::free) {}
|
||||
template<typename T2> UniqueCPtr(T2 ptr) : std::unique_ptr<T, void (*)(void *)>(ptr, std::free) {}
|
||||
};
|
44
src/Util.hpp
44
src/Util.hpp
|
@ -1,44 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015-2021, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
|
||||
template <typename T> static inline T assertValue(T&& val) {
|
||||
if (!val)
|
||||
throw std::invalid_argument("assertValue failed");
|
||||
|
||||
return std::forward<T>(val);
|
||||
}
|
||||
|
||||
static inline float clamp(float v, float min, float max) {
|
||||
return std::max(std::min(v, max), min);
|
||||
}
|
||||
|
||||
// A block coordinate relative to a chunk/section (X/Y/Z)
|
||||
typedef uint8_t block_idx_t;
|
||||
// A section index in a chunk (Y)
|
||||
typedef int8_t section_idx_t;
|
||||
// A chunk coordinate relative to a region (X/Z)
|
||||
typedef uint8_t chunk_idx_t;
|
||||
|
||||
// A block coordinate relative to a region (X/Z)
|
||||
typedef uint16_t region_block_idx_t;
|
||||
// A block coordinate (Y)
|
||||
typedef int16_t y_idx_t;
|
||||
|
||||
const y_idx_t y_idx_min = std::numeric_limits<y_idx_t>::min();
|
||||
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015-2021, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../NBT/CompoundTag.hpp"
|
||||
#include "../Resource/Biome.hpp"
|
||||
#include "../Resource/BlockType.hpp"
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace World {
|
||||
|
||||
struct Block {
|
||||
const Resource::BlockType *type;
|
||||
y_idx_t depth;
|
||||
uint8_t blockLight;
|
||||
|
||||
bool isVisible() const {
|
||||
return type && (type->flags & BLOCK_OPAQUE);
|
||||
}
|
||||
|
||||
Resource::FloatColor getColor(uint8_t biome) const {
|
||||
if (!isVisible())
|
||||
return Resource::FloatColor {};
|
||||
|
||||
return (Resource::Biome::Biomes[biome] ?: Resource::Biome::Default)->getBlockColor(type, depth);
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
return type;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -1,201 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015-2021, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#include "Chunk.hpp"
|
||||
#include "../NBT/IntTag.hpp"
|
||||
#include "../NBT/ListTag.hpp"
|
||||
#include "../NBT/StringTag.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace World {
|
||||
|
||||
Chunk::Chunk(const ChunkData *data) {
|
||||
std::shared_ptr<const NBT::ListTag> sectionsTag;
|
||||
|
||||
auto level = data->getRoot()->get<NBT::CompoundTag>("Level");
|
||||
if (level) {
|
||||
sectionsTag = level->get<NBT::ListTag>("Sections");
|
||||
if (!sectionsTag || sectionsTag->empty())
|
||||
return;
|
||||
|
||||
auto biomesIntArray = level->get<NBT::IntArrayTag>("Biomes");
|
||||
auto biomesByteArray = level->get<NBT::ByteArrayTag>("Biomes");
|
||||
|
||||
if (biomesIntArray && biomesIntArray->getLength() == BSIZE*BSIZE*BMAXY) {
|
||||
biomeInts = std::move(biomesIntArray);
|
||||
biomeFormat = INT_ARRAY;
|
||||
} else if (biomesIntArray && biomesIntArray->getLength() == SIZE*SIZE) {
|
||||
biomeInts = std::move(biomesIntArray);
|
||||
biomeFormat = INT_ARRAY_PRE1_15;
|
||||
} else if (biomesByteArray && biomesByteArray->getLength() == SIZE*SIZE) {
|
||||
biomeBytes = std::move(biomesByteArray);
|
||||
biomeFormat = BYTE_ARRAY;
|
||||
} else {
|
||||
throw std::invalid_argument("corrupt biome data");
|
||||
}
|
||||
} else {
|
||||
sectionsTag = data->getRoot()->get<NBT::ListTag>("sections");
|
||||
if (!sectionsTag || sectionsTag->empty())
|
||||
return;
|
||||
|
||||
biomeFormat = SECTION;
|
||||
}
|
||||
|
||||
auto dataVersionTag = data->getRoot()->get<NBT::IntTag>("DataVersion");
|
||||
uint32_t dataVersion = dataVersionTag ? dataVersionTag->getValue() : 0;
|
||||
|
||||
std::vector<std::unique_ptr<Section>> tmpSections;
|
||||
section_idx_t minY = std::numeric_limits<section_idx_t>::max();
|
||||
section_idx_t maxY = std::numeric_limits<section_idx_t>::min();
|
||||
|
||||
for (auto &sTag : *sectionsTag) {
|
||||
auto s = std::dynamic_pointer_cast<const NBT::CompoundTag>(sTag);
|
||||
std::unique_ptr<Section> section = Section::makeSection(s, dataVersion);
|
||||
section_idx_t Y = section->getY();
|
||||
if (Y < minY)
|
||||
minY = Y;
|
||||
if (Y > maxY)
|
||||
maxY = Y;
|
||||
|
||||
tmpSections.push_back(std::move(section));
|
||||
}
|
||||
|
||||
if (tmpSections.empty())
|
||||
return;
|
||||
|
||||
assertValue(minY <= maxY);
|
||||
sectionOffset = minY;
|
||||
sections.resize(maxY - minY + 1);
|
||||
|
||||
for (auto §ion : tmpSections) {
|
||||
section_idx_t Y = section->getY();
|
||||
Y -= sectionOffset;
|
||||
assertValue(Y >= 0 && size_t(Y) < sections.size());
|
||||
assertValue(!sections[Y]);
|
||||
sections[Y] = std::move(section);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Chunk::getBiome(block_idx_t x, y_idx_t y, block_idx_t z) const {
|
||||
if (x >= SIZE || z >= SIZE)
|
||||
throw std::invalid_argument("getBiome: invalid block coordinate");
|
||||
|
||||
switch (biomeFormat) {
|
||||
case INT_ARRAY:
|
||||
if (y < 0 || y >= MAXY)
|
||||
break;
|
||||
return biomeInts->getValue((y>>BSHIFT)*BSIZE*BSIZE + (z>>BSHIFT)*BSIZE + (x>>BSHIFT));
|
||||
case INT_ARRAY_PRE1_15:
|
||||
return biomeInts->getValue(z*SIZE + x);
|
||||
case BYTE_ARRAY:
|
||||
return biomeBytes->getValue(z*SIZE + x);
|
||||
case SECTION: {
|
||||
section_idx_t Y = (y >> HSHIFT) - sectionOffset;
|
||||
|
||||
if (Y < 0 || size_t(Y) >= sections.size() || !sections[Y])
|
||||
break;
|
||||
|
||||
return sections[Y]->getBiomeAt(x, y & HMASK, z);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
Block Chunk::getBlock(block_idx_t x, Chunk::Height height, block_idx_t z) const {
|
||||
Block block = {};
|
||||
|
||||
block.depth = height.depth;
|
||||
|
||||
if (height.y == y_idx_min)
|
||||
return block;
|
||||
|
||||
section_idx_t Y = (height.y >> HSHIFT) - sectionOffset;
|
||||
block_idx_t y = height.y & HMASK;
|
||||
|
||||
if (Y >= 0 && size_t(Y) < sections.size() && sections[Y])
|
||||
block.type = sections[Y]->getBlockStateAt(x, y, z);
|
||||
|
||||
section_idx_t Yt = ((height.y + 1) >> HSHIFT) - sectionOffset;
|
||||
block_idx_t yt = (height.y + 1) & HMASK;
|
||||
|
||||
if (Yt >= 0 && size_t(Yt) < sections.size() && sections[Yt])
|
||||
block.blockLight = sections[Yt]->getBlockLightAt(x, yt, z);
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
bool Chunk::getHeight(
|
||||
Chunk::Height *height, const Section *section,
|
||||
block_idx_t x, block_idx_t y, block_idx_t z, int flags
|
||||
) const {
|
||||
if (height->depth > y_idx_min)
|
||||
return false;
|
||||
|
||||
if (!(flags & WITH_DEPTH) && height->y > y_idx_min)
|
||||
return false;
|
||||
|
||||
const Resource::BlockType *type = section->getBlockStateAt(x, y, z);
|
||||
if (!type || !(type->flags & BLOCK_OPAQUE))
|
||||
return false;
|
||||
|
||||
if (height->y == y_idx_min)
|
||||
height->y = (section->getY() << HSHIFT) + y;
|
||||
|
||||
if (!(flags & WITH_DEPTH))
|
||||
return true;
|
||||
|
||||
if (type->flags & BLOCK_WATER)
|
||||
return false;
|
||||
|
||||
height->depth = (section->getY() << HSHIFT) + y;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Chunk::Heightmap Chunk::getTopLayer(int flags) const {
|
||||
uint32_t done = 0;
|
||||
Heightmap ret;
|
||||
|
||||
for (block_idx_t z = 0; z < SIZE; z++) {
|
||||
for (block_idx_t x = 0; x < SIZE; x++)
|
||||
ret.v[x][z] = Height { .y = y_idx_min, .depth = y_idx_min };
|
||||
}
|
||||
|
||||
for (section_idx_t Y = sections.size() - 1; Y >= 0; Y--) {
|
||||
if (done == SIZE*SIZE)
|
||||
break;
|
||||
|
||||
if (!sections[Y])
|
||||
continue;
|
||||
|
||||
const Section *section = sections[Y].get();
|
||||
|
||||
for (int8_t y = SIZE-1; y >= 0; y--) {
|
||||
if (done == SIZE*SIZE)
|
||||
break;
|
||||
|
||||
for (block_idx_t z = 0; z < SIZE; z++) {
|
||||
for (block_idx_t x = 0; x < SIZE; x++) {
|
||||
if (getHeight(&ret.v[x][z], section, x, y, z, flags))
|
||||
done++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015-2021, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#include "Block.hpp"
|
||||
#include "ChunkData.hpp"
|
||||
#include "Section.hpp"
|
||||
#include "../NBT/ByteArrayTag.hpp"
|
||||
#include "../NBT/IntArrayTag.hpp"
|
||||
#include "../Resource/BlockType.hpp"
|
||||
#include "../Util.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace World {
|
||||
|
||||
class Chunk {
|
||||
public:
|
||||
// Number of blocks in a chunk in x/z dimensions
|
||||
static const uint32_t SIZE = Section::SIZE;
|
||||
// Maximum Y value for pre-1.18 chunks
|
||||
static const y_idx_t MAXY = 256;
|
||||
|
||||
// Shift to get from height to section index
|
||||
static const unsigned HSHIFT = 4;
|
||||
// Mask to get from height to y index inside section
|
||||
static const block_idx_t HMASK = 0xf;
|
||||
|
||||
// Since Minecraft 1.15, biome information is stored for
|
||||
// 4x4x4 block groups
|
||||
static const unsigned BSHIFT = Section::BSHIFT;
|
||||
// Number of biome values in a chunk in x/z dimensions
|
||||
static const uint32_t BSIZE = Section::BSIZE;
|
||||
// Number of biome values in a chunk in y dimension
|
||||
static const uint32_t BMAXY = MAXY >> BSHIFT;
|
||||
|
||||
// Flags
|
||||
static const int WITH_DEPTH = (1 << 0);
|
||||
|
||||
struct Height {
|
||||
y_idx_t y;
|
||||
y_idx_t depth;
|
||||
};
|
||||
|
||||
struct Heightmap {
|
||||
Height v[SIZE][SIZE];
|
||||
};
|
||||
|
||||
private:
|
||||
section_idx_t sectionOffset;
|
||||
std::vector<std::unique_ptr<Section>> sections;
|
||||
|
||||
enum BiomeFormat {
|
||||
UNKNOWN = 0,
|
||||
BYTE_ARRAY,
|
||||
INT_ARRAY_PRE1_15,
|
||||
INT_ARRAY,
|
||||
SECTION,
|
||||
} biomeFormat = UNKNOWN;
|
||||
|
||||
std::shared_ptr<const NBT::ByteArrayTag> biomeBytes;
|
||||
std::shared_ptr<const NBT::IntArrayTag> biomeInts;
|
||||
|
||||
bool getHeight(
|
||||
Height *height, const Section *section,
|
||||
block_idx_t x, block_idx_t y, block_idx_t z, int flags
|
||||
) const;
|
||||
|
||||
const Resource::BlockType * getBlockStateAt(block_idx_t x, y_idx_t y, block_idx_t z) const {
|
||||
section_idx_t Y = (y >> HSHIFT) - sectionOffset;
|
||||
|
||||
if (Y < 0 || size_t(Y) >= sections.size() || !sections[Y])
|
||||
return nullptr;
|
||||
|
||||
return sections[Y]->getBlockStateAt(x, y & HMASK, z);
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
Chunk(const ChunkData *data);
|
||||
|
||||
uint8_t getBiome(block_idx_t x, y_idx_t y, block_idx_t z) const;
|
||||
Block getBlock(block_idx_t x, Height y, block_idx_t z) const;
|
||||
|
||||
Heightmap getTopLayer(int flags) const;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015-2018, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#include "ChunkData.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace World {
|
||||
|
||||
ChunkData::ChunkData(Buffer buffer) {
|
||||
size_t size = buffer.get32();
|
||||
|
||||
Buffer buffer2(buffer.get(size), size);
|
||||
|
||||
uint8_t format = buffer2.get8();
|
||||
if (format != 2)
|
||||
throw std::invalid_argument("unknown chunk format");
|
||||
|
||||
inflateChunk(buffer2);
|
||||
parseChunk();
|
||||
}
|
||||
|
||||
void ChunkData::inflateChunk(Buffer buffer) {
|
||||
size_t outlen = 0;
|
||||
uint8_t *output = nullptr;
|
||||
|
||||
z_stream stream = {};
|
||||
int ret = inflateInit(&stream);
|
||||
if (ret != Z_OK)
|
||||
throw std::runtime_error("inflateInit() failed");
|
||||
|
||||
stream.avail_in = buffer.getRemaining();
|
||||
stream.next_in = const_cast<uint8_t *>(buffer.get(stream.avail_in));
|
||||
|
||||
while (stream.avail_in) {
|
||||
outlen += 65536;
|
||||
output = static_cast<uint8_t *>(std::realloc(output, outlen));
|
||||
|
||||
stream.next_out = output + stream.total_out;
|
||||
stream.avail_out = outlen - stream.total_out;
|
||||
|
||||
ret = inflate(&stream, Z_NO_FLUSH);
|
||||
switch (ret) {
|
||||
case Z_NEED_DICT:
|
||||
case Z_DATA_ERROR:
|
||||
case Z_MEM_ERROR:
|
||||
inflateEnd(&stream);
|
||||
throw std::runtime_error("inflate() failed");
|
||||
}
|
||||
}
|
||||
|
||||
inflateEnd(&stream);
|
||||
|
||||
len = stream.total_out;
|
||||
data = UniqueCPtr<uint8_t[]>(output);
|
||||
}
|
||||
|
||||
void ChunkData::parseChunk() {
|
||||
Buffer nbt(data.get(), len);
|
||||
std::pair<std::string, std::shared_ptr<const NBT::Tag>> tag = NBT::Tag::readNamedTag(&nbt);
|
||||
if (!tag.first.empty())
|
||||
throw std::invalid_argument("invalid root tag");
|
||||
|
||||
root = assertValue(std::dynamic_pointer_cast<const NBT::CompoundTag>(tag.second));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015-2018, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#include "../Buffer.hpp"
|
||||
#include "../UniqueCPtr.hpp"
|
||||
#include "../Util.hpp"
|
||||
#include "../NBT/CompoundTag.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace World {
|
||||
|
||||
class ChunkData {
|
||||
private:
|
||||
size_t len;
|
||||
UniqueCPtr<uint8_t[]> data;
|
||||
|
||||
std::shared_ptr<const NBT::CompoundTag> root;
|
||||
|
||||
void inflateChunk(Buffer buffer);
|
||||
void parseChunk();
|
||||
|
||||
public:
|
||||
ChunkData(Buffer buffer);
|
||||
|
||||
const std::shared_ptr<const NBT::CompoundTag> & getRoot() const {
|
||||
return root;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#include "Level.hpp"
|
||||
#include "../GZip.hpp"
|
||||
#include "../Util.hpp"
|
||||
#include "../NBT/IntTag.hpp"
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace World {
|
||||
|
||||
Level::Level(const char *filename) {
|
||||
buffer = readGZip(filename);
|
||||
|
||||
Buffer nbt(buffer.data(), buffer.size());
|
||||
std::pair<std::string, std::shared_ptr<const NBT::Tag>> tag = NBT::Tag::readNamedTag(&nbt);
|
||||
if (tag.first != "")
|
||||
throw std::invalid_argument("invalid root tag");
|
||||
|
||||
root = assertValue(std::dynamic_pointer_cast<const NBT::CompoundTag>(tag.second));
|
||||
data = assertValue(root->get<NBT::CompoundTag>("Data"));
|
||||
}
|
||||
|
||||
std::pair<int32_t, int32_t> Level::getSpawn() const {
|
||||
int32_t x = assertValue(data->get<NBT::IntTag>("SpawnX"))->getValue();
|
||||
int32_t z = assertValue(data->get<NBT::IntTag>("SpawnZ"))->getValue();
|
||||
|
||||
return std::make_pair(x, z);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../NBT/CompoundTag.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace World {
|
||||
|
||||
class Level {
|
||||
private:
|
||||
std::vector<uint8_t> buffer;
|
||||
|
||||
std::shared_ptr<const NBT::CompoundTag> root;
|
||||
std::shared_ptr<const NBT::CompoundTag> data;
|
||||
|
||||
public:
|
||||
Level(const char *filename);
|
||||
|
||||
std::pair<int32_t, int32_t> getSpawn() const;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015-2021, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#include "Region.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace World {
|
||||
|
||||
Region::ChunkMap Region::processHeader(const uint8_t header[4096]) {
|
||||
ChunkMap map;
|
||||
|
||||
for (chunk_idx_t z = 0; z < 32; z++) {
|
||||
for (chunk_idx_t x = 0; x < 32; x++) {
|
||||
const uint8_t *p = &header[128*z + x*4];
|
||||
|
||||
uint32_t offset = (p[0] << 16) | (p[1] << 8) | p[2];
|
||||
|
||||
if (!offset)
|
||||
continue;
|
||||
|
||||
uint8_t len = p[3];
|
||||
|
||||
map.emplace(offset, ChunkDesc(x, z, len));
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
void Region::visitChunks(const char *filename, const ChunkVisitor &visitor) {
|
||||
std::ifstream file;
|
||||
file.exceptions(std::ios::badbit);
|
||||
file.open(filename, std::ios::in | std::ios::binary);
|
||||
|
||||
ChunkMap chunkMap;
|
||||
|
||||
{
|
||||
uint8_t header[4096];
|
||||
file.read((char *)header, sizeof(header));
|
||||
|
||||
chunkMap = processHeader(header);
|
||||
}
|
||||
|
||||
bool seen[SIZE][SIZE] = {};
|
||||
|
||||
size_t i = 1, c = 0;
|
||||
while (!file.eof() && !file.fail()) {
|
||||
auto it = chunkMap.find(i);
|
||||
if (it == chunkMap.end()) {
|
||||
file.ignore(4096);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
chunk_idx_t x = std::get<0>(it->second);
|
||||
chunk_idx_t z = std::get<1>(it->second);
|
||||
size_t len = std::get<2>(it->second);
|
||||
|
||||
if (seen[x][z])
|
||||
throw std::invalid_argument("duplicate chunk");
|
||||
|
||||
seen[x][z] = true;
|
||||
c++;
|
||||
|
||||
uint8_t buffer[len * 4096];
|
||||
std::memset(buffer, 0, sizeof(buffer));
|
||||
file.read((char *)buffer, len * 4096);
|
||||
|
||||
ChunkData chunk(Buffer(buffer, len * 4096));
|
||||
visitor(x, z, &chunk);
|
||||
|
||||
i += len;
|
||||
}
|
||||
|
||||
if (c != chunkMap.size())
|
||||
throw std::invalid_argument("region incomplete");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015-2021, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Chunk.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace World {
|
||||
|
||||
class Region {
|
||||
public:
|
||||
// Number of chunks in a region in each dimension
|
||||
static const uint32_t SIZE = 32;
|
||||
|
||||
typedef std::function<void (chunk_idx_t, chunk_idx_t, const ChunkData *)> ChunkVisitor;
|
||||
|
||||
Region() = delete;
|
||||
|
||||
private:
|
||||
typedef std::tuple<chunk_idx_t, chunk_idx_t, uint8_t> ChunkDesc;
|
||||
typedef std::unordered_map<uint32_t, ChunkDesc> ChunkMap;
|
||||
|
||||
static ChunkMap processHeader(const uint8_t header[4096]);
|
||||
|
||||
public:
|
||||
static void visitChunks(const char *filename, const ChunkVisitor &visitor);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -1,230 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015-2021, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#include "Section.hpp"
|
||||
#include "../Resource/Biome.hpp"
|
||||
#include "../NBT/ByteTag.hpp"
|
||||
#include "../NBT/IntTag.hpp"
|
||||
#include "../NBT/StringTag.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace World {
|
||||
|
||||
Section::Section(const std::shared_ptr<const NBT::CompoundTag> §ion) {
|
||||
const std::shared_ptr<const NBT::ByteTag> YByteTag = section->get<NBT::ByteTag>("Y");
|
||||
if (YByteTag) {
|
||||
Y = int8_t(YByteTag->getValue());
|
||||
} else {
|
||||
const std::shared_ptr<const NBT::IntTag> YIntTag = assertValue(section->get<NBT::IntTag>("Y"));
|
||||
int32_t value = YIntTag->getValue();
|
||||
if (int8_t(value) != value)
|
||||
throw std::invalid_argument("unsupported section Y coordinate");
|
||||
Y = value;
|
||||
}
|
||||
blockLight = section->get<NBT::ByteArrayTag>("BlockLight");
|
||||
}
|
||||
|
||||
const Resource::BlockType * Section::getBlockStateAt(block_idx_t, block_idx_t, block_idx_t) const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint8_t Section::getBiomeAt(block_idx_t x, block_idx_t y, block_idx_t z) const {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
std::unique_ptr<Section> Section::makeSection(const std::shared_ptr<const NBT::CompoundTag> §ion, uint32_t dataVersion) {
|
||||
{
|
||||
const std::shared_ptr<const NBT::CompoundTag> blockStates = section->get<NBT::CompoundTag>("block_states");
|
||||
if (blockStates) {
|
||||
const std::shared_ptr<const NBT::ListTag> palette = assertValue(blockStates->get<NBT::ListTag>("palette"));
|
||||
std::shared_ptr<const NBT::LongArrayTag> data = blockStates->get<NBT::LongArrayTag>("data");
|
||||
|
||||
const std::shared_ptr<const NBT::CompoundTag> biomes = assertValue(section->get<NBT::CompoundTag>("biomes"));
|
||||
const std::shared_ptr<const NBT::ListTag> biomePalette = assertValue(biomes->get<NBT::ListTag>("palette"));
|
||||
std::shared_ptr<const NBT::LongArrayTag> biomeData = biomes->get<NBT::LongArrayTag>("data");
|
||||
|
||||
return std::unique_ptr<Section>(new PaletteSection(
|
||||
section, std::move(data), palette, std::move(biomeData), biomePalette, dataVersion
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::shared_ptr<const NBT::LongArrayTag> blockStates = section->get<NBT::LongArrayTag>("BlockStates");
|
||||
if (blockStates) {
|
||||
const std::shared_ptr<const NBT::ListTag> palette = assertValue(section->get<NBT::ListTag>("Palette"));
|
||||
|
||||
return std::unique_ptr<Section>(new PaletteSection(
|
||||
section, std::move(blockStates), palette,
|
||||
std::shared_ptr<const NBT::LongArrayTag>(), std::shared_ptr<const NBT::ListTag>(), dataVersion
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::shared_ptr<const NBT::ByteArrayTag> blocks = section->get<NBT::ByteArrayTag>("Blocks");
|
||||
if (blocks) {
|
||||
std::shared_ptr<const NBT::ByteArrayTag> data = assertValue(section->get<NBT::ByteArrayTag>("Data"));
|
||||
|
||||
return std::unique_ptr<Section>(new LegacySection(section, std::move(blocks), std::move(data)));
|
||||
}
|
||||
}
|
||||
|
||||
return std::unique_ptr<Section>(new Section(section));
|
||||
}
|
||||
|
||||
|
||||
const Resource::BlockType * LegacySection::getBlockStateAt(block_idx_t x, block_idx_t y, block_idx_t z) const {
|
||||
uint8_t type = getBlockAt(x, y, z);
|
||||
uint8_t data = getDataAt(x, y, z);
|
||||
|
||||
return Resource::LEGACY_BLOCK_TYPES.types[type][data];
|
||||
}
|
||||
|
||||
|
||||
const Resource::BlockType * PaletteSection::lookup(const std::string &name, uint32_t dataVersion) {
|
||||
if (dataVersion < 1900 && name == "minecraft:stone_slab")
|
||||
return Resource::BlockType::lookup("minecraft:smooth_stone_slab");
|
||||
|
||||
return Resource::BlockType::lookup(name);
|
||||
}
|
||||
|
||||
PaletteSection::PaletteSection(
|
||||
const std::shared_ptr<const NBT::CompoundTag> §ion,
|
||||
std::shared_ptr<const NBT::LongArrayTag> &&blockStates0,
|
||||
const std::shared_ptr<const NBT::ListTag> &paletteData,
|
||||
std::shared_ptr<const NBT::LongArrayTag> &&biomes0,
|
||||
const std::shared_ptr<const NBT::ListTag> &biomePaletteData,
|
||||
uint32_t dataVersion0
|
||||
) : Section(section), blockStates(blockStates0), biomes(biomes0), dataVersion(dataVersion0) {
|
||||
bits = 4;
|
||||
while ((1u << bits) < paletteData->size()) {
|
||||
bits++;
|
||||
|
||||
if (bits > 12)
|
||||
throw std::invalid_argument("unsupported block palette size");
|
||||
}
|
||||
|
||||
biomeBits = 1;
|
||||
if (biomePaletteData) {
|
||||
while ((1u << biomeBits) < biomePaletteData->size()) {
|
||||
biomeBits++;
|
||||
|
||||
if (biomeBits > 6)
|
||||
throw std::invalid_argument("unsupported biome palette size");
|
||||
}
|
||||
}
|
||||
|
||||
unsigned expectedLength;
|
||||
|
||||
if (dataVersion < 2529) {
|
||||
expectedLength = 64 * bits;
|
||||
} else {
|
||||
unsigned blocksPerWord = (64 / bits);
|
||||
expectedLength = (4096 + blocksPerWord - 1) / blocksPerWord;
|
||||
}
|
||||
|
||||
if (blockStates && blockStates->getLength() != expectedLength)
|
||||
throw std::invalid_argument("corrupt section block data");
|
||||
|
||||
unsigned biomesPerWord = (64 / biomeBits);
|
||||
unsigned expectedBiomeLength = (64 + biomesPerWord - 1) / biomesPerWord;
|
||||
|
||||
if (biomes && biomes->getLength() != expectedBiomeLength)
|
||||
throw std::invalid_argument("corrupt section biome data");
|
||||
|
||||
for (const auto &entry : *paletteData) {
|
||||
const NBT::CompoundTag &paletteEntry = *assertValue(dynamic_cast<const NBT::CompoundTag *>(entry.get()));
|
||||
std::string name = assertValue(paletteEntry.get<NBT::StringTag>("Name"))->getValue();
|
||||
|
||||
const Resource::BlockType *type = lookup(name, dataVersion);
|
||||
if (!type)
|
||||
std::fprintf(stderr, "Warning: unknown block type: %s\n", name.c_str());
|
||||
|
||||
palette.push_back(type);
|
||||
}
|
||||
|
||||
if (biomePaletteData) {
|
||||
for (const auto &entry : *biomePaletteData) {
|
||||
const NBT::StringTag &paletteEntry =
|
||||
*assertValue(dynamic_cast<const NBT::StringTag *>(entry.get()));
|
||||
std::string name = paletteEntry.getValue();
|
||||
|
||||
auto it = Resource::Biome::Names.find(name);
|
||||
uint8_t biome;
|
||||
if (it != Resource::Biome::Names.end()) {
|
||||
biome = it->second;
|
||||
} else {
|
||||
std::fprintf(stderr, "Warning: unknown biome: %s\n", name.c_str());
|
||||
biome = 0xff;
|
||||
}
|
||||
|
||||
biomePalette.push_back(biome);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Resource::BlockType * PaletteSection::getBlockStateAt(block_idx_t x, block_idx_t y, block_idx_t z) const {
|
||||
if (!blockStates)
|
||||
return palette.at(0);
|
||||
|
||||
size_t index = getIndex(x, y, z);
|
||||
size_t bitIndex;
|
||||
|
||||
if (dataVersion < 2529) {
|
||||
bitIndex = bits * index;
|
||||
} else {
|
||||
unsigned blocksPerWord = (64 / bits);
|
||||
size_t word = index / blocksPerWord;
|
||||
bitIndex = 64 * word + bits * (index % blocksPerWord);
|
||||
}
|
||||
|
||||
size_t pos = bitIndex >> 3;
|
||||
unsigned shift = bitIndex & 7;
|
||||
uint16_t mask = (1u << bits) - 1;
|
||||
|
||||
uint32_t v = blockStates->getPointer()[mangleByteIndex(pos)];
|
||||
|
||||
if (shift + bits > 8)
|
||||
v |= blockStates->getPointer()[mangleByteIndex(pos + 1)] << 8;
|
||||
if (shift + bits > 16)
|
||||
v |= blockStates->getPointer()[mangleByteIndex(pos + 2)] << 16;
|
||||
/* We do not need to check for shift+bits > 24: bits should never
|
||||
be greater than 12, so our value will never span more than 3 bytes */
|
||||
|
||||
return palette.at((v >> shift) & mask);
|
||||
}
|
||||
|
||||
uint8_t PaletteSection::getBiomeAt(block_idx_t x, block_idx_t y, block_idx_t z) const {
|
||||
if (!biomes)
|
||||
return biomePalette.at(0);
|
||||
|
||||
size_t index = getBiomeIndex(x, y, z);
|
||||
|
||||
unsigned biomesPerWord = (64 / biomeBits);
|
||||
size_t word = index / biomesPerWord;
|
||||
size_t bitIndex = 64 * word + biomeBits * (index % biomesPerWord);
|
||||
|
||||
size_t pos = bitIndex >> 3;
|
||||
unsigned shift = bitIndex & 7;
|
||||
uint16_t mask = (1u << biomeBits) - 1;
|
||||
|
||||
uint32_t v = biomes->getPointer()[mangleByteIndex(pos)];
|
||||
|
||||
if (shift + biomeBits > 8)
|
||||
v |= biomes->getPointer()[mangleByteIndex(pos + 1)] << 8;
|
||||
/* We do not need to check for shift+bits > 16: biomeBits should never
|
||||
be greater than 6, so our value will never span more than 2 bytes */
|
||||
|
||||
return biomePalette.at((v >> shift) & mask);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,142 +0,0 @@
|
|||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
/*
|
||||
Copyright (c) 2015-2021, Matthias Schiffer <mschiffer@universe-factory.net>
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#include "../NBT/ByteArrayTag.hpp"
|
||||
#include "../NBT/CompoundTag.hpp"
|
||||
#include "../NBT/ListTag.hpp"
|
||||
#include "../NBT/LongArrayTag.hpp"
|
||||
#include "../Resource/BlockType.hpp"
|
||||
#include "../Util.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
|
||||
|
||||
namespace MinedMap {
|
||||
namespace World {
|
||||
|
||||
class Section {
|
||||
public:
|
||||
// Number of blocks in a section in each dimension
|
||||
static const uint32_t SIZE = 16;
|
||||
|
||||
// Since Minecraft 1.15, biome information is stored for
|
||||
// 4x4x4 block groups
|
||||
static const unsigned BSHIFT = 2;
|
||||
// Number of biome values in a chunk in x/z dimensions
|
||||
static const uint32_t BSIZE = SIZE >> BSHIFT;
|
||||
|
||||
|
||||
private:
|
||||
section_idx_t Y;
|
||||
std::shared_ptr<const NBT::ByteArrayTag> blockLight;
|
||||
|
||||
protected:
|
||||
static size_t getIndex(block_idx_t x, block_idx_t y, block_idx_t z) {
|
||||
if (x >= SIZE || y >= SIZE || z >= SIZE)
|
||||
throw std::range_error("Section::getIndex(): bad coordinates");
|
||||
|
||||
return SIZE*SIZE*y + SIZE*z + x;
|
||||
}
|
||||
|
||||
static size_t getBiomeIndex(block_idx_t x, block_idx_t y, block_idx_t z) {
|
||||
if (x >= SIZE || y >= SIZE || z >= SIZE)
|
||||
throw std::range_error("Section::getBiomeIndex(): bad coordinates");
|
||||
|
||||
return BSIZE*BSIZE*(y>>BSHIFT) + BSIZE*(z>>BSHIFT) + (x>>BSHIFT);
|
||||
}
|
||||
|
||||
static uint8_t getHalf(const uint8_t *v, block_idx_t x, block_idx_t y, block_idx_t z) {
|
||||
size_t i = getIndex(x, y, z);
|
||||
|
||||
if (i % 2)
|
||||
return (v[i/2] >> 4);
|
||||
else
|
||||
return (v[i/2] & 0xf);
|
||||
}
|
||||
|
||||
Section(const std::shared_ptr<const NBT::CompoundTag> §ion);
|
||||
|
||||
public:
|
||||
virtual ~Section() {}
|
||||
|
||||
section_idx_t getY() const { return Y; };
|
||||
|
||||
virtual const Resource::BlockType * getBlockStateAt(block_idx_t x, block_idx_t y, block_idx_t z) const;
|
||||
virtual uint8_t getBiomeAt(block_idx_t x, block_idx_t y, block_idx_t z) const;
|
||||
|
||||
uint8_t getBlockLightAt(block_idx_t x, block_idx_t y, block_idx_t z) const {
|
||||
if (!blockLight)
|
||||
return 0;
|
||||
|
||||
return getHalf(blockLight->getPointer(), x, y, z);
|
||||
}
|
||||
|
||||
static std::unique_ptr<Section> makeSection(const std::shared_ptr<const NBT::CompoundTag> §ion, uint32_t dataVersion);
|
||||
};
|
||||
|
||||
class LegacySection : public Section {
|
||||
private:
|
||||
std::shared_ptr<const NBT::ByteArrayTag> blocks;
|
||||
std::shared_ptr<const NBT::ByteArrayTag> data;
|
||||
|
||||
|
||||
uint8_t getBlockAt(block_idx_t x, block_idx_t y, block_idx_t z) const {
|
||||
return blocks->getValue(getIndex(x, y, z));
|
||||
}
|
||||
|
||||
uint8_t getDataAt(block_idx_t x, block_idx_t y, block_idx_t z) const {
|
||||
return getHalf(data->getPointer(), x, y, z);
|
||||
}
|
||||
|
||||
public:
|
||||
LegacySection(
|
||||
const std::shared_ptr<const NBT::CompoundTag> §ion,
|
||||
std::shared_ptr<const NBT::ByteArrayTag> &&blocks0,
|
||||
std::shared_ptr<const NBT::ByteArrayTag> &&data0
|
||||
) : Section(section), blocks(blocks0), data(data0) {}
|
||||
|
||||
virtual const Resource::BlockType * getBlockStateAt(block_idx_t x, block_idx_t y, block_idx_t z) const;
|
||||
};
|
||||
|
||||
class PaletteSection : public Section {
|
||||
private:
|
||||
std::shared_ptr<const NBT::LongArrayTag> blockStates;
|
||||
std::vector<const Resource::BlockType *> palette;
|
||||
unsigned bits;
|
||||
|
||||
std::shared_ptr<const NBT::LongArrayTag> biomes;
|
||||
std::vector<uint8_t> biomePalette;
|
||||
unsigned biomeBits;
|
||||
|
||||
uint32_t dataVersion;
|
||||
|
||||
static const Resource::BlockType * lookup(const std::string &name, uint32_t dataVersion);
|
||||
|
||||
static size_t mangleByteIndex(size_t index) {
|
||||
return (index & ~(size_t)7) + 7 - (index & 7);
|
||||
}
|
||||
|
||||
public:
|
||||
PaletteSection(
|
||||
const std::shared_ptr<const NBT::CompoundTag> §ion,
|
||||
std::shared_ptr<const NBT::LongArrayTag> &&blockStates0,
|
||||
const std::shared_ptr<const NBT::ListTag> &paletteData,
|
||||
std::shared_ptr<const NBT::LongArrayTag> &&biomes0,
|
||||
const std::shared_ptr<const NBT::ListTag> &biomePaletteData,
|
||||
uint32_t dataVersion0
|
||||
);
|
||||
|
||||
virtual const Resource::BlockType * getBlockStateAt(block_idx_t x, block_idx_t y, block_idx_t z) const;
|
||||
virtual uint8_t getBiomeAt(block_idx_t x, block_idx_t y, block_idx_t z) const;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
327
src/core/common.rs
Normal file
327
src/core/common.rs
Normal file
|
@ -0,0 +1,327 @@
|
|||
//! Common data types and functions used by multiple generation steps
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
fmt::Debug,
|
||||
hash::Hash,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use bincode::{Decode, Encode};
|
||||
use clap::ValueEnum;
|
||||
use regex::{Regex, RegexSet};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
io::fs::FileMetaVersion,
|
||||
resource::Biome,
|
||||
types::*,
|
||||
world::{block_entity::BlockEntity, layer},
|
||||
};
|
||||
|
||||
// Increase to force regeneration of all output files
|
||||
|
||||
/// MinedMap processed region data version number
|
||||
///
|
||||
/// Increase when the generation of processed regions from region data changes
|
||||
/// (usually because of updated resource data)
|
||||
pub const REGION_FILE_META_VERSION: FileMetaVersion = FileMetaVersion(7);
|
||||
|
||||
/// MinedMap map tile data version number
|
||||
///
|
||||
/// Increase when the generation of map tiles from processed regions changes
|
||||
/// (because of code changes in tile generation)
|
||||
pub const MAP_FILE_META_VERSION: FileMetaVersion = FileMetaVersion(0);
|
||||
|
||||
/// MinedMap lightmap data version number
|
||||
///
|
||||
/// Increase when the generation of lightmap tiles from region data changes
|
||||
/// (usually because of updated resource data)
|
||||
pub const LIGHTMAP_FILE_META_VERSION: FileMetaVersion = FileMetaVersion(5);
|
||||
|
||||
/// MinedMap mipmap data version number
|
||||
///
|
||||
/// Increase when the mipmap generation changes (this should not happen)
|
||||
pub const MIPMAP_FILE_META_VERSION: FileMetaVersion = FileMetaVersion(0);
|
||||
|
||||
/// MinedMap processed entity data version number
|
||||
///
|
||||
/// Increase when entity collection changes bacause of code changes.
|
||||
pub const ENTITIES_FILE_META_VERSION: FileMetaVersion = FileMetaVersion(3);
|
||||
|
||||
/// Coordinate pair of a generated tile
|
||||
///
|
||||
/// Each tile corresponds to one Minecraft region file
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct TileCoords {
|
||||
/// The X coordinate
|
||||
pub x: i32,
|
||||
/// The Z coordinate
|
||||
pub z: i32,
|
||||
}
|
||||
|
||||
impl Debug for TileCoords {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "({}, {})", self.x, self.z)
|
||||
}
|
||||
}
|
||||
|
||||
/// Set of tile coordinates
|
||||
///
|
||||
/// Used to store list of populated tiles for each mipmap level in the
|
||||
/// viewer metadata file.
|
||||
#[derive(Debug, Clone, Default, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct TileCoordMap(pub BTreeMap<i32, BTreeSet<i32>>);
|
||||
|
||||
impl TileCoordMap {
|
||||
/// Checks whether the map contains a given coordinate pair
|
||||
pub fn contains(&self, coords: TileCoords) -> bool {
|
||||
let Some(xs) = self.0.get(&coords.z) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
xs.contains(&coords.x)
|
||||
}
|
||||
}
|
||||
|
||||
/// Data structure for storing chunk data between processing and rendering steps
|
||||
#[derive(Debug, Encode, Decode)]
|
||||
pub struct ProcessedChunk {
|
||||
/// Block type data
|
||||
pub blocks: Box<layer::BlockArray>,
|
||||
/// Biome data
|
||||
pub biomes: Box<layer::BiomeArray>,
|
||||
/// Block height/depth data
|
||||
pub depths: Box<layer::DepthArray>,
|
||||
}
|
||||
|
||||
/// Data structure for storing region data between processing and rendering steps
|
||||
#[derive(Debug, Default, Encode, Decode)]
|
||||
pub struct ProcessedRegion {
|
||||
/// List of biomes used in the region
|
||||
///
|
||||
/// Indexed by [ProcessedChunk] biome data
|
||||
pub biome_list: Vec<Biome>,
|
||||
/// Processed chunk data
|
||||
pub chunks: ChunkArray<Option<Box<ProcessedChunk>>>,
|
||||
}
|
||||
|
||||
/// Data structure for storing entity data between processing and collection steps
|
||||
#[derive(Debug, Default, Encode, Decode)]
|
||||
pub struct ProcessedEntities {
|
||||
/// List of block entities
|
||||
pub block_entities: Vec<BlockEntity>,
|
||||
}
|
||||
|
||||
/// Derives a filename from region coordinates and a file extension
|
||||
///
|
||||
/// Can be used for input regions, processed data or rendered tiles
|
||||
fn coord_filename(coords: TileCoords, ext: &str) -> String {
|
||||
format!("r.{}.{}.{}", coords.x, coords.z, ext)
|
||||
}
|
||||
|
||||
/// Tile kind corresponding to a map layer
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum TileKind {
|
||||
/// Regular map tile contains block colors
|
||||
Map,
|
||||
/// Lightmap tile for illumination layer
|
||||
Lightmap,
|
||||
}
|
||||
|
||||
/// Common configuration based on command line arguments
|
||||
#[derive(Debug)]
|
||||
pub struct Config {
|
||||
/// Number of threads for parallel processing
|
||||
pub num_threads: usize,
|
||||
/// Number of threads for initial parallel processing
|
||||
pub num_threads_initial: usize,
|
||||
/// Path of input region directory
|
||||
pub region_dir: PathBuf,
|
||||
/// Path of input `level.dat` file
|
||||
pub level_dat_path: PathBuf,
|
||||
/// Path of input `level.dat_old` file
|
||||
pub level_dat_old_path: PathBuf,
|
||||
/// Base path for storage of rendered tile data
|
||||
pub output_dir: PathBuf,
|
||||
/// Path for storage of intermediate processed data files
|
||||
pub processed_dir: PathBuf,
|
||||
/// Path for storage of processed entity data files
|
||||
pub entities_dir: PathBuf,
|
||||
/// Path for storage of the final merged processed entity data file
|
||||
pub entities_path_final: PathBuf,
|
||||
/// Path of viewer metadata file
|
||||
pub viewer_info_path: PathBuf,
|
||||
/// Path of viewer entities file
|
||||
pub viewer_entities_path: PathBuf,
|
||||
/// Format of generated map tiles
|
||||
pub image_format: ImageFormat,
|
||||
/// Sign text filter patterns
|
||||
pub sign_patterns: RegexSet,
|
||||
/// Sign text transformation pattern
|
||||
pub sign_transforms: Vec<(Regex, String)>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Crates a new [Config] from [command line arguments](super::Args)
|
||||
pub fn new(args: &super::Args) -> Result<Self> {
|
||||
let num_threads = match args.jobs {
|
||||
Some(0) => num_cpus::get(),
|
||||
Some(threads) => threads,
|
||||
None => 1,
|
||||
};
|
||||
let num_threads_initial = args.jobs_initial.unwrap_or(num_threads);
|
||||
|
||||
let region_dir = [&args.input_dir, Path::new("region")].iter().collect();
|
||||
let level_dat_path = [&args.input_dir, Path::new("level.dat")].iter().collect();
|
||||
let level_dat_old_path = [&args.input_dir, Path::new("level.dat_old")]
|
||||
.iter()
|
||||
.collect();
|
||||
let processed_dir: PathBuf = [&args.output_dir, Path::new("processed")].iter().collect();
|
||||
let entities_dir: PathBuf = [&processed_dir, Path::new("entities")].iter().collect();
|
||||
let entities_path_final = [&entities_dir, Path::new("entities.bin")].iter().collect();
|
||||
let viewer_info_path = [&args.output_dir, Path::new("info.json")].iter().collect();
|
||||
let viewer_entities_path = [&args.output_dir, Path::new("entities.json")]
|
||||
.iter()
|
||||
.collect();
|
||||
|
||||
let sign_patterns = Self::sign_patterns(args).context("Failed to parse sign patterns")?;
|
||||
let sign_transforms =
|
||||
Self::sign_transforms(args).context("Failed to parse sign transforms")?;
|
||||
|
||||
Ok(Config {
|
||||
num_threads,
|
||||
num_threads_initial,
|
||||
region_dir,
|
||||
level_dat_path,
|
||||
level_dat_old_path,
|
||||
output_dir: args.output_dir.clone(),
|
||||
processed_dir,
|
||||
entities_dir,
|
||||
entities_path_final,
|
||||
viewer_info_path,
|
||||
viewer_entities_path,
|
||||
image_format: args.image_format,
|
||||
sign_patterns,
|
||||
sign_transforms,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parses the sign prefixes and sign filters into a [RegexSet]
|
||||
fn sign_patterns(args: &super::Args) -> Result<RegexSet> {
|
||||
let prefix_patterns: Vec<_> = args
|
||||
.sign_prefix
|
||||
.iter()
|
||||
.map(|prefix| format!("^{}", regex::escape(prefix)))
|
||||
.collect();
|
||||
Ok(RegexSet::new(
|
||||
prefix_patterns.iter().chain(args.sign_filter.iter()),
|
||||
)?)
|
||||
}
|
||||
|
||||
/// Parses the sign transform argument into a vector of [Regex] and
|
||||
/// corresponding replacement strings
|
||||
fn sign_transforms(args: &super::Args) -> Result<Vec<(Regex, String)>> {
|
||||
let splitter = Regex::new(r"^s/((?:[^\\/]|\\.)*)/((?:[^\\/]|\\.)*)/$").unwrap();
|
||||
|
||||
args.sign_transform
|
||||
.iter()
|
||||
.map(|t| Self::sign_transform(&splitter, t))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Parses the sign transform argument into a [Regex] and its corresponding
|
||||
/// replacement string
|
||||
fn sign_transform(splitter: &Regex, transform: &str) -> Result<(Regex, String)> {
|
||||
let captures = splitter
|
||||
.captures(transform)
|
||||
.with_context(|| format!("Invalid transform pattern '{}'", transform))?;
|
||||
let regexp = Regex::new(&captures[1])?;
|
||||
let replacement = captures[2].to_string();
|
||||
Ok((regexp, replacement))
|
||||
}
|
||||
|
||||
/// Constructs the path to an input region file
|
||||
pub fn region_path(&self, coords: TileCoords) -> PathBuf {
|
||||
let filename = coord_filename(coords, "mca");
|
||||
[&self.region_dir, Path::new(&filename)].iter().collect()
|
||||
}
|
||||
|
||||
/// Constructs the path of an intermediate processed region file
|
||||
pub fn processed_path(&self, coords: TileCoords) -> PathBuf {
|
||||
let filename = coord_filename(coords, "bin");
|
||||
[&self.processed_dir, Path::new(&filename)].iter().collect()
|
||||
}
|
||||
|
||||
/// Constructs the base output path for processed entity data
|
||||
pub fn entities_dir(&self, level: usize) -> PathBuf {
|
||||
[&self.entities_dir, Path::new(&level.to_string())]
|
||||
.iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Constructs the path of a processed entity data file
|
||||
pub fn entities_path(&self, level: usize, coords: TileCoords) -> PathBuf {
|
||||
let filename = coord_filename(coords, "bin");
|
||||
let dir = self.entities_dir(level);
|
||||
[Path::new(&dir), Path::new(&filename)].iter().collect()
|
||||
}
|
||||
|
||||
/// Constructs the base output path for a [TileKind] and mipmap level
|
||||
pub fn tile_dir(&self, kind: TileKind, level: usize) -> PathBuf {
|
||||
let prefix = match kind {
|
||||
TileKind::Map => "map",
|
||||
TileKind::Lightmap => "light",
|
||||
};
|
||||
let dir = format!("{}/{}", prefix, level);
|
||||
[&self.output_dir, Path::new(&dir)].iter().collect()
|
||||
}
|
||||
|
||||
/// Returns the file extension for the configured image format
|
||||
pub fn tile_extension(&self) -> &'static str {
|
||||
match self.image_format {
|
||||
ImageFormat::Png => "png",
|
||||
ImageFormat::Webp => "webp",
|
||||
}
|
||||
}
|
||||
/// Returns the configurured image format for the image library
|
||||
pub fn tile_image_format(&self) -> image::ImageFormat {
|
||||
match self.image_format {
|
||||
ImageFormat::Png => image::ImageFormat::Png,
|
||||
ImageFormat::Webp => image::ImageFormat::WebP,
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs the path of an output tile image
|
||||
pub fn tile_path(&self, kind: TileKind, level: usize, coords: TileCoords) -> PathBuf {
|
||||
let filename = coord_filename(coords, self.tile_extension());
|
||||
let dir = self.tile_dir(kind, level);
|
||||
[Path::new(&dir), Path::new(&filename)].iter().collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Format of generated map tiles
|
||||
#[derive(Debug, Clone, Copy, Default, ValueEnum)]
|
||||
pub enum ImageFormat {
|
||||
/// Generate PNG images
|
||||
#[default]
|
||||
Png,
|
||||
/// Generate WebP images
|
||||
Webp,
|
||||
}
|
||||
|
||||
/// Copies a chunk image into a region tile
|
||||
pub fn overlay_chunk<I, J>(image: &mut I, chunk: &J, coords: ChunkCoords)
|
||||
where
|
||||
I: image::GenericImage,
|
||||
J: image::GenericImageView<Pixel = I::Pixel>,
|
||||
{
|
||||
image::imageops::overlay(
|
||||
image,
|
||||
chunk,
|
||||
coords.x.0 as i64 * BLOCKS_PER_CHUNK as i64,
|
||||
coords.z.0 as i64 * BLOCKS_PER_CHUNK as i64,
|
||||
);
|
||||
}
|
122
src/core/entity_collector.rs
Normal file
122
src/core/entity_collector.rs
Normal file
|
@ -0,0 +1,122 @@
|
|||
//! The [EntityCollector]
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use tracing::{info, warn};
|
||||
|
||||
use super::{common::*, tile_collector::TileCollector, tile_merger::TileMerger};
|
||||
use crate::io::{fs, storage};
|
||||
|
||||
/// Generates mipmap tiles from full-resolution tile images
|
||||
pub struct EntityCollector<'a> {
|
||||
/// Common MinedMap configuration from command line
|
||||
config: &'a Config,
|
||||
/// List of populated tiles for base mipmap level (level 0)
|
||||
regions: &'a [TileCoords],
|
||||
}
|
||||
|
||||
impl TileMerger for EntityCollector<'_> {
|
||||
fn file_meta_version(&self) -> fs::FileMetaVersion {
|
||||
ENTITIES_FILE_META_VERSION
|
||||
}
|
||||
|
||||
fn tile_path(&self, level: usize, coords: TileCoords) -> std::path::PathBuf {
|
||||
self.config.entities_path(level, coords)
|
||||
}
|
||||
|
||||
fn write_tile(
|
||||
&self,
|
||||
file: &mut std::io::BufWriter<std::fs::File>,
|
||||
sources: &[super::tile_merger::Source],
|
||||
) -> Result<()> {
|
||||
Self::merge_entity_lists(file, sources.iter().map(|source| &source.1))
|
||||
}
|
||||
}
|
||||
|
||||
impl TileCollector for EntityCollector<'_> {
|
||||
type CollectOutput = ();
|
||||
|
||||
fn tiles(&self) -> &[TileCoords] {
|
||||
self.regions
|
||||
}
|
||||
|
||||
fn prepare(&self, level: usize) -> Result<()> {
|
||||
fs::create_dir_all(&self.config.entities_dir(level))
|
||||
}
|
||||
|
||||
fn finish(
|
||||
&self,
|
||||
_level: usize,
|
||||
_outputs: impl Iterator<Item = Self::CollectOutput>,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn collect_one(
|
||||
&self,
|
||||
level: usize,
|
||||
coords: TileCoords,
|
||||
prev: &TileCoordMap,
|
||||
) -> Result<Self::CollectOutput> {
|
||||
self.merge_tiles(level, coords, prev)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EntityCollector<'a> {
|
||||
/// Constructs a new EntityCollector
|
||||
pub fn new(config: &'a Config, regions: &'a [TileCoords]) -> Self {
|
||||
EntityCollector { config, regions }
|
||||
}
|
||||
|
||||
/// Merges multiple entity lists into one
|
||||
fn merge_entity_lists<P: AsRef<Path>>(
|
||||
file: &mut std::io::BufWriter<std::fs::File>,
|
||||
sources: impl Iterator<Item = P>,
|
||||
) -> Result<()> {
|
||||
let mut output = ProcessedEntities::default();
|
||||
|
||||
for source_path in sources {
|
||||
let mut source: ProcessedEntities = match storage::read_file(source_path.as_ref()) {
|
||||
Ok(source) => source,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"Failed to read entity data file {}: {:?}",
|
||||
source_path.as_ref().display(),
|
||||
err,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
output.block_entities.append(&mut source.block_entities);
|
||||
}
|
||||
|
||||
storage::write(file, &output).context("Failed to write entity data")
|
||||
}
|
||||
|
||||
/// Runs the mipmap generation
|
||||
pub fn run(self) -> Result<()> {
|
||||
info!("Collecting entity data...");
|
||||
|
||||
let tile_stack = self.collect_tiles()?;
|
||||
|
||||
// Final merge
|
||||
let level = tile_stack.len() - 1;
|
||||
let tile_map = &tile_stack[level];
|
||||
let sources: Vec<_> = [(-1, -1), (-1, 0), (0, -1), (0, 0)]
|
||||
.into_iter()
|
||||
.map(|(x, z)| TileCoords { x, z })
|
||||
.filter(|&coords| tile_map.contains(coords))
|
||||
.map(|coords| self.tile_path(level, coords))
|
||||
.collect();
|
||||
|
||||
fs::create_with_tmpfile(&self.config.entities_path_final, |file| {
|
||||
Self::merge_entity_lists(file, sources.iter())
|
||||
})?;
|
||||
|
||||
info!("Collected entity data.");
|
||||
Ok(())
|
||||
}
|
||||
}
|
234
src/core/metadata_writer.rs
Normal file
234
src/core/metadata_writer.rs
Normal file
|
@ -0,0 +1,234 @@
|
|||
//! The [MetadataWriter] and related types
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use regex::Regex;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
core::common::*,
|
||||
io::{fs, storage},
|
||||
world::{
|
||||
block_entity::{self, BlockEntity, BlockEntityData},
|
||||
de, sign,
|
||||
},
|
||||
};
|
||||
|
||||
/// Minimum and maximum X and Z tile coordinates for a mipmap level
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Bounds {
|
||||
/// Minimum X coordinate
|
||||
min_x: i32,
|
||||
/// Maximum X coordinate
|
||||
max_x: i32,
|
||||
/// Minimum Z coordinate
|
||||
min_z: i32,
|
||||
/// Maximum Z coordinate
|
||||
max_z: i32,
|
||||
}
|
||||
|
||||
/// Mipmap level information in viewer metadata file
|
||||
#[derive(Debug, Serialize)]
|
||||
struct Mipmap<'t> {
|
||||
/// Minimum and maximum tile coordinates of the mipmap level
|
||||
bounds: Bounds,
|
||||
/// Map of populated tiles for the mipmap level
|
||||
regions: &'t TileCoordMap,
|
||||
}
|
||||
|
||||
/// Initial spawn point for new players
|
||||
#[derive(Debug, Serialize)]
|
||||
struct Spawn {
|
||||
/// Spawn X coordinate
|
||||
x: i32,
|
||||
/// Spawn Z coordinate
|
||||
z: i32,
|
||||
}
|
||||
|
||||
/// Keeps track of enabled MinedMap features
|
||||
#[derive(Debug, Serialize)]
|
||||
struct Features {
|
||||
/// Sign layer
|
||||
signs: bool,
|
||||
}
|
||||
|
||||
/// Viewer metadata JSON data structure
|
||||
#[derive(Debug, Serialize)]
|
||||
struct Metadata<'t> {
|
||||
/// Tile information for each mipmap level
|
||||
mipmaps: Vec<Mipmap<'t>>,
|
||||
/// Initial spawn point for new players
|
||||
spawn: Spawn,
|
||||
/// Enabled MinedMap features
|
||||
features: Features,
|
||||
/// Format of generated map tiles
|
||||
tile_extension: &'static str,
|
||||
}
|
||||
|
||||
/// Viewer entity JSON data structure
|
||||
#[derive(Debug, Serialize, Default)]
|
||||
struct Entities {
|
||||
/// List of signs
|
||||
signs: Vec<BlockEntity>,
|
||||
}
|
||||
|
||||
/// The MetadataWriter is used to generate the viewer metadata file
|
||||
pub struct MetadataWriter<'a> {
|
||||
/// Common MinedMap configuration from command line
|
||||
config: &'a Config,
|
||||
/// Map of generated tiles for each mipmap level
|
||||
tiles: &'a [TileCoordMap],
|
||||
}
|
||||
|
||||
impl<'a> MetadataWriter<'a> {
|
||||
/// Creates a new MetadataWriter
|
||||
pub fn new(config: &'a Config, tiles: &'a [TileCoordMap]) -> Self {
|
||||
MetadataWriter { config, tiles }
|
||||
}
|
||||
|
||||
/// Helper to construct a [Mipmap] data structure from a [TileCoordMap]
|
||||
fn mipmap_entry(regions: &TileCoordMap) -> Mipmap {
|
||||
let mut min_x = i32::MAX;
|
||||
let mut max_x = i32::MIN;
|
||||
let mut min_z = i32::MAX;
|
||||
let mut max_z = i32::MIN;
|
||||
|
||||
for (&z, xs) in ®ions.0 {
|
||||
if z < min_z {
|
||||
min_z = z;
|
||||
}
|
||||
if z > max_z {
|
||||
max_z = z;
|
||||
}
|
||||
|
||||
for &x in xs {
|
||||
if x < min_x {
|
||||
min_x = x;
|
||||
}
|
||||
if x > max_x {
|
||||
max_x = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Mipmap {
|
||||
bounds: Bounds {
|
||||
min_x,
|
||||
max_x,
|
||||
min_z,
|
||||
max_z,
|
||||
},
|
||||
regions,
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads and deserializes the `level.dat` of the Minecraft save data
|
||||
fn read_level_dat(&self) -> Result<de::LevelDat> {
|
||||
let res = crate::nbt::data::from_file(&self.config.level_dat_path);
|
||||
if res.is_err() {
|
||||
if let Ok(level_dat_old) = crate::nbt::data::from_file(&self.config.level_dat_old_path)
|
||||
{
|
||||
return Ok(level_dat_old);
|
||||
}
|
||||
}
|
||||
res.context("Failed to read level.dat")
|
||||
}
|
||||
|
||||
/// Generates [Spawn] data from a [de::LevelDat]
|
||||
fn spawn(level_dat: &de::LevelDat) -> Spawn {
|
||||
Spawn {
|
||||
x: level_dat.data.spawn_x,
|
||||
z: level_dat.data.spawn_z,
|
||||
}
|
||||
}
|
||||
|
||||
/// Filter signs according to the sign pattern configuration
|
||||
fn sign_filter(&self, sign: &block_entity::Sign) -> bool {
|
||||
let front_text = sign.front_text.to_string();
|
||||
if self.config.sign_patterns.is_match(front_text.trim()) {
|
||||
return true;
|
||||
}
|
||||
let back_text = sign.back_text.to_string();
|
||||
if self.config.sign_patterns.is_match(back_text.trim()) {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Applies a single transform to a [sign::SignText]
|
||||
///
|
||||
/// The regular expression is applied for each line of the sign text
|
||||
/// separately (actually for each element when JSON text is used)
|
||||
fn sign_text_transform(sign_text: &mut sign::SignText, transform: &(Regex, String)) {
|
||||
let (regexp, replacement) = transform;
|
||||
|
||||
for line in &mut sign_text.0 {
|
||||
for text in &mut line.0 {
|
||||
text.text = regexp.replace_all(&text.text, replacement).into_owned()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies the configured transforms to the text of a sign
|
||||
fn sign_transform(&self, sign: &mut block_entity::Sign) {
|
||||
for transform in &self.config.sign_transforms {
|
||||
Self::sign_text_transform(&mut sign.front_text, transform);
|
||||
Self::sign_text_transform(&mut sign.back_text, transform);
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates [Entities] data from collected entity lists
|
||||
fn entities(&self) -> Result<Entities> {
|
||||
let data: ProcessedEntities = storage::read_file(&self.config.entities_path_final)
|
||||
.context("Failed to read entity data file")?;
|
||||
|
||||
let ret = Entities {
|
||||
signs: data
|
||||
.block_entities
|
||||
.into_iter()
|
||||
.filter(|entity| match &entity.data {
|
||||
BlockEntityData::Sign(sign) => self.sign_filter(sign),
|
||||
})
|
||||
.map(|mut entity| {
|
||||
match &mut entity.data {
|
||||
BlockEntityData::Sign(sign) => self.sign_transform(sign),
|
||||
};
|
||||
entity
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Runs the viewer metadata file generation
|
||||
pub fn run(self) -> Result<()> {
|
||||
let level_dat = self.read_level_dat()?;
|
||||
|
||||
let features = Features {
|
||||
signs: !self.config.sign_patterns.is_empty(),
|
||||
};
|
||||
|
||||
let mut metadata = Metadata {
|
||||
mipmaps: Vec::new(),
|
||||
spawn: Self::spawn(&level_dat),
|
||||
features,
|
||||
tile_extension: self.config.tile_extension(),
|
||||
};
|
||||
|
||||
for tile_map in self.tiles.iter() {
|
||||
metadata.mipmaps.push(Self::mipmap_entry(tile_map));
|
||||
}
|
||||
|
||||
fs::create_with_tmpfile(&self.config.viewer_info_path, |file| {
|
||||
serde_json::to_writer(file, &metadata).context("Failed to write info.json")
|
||||
})?;
|
||||
|
||||
let entities = self.entities()?;
|
||||
fs::create_with_tmpfile(&self.config.viewer_entities_path, |file| {
|
||||
serde_json::to_writer(file, &entities).context("Failed to write entities.json")
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
214
src/core/mod.rs
Normal file
214
src/core/mod.rs
Normal file
|
@ -0,0 +1,214 @@
|
|||
//! Core functions of the MinedMap CLI
|
||||
|
||||
mod common;
|
||||
mod entity_collector;
|
||||
mod metadata_writer;
|
||||
mod region_group;
|
||||
mod region_processor;
|
||||
mod tile_collector;
|
||||
mod tile_merger;
|
||||
mod tile_mipmapper;
|
||||
mod tile_renderer;
|
||||
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::mpsc::{self, Receiver},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use clap::Parser;
|
||||
use git_version::git_version;
|
||||
|
||||
use common::{Config, ImageFormat};
|
||||
use metadata_writer::MetadataWriter;
|
||||
use notify::{RecommendedWatcher, RecursiveMode, Watcher as _};
|
||||
use rayon::ThreadPool;
|
||||
use region_processor::RegionProcessor;
|
||||
use tile_mipmapper::TileMipmapper;
|
||||
use tile_renderer::TileRenderer;
|
||||
use tokio::runtime::Runtime;
|
||||
use tracing::{info, warn};
|
||||
|
||||
use self::entity_collector::EntityCollector;
|
||||
|
||||
/// Returns the MinedMap version number
|
||||
fn version() -> &'static str {
|
||||
option_env!("MINEDMAP_VERSION").unwrap_or(
|
||||
git_version!(
|
||||
args = ["--abbrev=7", "--match=v*", "--dirty=-modified"],
|
||||
cargo_prefix = "v",
|
||||
)
|
||||
.strip_prefix("v")
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Command line arguments for minedmap CLI
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(
|
||||
about,
|
||||
version = version(),
|
||||
max_term_width = 100,
|
||||
)]
|
||||
pub struct Args {
|
||||
/// Number of parallel threads to use for processing
|
||||
///
|
||||
/// If not given, only a single thread is used. Pass 0 to
|
||||
/// use one thread per logical CPU core.
|
||||
#[arg(short, long)]
|
||||
pub jobs: Option<usize>,
|
||||
/// Number of parallel threads to use for initial processing
|
||||
///
|
||||
/// Passing this option only makes sense with --watch. The first run after
|
||||
/// starting MinedMap will use as many parallel jobs as configured using
|
||||
/// --job-initial, while subsequent regenerations of tiles will use the
|
||||
/// the number configured using --jobs.
|
||||
///
|
||||
/// If not given, the value from the --jobs option is used.
|
||||
#[arg(long)]
|
||||
pub jobs_initial: Option<usize>,
|
||||
/// Enable verbose messages
|
||||
#[arg(short, long)]
|
||||
pub verbose: bool,
|
||||
/// Watch for file changes and regenerate tiles automatically instead of
|
||||
/// exiting after generation
|
||||
#[arg(long)]
|
||||
pub watch: bool,
|
||||
/// Minimum delay between map generation cycles in watch mode
|
||||
#[arg(long, value_parser = humantime::parse_duration, default_value = "30s")]
|
||||
pub watch_delay: Duration,
|
||||
/// Format of generated map tiles
|
||||
#[arg(long, value_enum, default_value_t)]
|
||||
pub image_format: ImageFormat,
|
||||
/// Prefix for text of signs to show on the map
|
||||
#[arg(long)]
|
||||
pub sign_prefix: Vec<String>,
|
||||
/// Regular expression for text of signs to show on the map
|
||||
///
|
||||
/// --sign-prefix and --sign-filter allow to filter for signs to display;
|
||||
/// by default, none are visible. The options may be passed multiple times,
|
||||
/// and a sign will be visible if it matches any pattern.
|
||||
///
|
||||
/// To make all signs visible, pass an empty string to either option.
|
||||
#[arg(long)]
|
||||
pub sign_filter: Vec<String>,
|
||||
/// Regular expression replacement pattern for sign texts
|
||||
///
|
||||
/// Accepts patterns of the form 's/regexp/replacement/'. Transforms
|
||||
/// are applied to each line of sign texts separately.
|
||||
#[arg(long)]
|
||||
pub sign_transform: Vec<String>,
|
||||
/// Minecraft save directory
|
||||
pub input_dir: PathBuf,
|
||||
/// MinedMap data directory
|
||||
pub output_dir: PathBuf,
|
||||
}
|
||||
|
||||
/// Configures a Rayon thread pool for parallel processing
|
||||
fn setup_threads(num_threads: usize) -> Result<ThreadPool> {
|
||||
rayon::ThreadPoolBuilder::new()
|
||||
.num_threads(num_threads)
|
||||
.build()
|
||||
.context("Failed to configure thread pool")
|
||||
}
|
||||
|
||||
/// Runs all MinedMap generation steps, updating all tiles as needed
|
||||
fn generate(config: &Config, rt: &Runtime) -> Result<()> {
|
||||
let regions = RegionProcessor::new(config).run()?;
|
||||
TileRenderer::new(config, rt, ®ions).run()?;
|
||||
let tiles = TileMipmapper::new(config, ®ions).run()?;
|
||||
EntityCollector::new(config, ®ions).run()?;
|
||||
MetadataWriter::new(config, &tiles).run()
|
||||
}
|
||||
|
||||
/// Creates a file watcher for the
|
||||
fn create_watcher(args: &Args) -> Result<(RecommendedWatcher, Receiver<()>)> {
|
||||
let (tx, rx) = mpsc::sync_channel::<()>(1);
|
||||
let mut watcher = notify::recommended_watcher(move |res| {
|
||||
// Ignore errors - we already have a watch trigger queued if try_send() fails
|
||||
let event: notify::Event = match res {
|
||||
Ok(event) => event,
|
||||
Err(err) => {
|
||||
warn!("Watch error: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let notify::EventKind::Modify(modify_kind) = event.kind else {
|
||||
return;
|
||||
};
|
||||
if !matches!(
|
||||
modify_kind,
|
||||
notify::event::ModifyKind::Data(_)
|
||||
| notify::event::ModifyKind::Name(notify::event::RenameMode::To)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if !event
|
||||
.paths
|
||||
.iter()
|
||||
.any(|path| path.ends_with("level.dat") || path.extension() == Some("mcu".as_ref()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
let _ = tx.try_send(());
|
||||
})?;
|
||||
watcher.watch(&args.input_dir, RecursiveMode::Recursive)?;
|
||||
Ok((watcher, rx))
|
||||
}
|
||||
|
||||
/// Watches the data directory for changes, returning when a change has happened
|
||||
fn wait_watcher(args: &Args, watch_channel: &Receiver<()>) -> Result<()> {
|
||||
info!("Watching for changes...");
|
||||
let () = watch_channel
|
||||
.recv()
|
||||
.context("Failed to read watch event channel")?;
|
||||
info!("Change detected.");
|
||||
|
||||
thread::sleep(args.watch_delay);
|
||||
|
||||
let _ = watch_channel.try_recv();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// MinedMap CLI main function
|
||||
pub fn cli() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
let config = Config::new(&args)?;
|
||||
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(if args.verbose {
|
||||
tracing::Level::DEBUG
|
||||
} else {
|
||||
tracing::Level::INFO
|
||||
})
|
||||
.with_target(false)
|
||||
.init();
|
||||
|
||||
let mut pool = setup_threads(config.num_threads_initial)?;
|
||||
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let watch = args.watch.then(|| create_watcher(&args)).transpose()?;
|
||||
|
||||
pool.install(|| generate(&config, &rt))?;
|
||||
|
||||
let Some((_watcher, watch_channel)) = watch else {
|
||||
// watch mode disabled
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if config.num_threads != config.num_threads_initial {
|
||||
pool = setup_threads(config.num_threads)?;
|
||||
}
|
||||
pool.install(move || {
|
||||
loop {
|
||||
wait_watcher(&args, &watch_channel)?;
|
||||
generate(&config, &rt)?;
|
||||
}
|
||||
})
|
||||
}
|
143
src/core/region_group.rs
Normal file
143
src/core/region_group.rs
Normal file
|
@ -0,0 +1,143 @@
|
|||
//! The generic [RegionGroup] data structure
|
||||
|
||||
use std::{future::Future, iter};
|
||||
|
||||
use anyhow::Result;
|
||||
use futures_util::future::OptionFuture;
|
||||
|
||||
/// A generic array of 3x3 elements
|
||||
///
|
||||
/// A RegionGroup is used to store information about a 3x3 neighborhood of
|
||||
/// regions.
|
||||
///
|
||||
/// The center element is always populated, while the 8 adjacent elements may be None.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct RegionGroup<T> {
|
||||
/// The element corresponding to the center of the 9x9 neighborhood
|
||||
center: T,
|
||||
/// The remaining elements, stored in row-first order
|
||||
///
|
||||
/// The center element is always None.
|
||||
neighs: [Option<T>; 9],
|
||||
}
|
||||
|
||||
impl<T> RegionGroup<T> {
|
||||
/// Constructs a new RegionGroup from a closure called for each element
|
||||
///
|
||||
/// The X and Z coordinates relative to the center (in the range -1..1)
|
||||
/// are passed to the closure.
|
||||
///
|
||||
/// Panics of the closure returns None for the center element.
|
||||
pub fn new<F>(f: F) -> Self
|
||||
where
|
||||
F: Fn(i8, i8) -> Option<T>,
|
||||
{
|
||||
RegionGroup {
|
||||
center: f(0, 0).expect("Center element of RegionGroup must not be None"),
|
||||
neighs: [
|
||||
f(-1, -1),
|
||||
f(-1, 0),
|
||||
f(-1, 1),
|
||||
f(0, -1),
|
||||
None,
|
||||
f(0, 1),
|
||||
f(1, -1),
|
||||
f(1, 0),
|
||||
f(1, 1),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the center element
|
||||
pub fn center(&self) -> &T {
|
||||
&self.center
|
||||
}
|
||||
|
||||
/// Returns a reference to an element of the RegionGroup, if populated
|
||||
///
|
||||
/// Always returns None for X and Z coordinates outside of the -1..1 range.
|
||||
pub fn get(&self, x: i8, z: i8) -> Option<&T> {
|
||||
if (x, z) == (0, 0) {
|
||||
return Some(&self.center);
|
||||
}
|
||||
if !(-1..=1).contains(&x) || !(-1..=1).contains(&z) {
|
||||
return None;
|
||||
}
|
||||
self.neighs.get((3 * x + z + 4) as usize)?.as_ref()
|
||||
}
|
||||
|
||||
/// Runs a closure on each element to construct a new RegionGroup
|
||||
pub fn map<U, F>(self, mut f: F) -> RegionGroup<U>
|
||||
where
|
||||
F: FnMut(T) -> U,
|
||||
{
|
||||
RegionGroup {
|
||||
center: f(self.center),
|
||||
neighs: self.neighs.map(|entry| entry.map(&mut f)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs a fallible closure on each element to construct a new RegionGroup
|
||||
///
|
||||
/// [Err] return values for the center element are passed up. Outer elements
|
||||
/// become unpopulated when the closure fails.
|
||||
pub fn try_map<U, F>(self, mut f: F) -> Result<RegionGroup<U>>
|
||||
where
|
||||
F: FnMut(T) -> Result<U>,
|
||||
{
|
||||
let RegionGroup { center, neighs } = self;
|
||||
let center = f(center)?;
|
||||
let neighs = neighs.map(|entry| entry.and_then(|value| f(value).ok()));
|
||||
Ok(RegionGroup { center, neighs })
|
||||
}
|
||||
|
||||
/// Runs an asynchronous closure on each element to construct a new RegionGroup
|
||||
#[allow(dead_code)]
|
||||
pub async fn async_map<U, F, Fut>(self, mut f: F) -> RegionGroup<U>
|
||||
where
|
||||
Fut: Future<Output = U>,
|
||||
F: FnMut(T) -> Fut,
|
||||
{
|
||||
let center = f(self.center);
|
||||
let neighs = futures_util::future::join_all(
|
||||
self.neighs
|
||||
.map(|entry| OptionFuture::from(entry.map(&mut f))),
|
||||
);
|
||||
let (center, neighs) = futures_util::join!(center, neighs);
|
||||
RegionGroup {
|
||||
center,
|
||||
neighs: <[Option<_>; 9]>::try_from(neighs).ok().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs a fallible asynchronous closure on each element to construct a new RegionGroup
|
||||
///
|
||||
/// [Err] return values for the center element are passed up. Outer elements
|
||||
/// become unpopulated when the closure fails.
|
||||
pub async fn async_try_map<U, F, Fut>(self, mut f: F) -> Result<RegionGroup<U>>
|
||||
where
|
||||
Fut: Future<Output = Result<U>>,
|
||||
F: FnMut(T) -> Fut,
|
||||
{
|
||||
let center = f(self.center);
|
||||
let neighs = futures_util::future::join_all(
|
||||
self.neighs
|
||||
.map(|entry| OptionFuture::from(entry.map(&mut f))),
|
||||
);
|
||||
let (center, neighs) = futures_util::join!(center, neighs);
|
||||
let center = center?;
|
||||
|
||||
let neighs: Vec<_> = neighs
|
||||
.into_iter()
|
||||
.map(|entry| entry.and_then(Result::ok))
|
||||
.collect();
|
||||
let neighs = <[Option<_>; 9]>::try_from(neighs).ok().unwrap();
|
||||
|
||||
Ok(RegionGroup { center, neighs })
|
||||
}
|
||||
|
||||
/// Returns an [Iterator] over all populated elements
|
||||
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
||||
iter::once(&self.center).chain(self.neighs.iter().filter_map(Option::as_ref))
|
||||
}
|
||||
}
|
453
src/core/region_processor.rs
Normal file
453
src/core/region_processor.rs
Normal file
|
@ -0,0 +1,453 @@
|
|||
//! The [RegionProcessor] and related functions
|
||||
|
||||
use std::{ffi::OsStr, path::PathBuf, sync::mpsc, time::SystemTime};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use enum_map::{Enum, EnumMap};
|
||||
use indexmap::IndexSet;
|
||||
use minedmap_resource::Biome;
|
||||
use rayon::prelude::*;
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
use super::common::*;
|
||||
use crate::{
|
||||
io::{fs, storage},
|
||||
resource,
|
||||
types::*,
|
||||
world::{self, layer},
|
||||
};
|
||||
|
||||
/// Parses a filename in the format r.X.Z.mca into the contained X and Z values
|
||||
fn parse_region_filename(file_name: &OsStr) -> Option<TileCoords> {
|
||||
let parts: Vec<_> = file_name.to_str()?.split('.').collect();
|
||||
let &["r", x, z, "mca"] = parts.as_slice() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(TileCoords {
|
||||
x: x.parse().ok()?,
|
||||
z: z.parse().ok()?,
|
||||
})
|
||||
}
|
||||
|
||||
/// [RegionProcessor::process_region] return values
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Enum)]
|
||||
enum RegionProcessorStatus {
|
||||
/// Region was processed
|
||||
Ok,
|
||||
/// Region was processed, unknown blocks or biomes were encountered
|
||||
OkWithUnknown,
|
||||
/// Region was unchanged and skipped
|
||||
Skipped,
|
||||
/// Reading the region failed, previous processed data is reused
|
||||
ErrorOk,
|
||||
/// Reading the region failed, no previous data available
|
||||
ErrorMissing,
|
||||
}
|
||||
|
||||
/// Data of a region being processed by a [SingleRegionProcessor]
|
||||
#[derive(Debug)]
|
||||
struct SingleRegionData {
|
||||
/// [IndexSet] of biomes used by the processed region
|
||||
biome_list: IndexSet<Biome>,
|
||||
/// Processed region chunk intermediate data
|
||||
chunks: ChunkArray<Option<Box<ProcessedChunk>>>,
|
||||
/// Lightmap intermediate data
|
||||
lightmap: image::GrayAlphaImage,
|
||||
/// Processed entity intermediate data
|
||||
entities: ProcessedEntities,
|
||||
/// True if any unknown block or biome types were encountered during processing
|
||||
has_unknown: bool,
|
||||
}
|
||||
|
||||
impl Default for SingleRegionData {
|
||||
fn default() -> Self {
|
||||
/// Width/height of the region data
|
||||
const N: u32 = (BLOCKS_PER_CHUNK * CHUNKS_PER_REGION) as u32;
|
||||
|
||||
let lightmap = image::GrayAlphaImage::new(N, N);
|
||||
Self {
|
||||
biome_list: Default::default(),
|
||||
chunks: Default::default(),
|
||||
lightmap,
|
||||
entities: Default::default(),
|
||||
has_unknown: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles processing for a single region
|
||||
struct SingleRegionProcessor<'a> {
|
||||
/// Registry of known block types
|
||||
block_types: &'a resource::BlockTypes,
|
||||
/// Registry of known biome types
|
||||
biome_types: &'a resource::BiomeTypes,
|
||||
/// Coordinates of the region this instance is processing
|
||||
coords: TileCoords,
|
||||
/// Input region filename
|
||||
input_path: PathBuf,
|
||||
/// Processed region data output filename
|
||||
output_path: PathBuf,
|
||||
/// Lightmap output filename
|
||||
lightmap_path: PathBuf,
|
||||
/// Processed entity output filename
|
||||
entities_path: PathBuf,
|
||||
/// Timestamp of last modification of input file
|
||||
input_timestamp: SystemTime,
|
||||
/// Timestamp of last modification of processed region output file (if valid)
|
||||
output_timestamp: Option<SystemTime>,
|
||||
/// Timestamp of last modification of lightmap output file (if valid)
|
||||
lightmap_timestamp: Option<SystemTime>,
|
||||
/// Timestamp of last modification of entity list output file (if valid)
|
||||
entities_timestamp: Option<SystemTime>,
|
||||
/// True if processed region output file needs to be updated
|
||||
output_needed: bool,
|
||||
/// True if lightmap output file needs to be updated
|
||||
lightmap_needed: bool,
|
||||
/// True if entity output file needs to be updated
|
||||
entities_needed: bool,
|
||||
/// Format of generated map tiles
|
||||
image_format: image::ImageFormat,
|
||||
}
|
||||
|
||||
impl<'a> SingleRegionProcessor<'a> {
|
||||
/// Initializes a [SingleRegionProcessor]
|
||||
fn new(processor: &'a RegionProcessor<'a>, coords: TileCoords) -> Result<Self> {
|
||||
let input_path = processor.config.region_path(coords);
|
||||
let input_timestamp = fs::modified_timestamp(&input_path)?;
|
||||
|
||||
let output_path = processor.config.processed_path(coords);
|
||||
let output_timestamp = fs::read_timestamp(&output_path, REGION_FILE_META_VERSION);
|
||||
|
||||
let lightmap_path = processor.config.tile_path(TileKind::Lightmap, 0, coords);
|
||||
let lightmap_timestamp = fs::read_timestamp(&lightmap_path, LIGHTMAP_FILE_META_VERSION);
|
||||
|
||||
let entities_path = processor.config.entities_path(0, coords);
|
||||
let entities_timestamp = fs::read_timestamp(&entities_path, ENTITIES_FILE_META_VERSION);
|
||||
|
||||
let output_needed = Some(input_timestamp) > output_timestamp;
|
||||
let lightmap_needed = Some(input_timestamp) > lightmap_timestamp;
|
||||
let entities_needed = Some(input_timestamp) > entities_timestamp;
|
||||
|
||||
Ok(SingleRegionProcessor {
|
||||
block_types: &processor.block_types,
|
||||
biome_types: &processor.biome_types,
|
||||
coords,
|
||||
input_path,
|
||||
output_path,
|
||||
lightmap_path,
|
||||
entities_path,
|
||||
input_timestamp,
|
||||
output_timestamp,
|
||||
lightmap_timestamp,
|
||||
entities_timestamp,
|
||||
output_needed,
|
||||
lightmap_needed,
|
||||
entities_needed,
|
||||
image_format: processor.config.tile_image_format(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Renders a lightmap subtile from chunk block light data
|
||||
fn render_chunk_lightmap(
|
||||
block_light: Box<world::layer::BlockLightArray>,
|
||||
) -> image::GrayAlphaImage {
|
||||
/// Width/height of generated chunk lightmap
|
||||
const N: u32 = BLOCKS_PER_CHUNK as u32;
|
||||
|
||||
image::GrayAlphaImage::from_fn(N, N, |x, z| {
|
||||
let v: f32 = block_light[LayerBlockCoords {
|
||||
x: BlockX::new(x),
|
||||
z: BlockZ::new(z),
|
||||
}]
|
||||
.into();
|
||||
image::LumaA([0, (192.0 * (1.0 - v / 15.0)) as u8])
|
||||
})
|
||||
}
|
||||
|
||||
/// Saves processed region data
|
||||
///
|
||||
/// The timestamp is the time of the last modification of the input region data.
|
||||
fn save_region(&self, processed_region: &ProcessedRegion) -> Result<()> {
|
||||
if !self.output_needed {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
storage::write_file(
|
||||
&self.output_path,
|
||||
processed_region,
|
||||
REGION_FILE_META_VERSION,
|
||||
self.input_timestamp,
|
||||
)
|
||||
}
|
||||
|
||||
/// Saves a lightmap tile
|
||||
///
|
||||
/// The timestamp is the time of the last modification of the input region data.
|
||||
fn save_lightmap(&self, lightmap: &image::GrayAlphaImage) -> Result<()> {
|
||||
if !self.lightmap_needed {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
fs::create_with_timestamp(
|
||||
&self.lightmap_path,
|
||||
LIGHTMAP_FILE_META_VERSION,
|
||||
self.input_timestamp,
|
||||
|file| {
|
||||
lightmap
|
||||
.write_to(file, self.image_format)
|
||||
.context("Failed to save image")
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Saves processed entity data
|
||||
///
|
||||
/// The timestamp is the time of the last modification of the input region data.
|
||||
fn save_entities(&self, entities: &mut ProcessedEntities) -> Result<()> {
|
||||
if !self.entities_needed {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
entities.block_entities.sort_unstable();
|
||||
|
||||
storage::write_file(
|
||||
&self.entities_path,
|
||||
entities,
|
||||
ENTITIES_FILE_META_VERSION,
|
||||
self.input_timestamp,
|
||||
)
|
||||
}
|
||||
|
||||
/// Processes a single chunk
|
||||
fn process_chunk(
|
||||
&self,
|
||||
data: &mut SingleRegionData,
|
||||
chunk_coords: ChunkCoords,
|
||||
chunk_data: world::de::Chunk,
|
||||
) -> Result<()> {
|
||||
let (chunk, has_unknown) =
|
||||
world::chunk::Chunk::new(&chunk_data, self.block_types, self.biome_types)
|
||||
.with_context(|| format!("Failed to decode chunk {:?}", chunk_coords))?;
|
||||
data.has_unknown |= has_unknown;
|
||||
|
||||
if self.output_needed || self.lightmap_needed {
|
||||
if let Some(layer::LayerData {
|
||||
blocks,
|
||||
biomes,
|
||||
block_light,
|
||||
depths,
|
||||
}) = world::layer::top_layer(&mut data.biome_list, &chunk)
|
||||
.with_context(|| format!("Failed to process chunk {:?}", chunk_coords))?
|
||||
{
|
||||
if self.output_needed {
|
||||
data.chunks[chunk_coords] = Some(Box::new(ProcessedChunk {
|
||||
blocks,
|
||||
biomes,
|
||||
depths,
|
||||
}));
|
||||
}
|
||||
|
||||
if self.lightmap_needed {
|
||||
let chunk_lightmap = Self::render_chunk_lightmap(block_light);
|
||||
overlay_chunk(&mut data.lightmap, &chunk_lightmap, chunk_coords);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.entities_needed {
|
||||
let mut block_entities = chunk.block_entities().with_context(|| {
|
||||
format!(
|
||||
"Failed to process block entities for chunk {:?}",
|
||||
chunk_coords,
|
||||
)
|
||||
})?;
|
||||
data.entities.block_entities.append(&mut block_entities);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Processes the chunks of the region
|
||||
fn process_chunks(&self, data: &mut SingleRegionData) -> Result<()> {
|
||||
crate::nbt::region::from_file(&self.input_path)?.foreach_chunk(
|
||||
|chunk_coords, chunk_data| self.process_chunk(data, chunk_coords, chunk_data),
|
||||
)
|
||||
}
|
||||
|
||||
/// Processes the region
|
||||
fn run(&self) -> Result<RegionProcessorStatus> {
|
||||
if !self.output_needed && !self.lightmap_needed && !self.entities_needed {
|
||||
debug!(
|
||||
"Skipping unchanged region r.{}.{}.mca",
|
||||
self.coords.x, self.coords.z
|
||||
);
|
||||
return Ok(RegionProcessorStatus::Skipped);
|
||||
}
|
||||
|
||||
debug!(
|
||||
"Processing region r.{}.{}.mca",
|
||||
self.coords.x, self.coords.z
|
||||
);
|
||||
|
||||
let mut data = SingleRegionData::default();
|
||||
|
||||
if let Err(err) = self.process_chunks(&mut data) {
|
||||
if self.output_timestamp.is_some()
|
||||
&& self.lightmap_timestamp.is_some()
|
||||
&& self.entities_timestamp.is_some()
|
||||
{
|
||||
warn!(
|
||||
"Failed to process region {:?}, using old data: {:?}",
|
||||
self.coords, err
|
||||
);
|
||||
return Ok(RegionProcessorStatus::ErrorOk);
|
||||
} else {
|
||||
warn!(
|
||||
"Failed to process region {:?}, no old data available: {:?}",
|
||||
self.coords, err
|
||||
);
|
||||
return Ok(RegionProcessorStatus::ErrorMissing);
|
||||
}
|
||||
}
|
||||
|
||||
let processed_region = ProcessedRegion {
|
||||
biome_list: data.biome_list.into_iter().collect(),
|
||||
chunks: data.chunks,
|
||||
};
|
||||
|
||||
self.save_region(&processed_region)?;
|
||||
self.save_lightmap(&data.lightmap)?;
|
||||
self.save_entities(&mut data.entities)?;
|
||||
|
||||
Ok(if data.has_unknown {
|
||||
RegionProcessorStatus::OkWithUnknown
|
||||
} else {
|
||||
RegionProcessorStatus::Ok
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Type with methods for processing the regions of a Minecraft save directory
|
||||
///
|
||||
/// The RegionProcessor builds lightmap tiles as well as processed region data
|
||||
/// consumed by subsequent generation steps.
|
||||
pub struct RegionProcessor<'a> {
|
||||
/// Registry of known block types
|
||||
block_types: resource::BlockTypes,
|
||||
/// Registry of known biome types
|
||||
biome_types: resource::BiomeTypes,
|
||||
/// Common MinedMap configuration from command line
|
||||
config: &'a Config,
|
||||
}
|
||||
|
||||
impl<'a> RegionProcessor<'a> {
|
||||
/// Constructs a new RegionProcessor
|
||||
pub fn new(config: &'a Config) -> Self {
|
||||
RegionProcessor {
|
||||
block_types: resource::BlockTypes::default(),
|
||||
biome_types: resource::BiomeTypes::default(),
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a list of all regions of the input Minecraft save data
|
||||
fn collect_regions(&self) -> Result<Vec<TileCoords>> {
|
||||
Ok(self
|
||||
.config
|
||||
.region_dir
|
||||
.read_dir()
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to read directory {}",
|
||||
self.config.region_dir.display()
|
||||
)
|
||||
})?
|
||||
.filter_map(|entry| entry.ok())
|
||||
.filter(|entry| {
|
||||
(|| {
|
||||
// We are only interested in regular files
|
||||
let file_type = entry.file_type().ok()?;
|
||||
if !file_type.is_file() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let metadata = entry.metadata().ok()?;
|
||||
if metadata.len() == 0 {
|
||||
return None;
|
||||
}
|
||||
Some(())
|
||||
})()
|
||||
.is_some()
|
||||
})
|
||||
.filter_map(|entry| parse_region_filename(&entry.file_name()))
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Processes a single region file
|
||||
fn process_region(&self, coords: TileCoords) -> Result<RegionProcessorStatus> {
|
||||
SingleRegionProcessor::new(self, coords)?.run()
|
||||
}
|
||||
|
||||
/// Iterates over all region files of a Minecraft save directory
|
||||
///
|
||||
/// Returns a list of the coordinates of all processed regions
|
||||
pub fn run(self) -> Result<Vec<TileCoords>> {
|
||||
use RegionProcessorStatus as Status;
|
||||
|
||||
fs::create_dir_all(&self.config.processed_dir)?;
|
||||
fs::create_dir_all(&self.config.tile_dir(TileKind::Lightmap, 0))?;
|
||||
fs::create_dir_all(&self.config.entities_dir(0))?;
|
||||
|
||||
info!("Processing region files...");
|
||||
|
||||
let (region_send, region_recv) = mpsc::channel();
|
||||
let (status_send, status_recv) = mpsc::channel();
|
||||
|
||||
self.collect_regions()?.par_iter().try_for_each(|&coords| {
|
||||
let ret = self
|
||||
.process_region(coords)
|
||||
.with_context(|| format!("Failed to process region {:?}", coords))?;
|
||||
|
||||
if ret != Status::ErrorMissing {
|
||||
region_send.send(coords).unwrap();
|
||||
}
|
||||
|
||||
status_send.send(ret).unwrap();
|
||||
|
||||
anyhow::Ok(())
|
||||
})?;
|
||||
|
||||
drop(region_send);
|
||||
let mut regions: Vec<_> = region_recv.into_iter().collect();
|
||||
|
||||
drop(status_send);
|
||||
|
||||
let mut status = EnumMap::<_, usize>::default();
|
||||
for ret in status_recv {
|
||||
status[ret] += 1;
|
||||
}
|
||||
|
||||
info!(
|
||||
"Processed region files ({} processed, {} unchanged, {} errors)",
|
||||
status[Status::Ok] + status[Status::OkWithUnknown],
|
||||
status[Status::Skipped],
|
||||
status[Status::ErrorOk] + status[Status::ErrorMissing],
|
||||
);
|
||||
|
||||
if status[Status::OkWithUnknown] > 0 {
|
||||
warn!("Unknown block or biome types found during processing");
|
||||
eprint!(concat!(
|
||||
"\n",
|
||||
" If you're encountering this issue with an unmodified Minecraft version supported by MinedMap,\n",
|
||||
" please file a bug report including the output with the --verbose flag.\n",
|
||||
"\n",
|
||||
));
|
||||
}
|
||||
|
||||
// Sort regions in a zig-zag pattern to optimize cache usage
|
||||
regions.sort_unstable_by_key(|&TileCoords { x, z }| (x, if x % 2 == 0 { z } else { -z }));
|
||||
|
||||
Ok(regions)
|
||||
}
|
||||
}
|
107
src/core/tile_collector.rs
Normal file
107
src/core/tile_collector.rs
Normal file
|
@ -0,0 +1,107 @@
|
|||
//! A trait for recursively processing tiles
|
||||
//!
|
||||
//! Used for mipmap generation and collecting entity data
|
||||
|
||||
use std::sync::mpsc;
|
||||
|
||||
use anyhow::Result;
|
||||
use rayon::prelude::*;
|
||||
|
||||
use super::common::*;
|
||||
|
||||
/// Helper to determine if no further mipmap levels are needed
|
||||
///
|
||||
/// If all tile coordinates are -1 or 0, further mipmap levels will not
|
||||
/// decrease the number of tiles and mipmap generated is considered finished.
|
||||
fn done(tiles: &TileCoordMap) -> bool {
|
||||
tiles
|
||||
.0
|
||||
.iter()
|
||||
.all(|(z, xs)| (-1..=0).contains(z) && xs.iter().all(|x| (-1..=0).contains(x)))
|
||||
}
|
||||
|
||||
/// Derives the map of populated tile coordinates for the next mipmap level
|
||||
fn map_coords(tiles: &TileCoordMap) -> TileCoordMap {
|
||||
let mut ret = TileCoordMap::default();
|
||||
|
||||
for (&z, xs) in &tiles.0 {
|
||||
for &x in xs {
|
||||
let xt = x >> 1;
|
||||
let zt = z >> 1;
|
||||
|
||||
ret.0.entry(zt).or_default().insert(xt);
|
||||
}
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
/// Trait to implement for collecting tiles recursively
|
||||
pub trait TileCollector: Sync {
|
||||
/// Return value of [TileCollector::collect_one]
|
||||
type CollectOutput: Send;
|
||||
|
||||
/// List of level 0 tiles
|
||||
fn tiles(&self) -> &[TileCoords];
|
||||
|
||||
/// Called at the beginning of each level of processing
|
||||
fn prepare(&self, level: usize) -> Result<()>;
|
||||
|
||||
/// Called at the end of each level of processing
|
||||
fn finish(
|
||||
&self,
|
||||
level: usize,
|
||||
outputs: impl Iterator<Item = Self::CollectOutput>,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Called for each tile coordinate of the level that is currently being generated
|
||||
fn collect_one(
|
||||
&self,
|
||||
level: usize,
|
||||
coords: TileCoords,
|
||||
prev: &TileCoordMap,
|
||||
) -> Result<Self::CollectOutput>;
|
||||
|
||||
/// Collects tiles recursively
|
||||
fn collect_tiles(&self) -> Result<Vec<TileCoordMap>> {
|
||||
let mut tile_stack = {
|
||||
let mut tile_map = TileCoordMap::default();
|
||||
|
||||
for &TileCoords { x, z } in self.tiles() {
|
||||
tile_map.0.entry(z).or_default().insert(x);
|
||||
}
|
||||
|
||||
vec![tile_map]
|
||||
};
|
||||
|
||||
loop {
|
||||
let level = tile_stack.len();
|
||||
let prev = &tile_stack[level - 1];
|
||||
if done(prev) {
|
||||
break;
|
||||
}
|
||||
|
||||
self.prepare(level)?;
|
||||
|
||||
let next = map_coords(prev);
|
||||
|
||||
let (send, recv) = mpsc::channel();
|
||||
|
||||
next.0
|
||||
.par_iter()
|
||||
.flat_map(|(&z, xs)| xs.par_iter().map(move |&x| TileCoords { x, z }))
|
||||
.try_for_each(|coords| {
|
||||
let output = self.collect_one(level, coords, prev)?;
|
||||
send.send(output).unwrap();
|
||||
anyhow::Ok(())
|
||||
})?;
|
||||
|
||||
drop(send);
|
||||
self.finish(level, recv.into_iter())?;
|
||||
|
||||
tile_stack.push(next);
|
||||
}
|
||||
|
||||
Ok(tile_stack)
|
||||
}
|
||||
}
|
97
src/core/tile_merger.rs
Normal file
97
src/core/tile_merger.rs
Normal file
|
@ -0,0 +1,97 @@
|
|||
//! Mipmap-style merging of tiles
|
||||
|
||||
use std::{
|
||||
fs::File,
|
||||
io::BufWriter,
|
||||
path::{Path, PathBuf},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use tracing::warn;
|
||||
|
||||
use super::common::*;
|
||||
use crate::io::fs;
|
||||
|
||||
/// [TileMerger::merge_tiles] return
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Stat {
|
||||
/// None of the input files were found
|
||||
NotFound,
|
||||
/// The output file is up-to-date
|
||||
Skipped,
|
||||
/// The output file is regenerated
|
||||
Regenerate,
|
||||
}
|
||||
|
||||
/// A source file for the [TileMerger]
|
||||
///
|
||||
/// The tuple elements are X and Z coordinate offsets in the range [0, 1],
|
||||
/// the file path and the time of last change of the input.
|
||||
pub type Source = ((i32, i32), PathBuf, SystemTime);
|
||||
|
||||
/// Reusable trait for mipmap-style tile merging with change tracking
|
||||
pub trait TileMerger {
|
||||
/// [fs::FileMetaVersion] of input and output files
|
||||
///
|
||||
/// The version in the file metadata on disk must match the returned
|
||||
/// version for the a to be considered up-to-date.
|
||||
fn file_meta_version(&self) -> fs::FileMetaVersion;
|
||||
|
||||
/// Returns the paths of input and output files
|
||||
fn tile_path(&self, level: usize, coords: TileCoords) -> PathBuf;
|
||||
|
||||
/// Can be used to log the processing status
|
||||
fn log(&self, _output_path: &Path, _stat: Stat) {}
|
||||
|
||||
/// Handles the actual merging of source files
|
||||
fn write_tile(&self, file: &mut BufWriter<File>, sources: &[Source]) -> Result<()>;
|
||||
|
||||
/// Generates a tile at given coordinates and mipmap level
|
||||
fn merge_tiles(&self, level: usize, coords: TileCoords, prev: &TileCoordMap) -> Result<Stat> {
|
||||
let version = self.file_meta_version();
|
||||
let output_path = self.tile_path(level, coords);
|
||||
let output_timestamp = fs::read_timestamp(&output_path, version);
|
||||
|
||||
let sources: Vec<_> = [(0, 0), (0, 1), (1, 0), (1, 1)]
|
||||
.into_iter()
|
||||
.filter_map(|(dx, dz)| {
|
||||
let source_coords = TileCoords {
|
||||
x: 2 * coords.x + dx,
|
||||
z: 2 * coords.z + dz,
|
||||
};
|
||||
if !prev.contains(source_coords) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let source_path = self.tile_path(level - 1, source_coords);
|
||||
let timestamp = match fs::modified_timestamp(&source_path) {
|
||||
Ok(timestamp) => timestamp,
|
||||
Err(err) => {
|
||||
warn!("{:?}", err);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
Some(((dx, dz), source_path, timestamp))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let Some(input_timestamp) = sources.iter().map(|(_, _, ts)| *ts).max() else {
|
||||
self.log(&output_path, Stat::NotFound);
|
||||
return Ok(Stat::NotFound);
|
||||
};
|
||||
|
||||
if Some(input_timestamp) <= output_timestamp {
|
||||
self.log(&output_path, Stat::Skipped);
|
||||
return Ok(Stat::Skipped);
|
||||
}
|
||||
|
||||
self.log(&output_path, Stat::Regenerate);
|
||||
|
||||
fs::create_with_timestamp(&output_path, version, input_timestamp, |file| {
|
||||
self.write_tile(file, &sources)
|
||||
})?;
|
||||
|
||||
Ok(Stat::Regenerate)
|
||||
}
|
||||
}
|
241
src/core/tile_mipmapper.rs
Normal file
241
src/core/tile_mipmapper.rs
Normal file
|
@ -0,0 +1,241 @@
|
|||
//! The [TileMipmapper]
|
||||
|
||||
use std::{marker::PhantomData, ops::Add};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
use super::{
|
||||
common::*,
|
||||
tile_collector::TileCollector,
|
||||
tile_merger::{self, TileMerger},
|
||||
};
|
||||
use crate::{io::fs, types::*};
|
||||
|
||||
/// Counters for the number of processed and total tiles
|
||||
///
|
||||
/// Used as return of [TileMipmapper::collect_one]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct MipmapStat {
|
||||
/// Total number of tiles
|
||||
total: usize,
|
||||
/// Processed number of tiles
|
||||
processed: usize,
|
||||
}
|
||||
|
||||
impl From<tile_merger::Stat> for MipmapStat {
|
||||
fn from(value: tile_merger::Stat) -> Self {
|
||||
match value {
|
||||
tile_merger::Stat::NotFound => MipmapStat {
|
||||
total: 0,
|
||||
processed: 0,
|
||||
},
|
||||
tile_merger::Stat::Skipped => MipmapStat {
|
||||
total: 1,
|
||||
processed: 0,
|
||||
},
|
||||
tile_merger::Stat::Regenerate => MipmapStat {
|
||||
total: 1,
|
||||
processed: 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for MipmapStat {
|
||||
type Output = MipmapStat;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
MipmapStat {
|
||||
total: self.total + rhs.total,
|
||||
processed: self.processed + rhs.processed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// [TileMerger] for map tile images
|
||||
struct MapMerger<'a, P> {
|
||||
/// Common MinedMap configuration from command line
|
||||
config: &'a Config,
|
||||
/// Tile kind (map or lightmap)
|
||||
kind: TileKind,
|
||||
/// Pixel format type
|
||||
_pixel: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<'a, P> MapMerger<'a, P> {
|
||||
/// Creates a new [MapMerger]
|
||||
fn new(config: &'a Config, kind: TileKind) -> Self {
|
||||
MapMerger {
|
||||
config,
|
||||
kind,
|
||||
_pixel: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: image::PixelWithColorType> TileMerger for MapMerger<'_, P>
|
||||
where
|
||||
[P::Subpixel]: image::EncodableLayout,
|
||||
image::ImageBuffer<P, Vec<P::Subpixel>>: Into<image::DynamicImage>,
|
||||
{
|
||||
fn file_meta_version(&self) -> fs::FileMetaVersion {
|
||||
MIPMAP_FILE_META_VERSION
|
||||
}
|
||||
|
||||
fn tile_path(&self, level: usize, coords: TileCoords) -> std::path::PathBuf {
|
||||
self.config.tile_path(self.kind, level, coords)
|
||||
}
|
||||
|
||||
fn log(&self, output_path: &std::path::Path, stat: super::tile_merger::Stat) {
|
||||
match stat {
|
||||
super::tile_merger::Stat::NotFound => {}
|
||||
super::tile_merger::Stat::Skipped => {
|
||||
debug!(
|
||||
"Skipping unchanged mipmap tile {}",
|
||||
output_path
|
||||
.strip_prefix(&self.config.output_dir)
|
||||
.expect("tile path must be in output directory")
|
||||
.display(),
|
||||
);
|
||||
}
|
||||
super::tile_merger::Stat::Regenerate => {
|
||||
debug!(
|
||||
"Rendering mipmap tile {}",
|
||||
output_path
|
||||
.strip_prefix(&self.config.output_dir)
|
||||
.expect("tile path must be in output directory")
|
||||
.display(),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn write_tile(
|
||||
&self,
|
||||
file: &mut std::io::BufWriter<std::fs::File>,
|
||||
sources: &[super::tile_merger::Source],
|
||||
) -> Result<()> {
|
||||
/// Tile width/height
|
||||
const N: u32 = (BLOCKS_PER_CHUNK * CHUNKS_PER_REGION) as u32;
|
||||
|
||||
let mut image: image::DynamicImage =
|
||||
image::ImageBuffer::<P, Vec<P::Subpixel>>::new(N, N).into();
|
||||
|
||||
for ((dx, dz), source_path, _) in sources {
|
||||
let source = match image::open(source_path) {
|
||||
Ok(source) => source,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"Failed to read source image {}: {:?}",
|
||||
source_path.display(),
|
||||
err,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let resized = source.resize(N / 2, N / 2, image::imageops::FilterType::Triangle);
|
||||
image::imageops::overlay(
|
||||
&mut image,
|
||||
&resized,
|
||||
*dx as i64 * (N / 2) as i64,
|
||||
*dz as i64 * (N / 2) as i64,
|
||||
);
|
||||
}
|
||||
|
||||
image
|
||||
.write_to(file, self.config.tile_image_format())
|
||||
.context("Failed to save image")
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates mipmap tiles from full-resolution tile images
|
||||
pub struct TileMipmapper<'a> {
|
||||
/// Common MinedMap configuration from command line
|
||||
config: &'a Config,
|
||||
/// List of populated tiles for base mipmap level (level 0)
|
||||
regions: &'a [TileCoords],
|
||||
}
|
||||
|
||||
impl TileCollector for TileMipmapper<'_> {
|
||||
type CollectOutput = MipmapStat;
|
||||
|
||||
fn tiles(&self) -> &[TileCoords] {
|
||||
self.regions
|
||||
}
|
||||
|
||||
fn prepare(&self, level: usize) -> Result<()> {
|
||||
info!("Generating level {} mipmaps...", level);
|
||||
|
||||
fs::create_dir_all(&self.config.tile_dir(TileKind::Map, level))?;
|
||||
fs::create_dir_all(&self.config.tile_dir(TileKind::Lightmap, level))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn finish(
|
||||
&self,
|
||||
level: usize,
|
||||
outputs: impl Iterator<Item = Self::CollectOutput>,
|
||||
) -> Result<()> {
|
||||
let stat = outputs.fold(
|
||||
MipmapStat {
|
||||
total: 0,
|
||||
processed: 0,
|
||||
},
|
||||
MipmapStat::add,
|
||||
);
|
||||
info!(
|
||||
"Generated level {} mipmaps ({} processed, {} unchanged)",
|
||||
level,
|
||||
stat.processed,
|
||||
stat.total - stat.processed,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn collect_one(
|
||||
&self,
|
||||
level: usize,
|
||||
coords: TileCoords,
|
||||
prev: &TileCoordMap,
|
||||
) -> Result<Self::CollectOutput> {
|
||||
let map_stat = self.render_mipmap::<image::Rgba<u8>>(TileKind::Map, level, coords, prev)?;
|
||||
let lightmap_stat =
|
||||
self.render_mipmap::<image::LumaA<u8>>(TileKind::Lightmap, level, coords, prev)?;
|
||||
Ok(map_stat + lightmap_stat)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TileMipmapper<'a> {
|
||||
/// Constructs a new TileMipmapper
|
||||
pub fn new(config: &'a Config, regions: &'a [TileCoords]) -> Self {
|
||||
TileMipmapper { config, regions }
|
||||
}
|
||||
|
||||
/// Renders and saves a single mipmap tile image
|
||||
///
|
||||
/// Each mipmap tile is rendered by taking 2x2 tiles from the
|
||||
/// previous level and scaling them down by 50%.
|
||||
fn render_mipmap<P: image::PixelWithColorType>(
|
||||
&self,
|
||||
kind: TileKind,
|
||||
level: usize,
|
||||
coords: TileCoords,
|
||||
prev: &TileCoordMap,
|
||||
) -> Result<MipmapStat>
|
||||
where
|
||||
[P::Subpixel]: image::EncodableLayout,
|
||||
image::ImageBuffer<P, Vec<P::Subpixel>>: Into<image::DynamicImage>,
|
||||
{
|
||||
let merger = MapMerger::<P>::new(self.config, kind);
|
||||
let ret = merger.merge_tiles(level, coords, prev)?;
|
||||
Ok(ret.into())
|
||||
}
|
||||
|
||||
/// Runs the mipmap generation
|
||||
pub fn run(self) -> Result<Vec<TileCoordMap>> {
|
||||
self.collect_tiles()
|
||||
}
|
||||
}
|
341
src/core/tile_renderer.rs
Normal file
341
src/core/tile_renderer.rs
Normal file
|
@ -0,0 +1,341 @@
|
|||
//! The [TileRenderer] and related types and functions
|
||||
|
||||
use std::{
|
||||
num::NonZeroUsize,
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use lru::LruCache;
|
||||
use rayon::prelude::*;
|
||||
use tokio::sync::OnceCell;
|
||||
use tracing::{debug, info};
|
||||
|
||||
use super::{common::*, region_group::RegionGroup};
|
||||
use crate::{
|
||||
io::{fs, storage},
|
||||
resource::{Colorf, block_color, needs_biome},
|
||||
types::*,
|
||||
util::coord_offset,
|
||||
};
|
||||
|
||||
/// Type for referencing loaded [ProcessedRegion] data
|
||||
type RegionRef = Arc<ProcessedRegion>;
|
||||
|
||||
/// Returns the index of the biome at a block coordinate
|
||||
///
|
||||
/// The passed chunk and block coordinates relative to the center of the
|
||||
/// region group is offset by *dx* and *dz*.
|
||||
///
|
||||
/// The returned tuple contains the relative region coordinates the offset coordinate
|
||||
/// ends up in (in the range -1..1) and the index in that region's biome list.
|
||||
fn biome_at(
|
||||
region_group: &RegionGroup<RegionRef>,
|
||||
chunk: ChunkCoords,
|
||||
block: LayerBlockCoords,
|
||||
dx: i32,
|
||||
dz: i32,
|
||||
) -> Option<(i8, i8, u16)> {
|
||||
let (region_x, chunk_x, block_x) = coord_offset(chunk.x, block.x, dx);
|
||||
let (region_z, chunk_z, block_z) = coord_offset(chunk.z, block.z, dz);
|
||||
let chunk = ChunkCoords {
|
||||
x: chunk_x,
|
||||
z: chunk_z,
|
||||
};
|
||||
let block = LayerBlockCoords {
|
||||
x: block_x,
|
||||
z: block_z,
|
||||
};
|
||||
let region = region_group.get(region_x, region_z)?;
|
||||
Some((
|
||||
region_x,
|
||||
region_z,
|
||||
region.chunks[chunk].as_ref()?.biomes[block]?.get() - 1,
|
||||
))
|
||||
}
|
||||
|
||||
/// The TileRenderer generates map tiles from processed region data
|
||||
pub struct TileRenderer<'a> {
|
||||
/// Common MinedMap configuration from command line
|
||||
config: &'a Config,
|
||||
/// Runtime for asynchronous region loading
|
||||
rt: &'a tokio::runtime::Runtime,
|
||||
/// List of populated regions to render tiles for
|
||||
regions: &'a [TileCoords],
|
||||
/// Set of populated regions for fast existence checking
|
||||
region_set: rustc_hash::FxHashSet<TileCoords>,
|
||||
/// Cache of previously loaded regions
|
||||
region_cache: Mutex<LruCache<PathBuf, Arc<OnceCell<RegionRef>>>>,
|
||||
}
|
||||
|
||||
impl<'a> TileRenderer<'a> {
|
||||
/// Constructs a new TileRenderer
|
||||
pub fn new(
|
||||
config: &'a Config,
|
||||
rt: &'a tokio::runtime::Runtime,
|
||||
regions: &'a [TileCoords],
|
||||
) -> Self {
|
||||
let region_cache = Mutex::new(LruCache::new(
|
||||
NonZeroUsize::new(6 + 6 * config.num_threads).unwrap(),
|
||||
));
|
||||
let region_set = regions.iter().copied().collect();
|
||||
TileRenderer {
|
||||
config,
|
||||
rt,
|
||||
regions,
|
||||
region_set,
|
||||
region_cache,
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads [ProcessedRegion] for a region or returns previously loaded data from the region cache
|
||||
async fn load_region(&self, processed_path: PathBuf) -> Result<RegionRef> {
|
||||
let region_loader = {
|
||||
let mut region_cache = self.region_cache.lock().unwrap();
|
||||
if let Some(region_loader) = region_cache.get(&processed_path) {
|
||||
Arc::clone(region_loader)
|
||||
} else {
|
||||
let region_loader = Default::default();
|
||||
region_cache.put(processed_path.clone(), Arc::clone(®ion_loader));
|
||||
region_loader
|
||||
}
|
||||
};
|
||||
|
||||
region_loader
|
||||
.get_or_try_init(|| async {
|
||||
storage::read_file(&processed_path).context("Failed to load processed region data")
|
||||
})
|
||||
.await
|
||||
.cloned()
|
||||
}
|
||||
|
||||
/// Loads a 3x3 neighborhood of processed region data
|
||||
async fn load_region_group(
|
||||
&self,
|
||||
processed_paths: RegionGroup<PathBuf>,
|
||||
) -> Result<RegionGroup<RegionRef>> {
|
||||
processed_paths
|
||||
.async_try_map(move |path| self.load_region(path))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Computes the color of a tile pixel
|
||||
fn block_color_at(
|
||||
region_group: &RegionGroup<RegionRef>,
|
||||
chunk: &ProcessedChunk,
|
||||
chunk_coords: ChunkCoords,
|
||||
block_coords: LayerBlockCoords,
|
||||
) -> Option<Colorf> {
|
||||
/// Helper for keys in the weight table
|
||||
///
|
||||
/// Hashing the value as a single u32 is more efficient than hashing
|
||||
/// the tuple elements separately.
|
||||
fn biome_key((dx, dz, index): (i8, i8, u16)) -> u32 {
|
||||
(dx as u8 as u32) | ((dz as u8 as u32) << 8) | ((index as u32) << 16)
|
||||
}
|
||||
|
||||
/// One quadrant of the kernel used to smooth biome edges
|
||||
///
|
||||
/// The kernel is mirrored in X und Z direction to build the full 5x5
|
||||
/// smoothing kernel.
|
||||
const SMOOTH: [[f32; 3]; 3] = [[41.0, 26.0, 7.0], [26.0, 16.0, 4.0], [7.0, 4.0, 1.0]];
|
||||
/// Maximum X coordinate offset to take into account for biome smoothing
|
||||
const X: isize = SMOOTH[0].len() as isize - 1;
|
||||
/// Maximum Z coordinate offset to take into account for biome smoothing
|
||||
const Z: isize = SMOOTH.len() as isize - 1;
|
||||
|
||||
let block = chunk.blocks[block_coords]?;
|
||||
let depth = chunk.depths[block_coords]?;
|
||||
|
||||
if !needs_biome(block) {
|
||||
return Some(block_color(block, None, depth.0 as f32));
|
||||
}
|
||||
|
||||
let mut weights = rustc_hash::FxHashMap::<u32, ((i8, i8, u16), f32)>::default();
|
||||
for dz in -Z..=Z {
|
||||
for dx in -X..=X {
|
||||
let w = SMOOTH[dz.unsigned_abs()][dx.unsigned_abs()];
|
||||
if w == 0.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(biome) = biome_at(
|
||||
region_group,
|
||||
chunk_coords,
|
||||
block_coords,
|
||||
dx as i32,
|
||||
dz as i32,
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let value = weights.entry(biome_key(biome)).or_default();
|
||||
value.0 = biome;
|
||||
value.1 += w;
|
||||
}
|
||||
}
|
||||
|
||||
if weights.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut color = Colorf::ZERO;
|
||||
let mut total = 0.0;
|
||||
|
||||
for ((region_x, region_z, index), w) in weights.into_values() {
|
||||
let region = region_group.get(region_x, region_z)?;
|
||||
let biome = region.biome_list.get(usize::from(index))?;
|
||||
|
||||
total += w;
|
||||
color += w * block_color(block, Some(biome), depth.0 as f32);
|
||||
}
|
||||
|
||||
Some(color / total)
|
||||
}
|
||||
|
||||
/// Renders a chunk subtile into a region tile image
|
||||
fn render_chunk(
|
||||
image: &mut image::RgbaImage,
|
||||
region_group: &RegionGroup<RegionRef>,
|
||||
chunk: &ProcessedChunk,
|
||||
chunk_coords: ChunkCoords,
|
||||
) {
|
||||
/// Width/height of a chunk subtile
|
||||
const N: u32 = BLOCKS_PER_CHUNK as u32;
|
||||
|
||||
let chunk_image = image::RgbaImage::from_fn(N, N, |x, z| {
|
||||
let block_coords = LayerBlockCoords {
|
||||
x: BlockX::new(x),
|
||||
z: BlockZ::new(z),
|
||||
};
|
||||
let color = Self::block_color_at(region_group, chunk, chunk_coords, block_coords);
|
||||
image::Rgba(
|
||||
color
|
||||
.map(|c| [c[0] as u8, c[1] as u8, c[2] as u8, 255])
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
});
|
||||
overlay_chunk(image, &chunk_image, chunk_coords);
|
||||
}
|
||||
|
||||
/// Renders a region tile image
|
||||
fn render_region(image: &mut image::RgbaImage, region_group: &RegionGroup<RegionRef>) {
|
||||
for (coords, chunk) in region_group.center().chunks.iter() {
|
||||
let Some(chunk) = chunk else {
|
||||
continue;
|
||||
};
|
||||
|
||||
Self::render_chunk(image, region_group, chunk, coords);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the filename of the processed data for a region and the time of its last modification
|
||||
fn processed_source(&self, coords: TileCoords) -> Result<(PathBuf, SystemTime)> {
|
||||
let path = self.config.processed_path(coords);
|
||||
let timestamp = fs::modified_timestamp(&path)?;
|
||||
Ok((path, timestamp))
|
||||
}
|
||||
|
||||
/// Returns the filenames of the processed data for a 3x3 neighborhood of a region
|
||||
/// and the time of last modification for any of them
|
||||
fn processed_sources(&self, coords: TileCoords) -> Result<(RegionGroup<PathBuf>, SystemTime)> {
|
||||
let sources = RegionGroup::new(|x, z| {
|
||||
Some(TileCoords {
|
||||
x: coords.x + (x as i32),
|
||||
z: coords.z + (z as i32),
|
||||
})
|
||||
.filter(|entry| self.region_set.contains(entry))
|
||||
})
|
||||
.try_map(|entry| self.processed_source(entry))
|
||||
.with_context(|| format!("Region {:?} from previous step must exist", coords))?;
|
||||
|
||||
let max_timestamp = *sources
|
||||
.iter()
|
||||
.map(|(_, timestamp)| timestamp)
|
||||
.max()
|
||||
.expect("at least one timestamp must exist");
|
||||
|
||||
let paths = sources.map(|(path, _)| path);
|
||||
Ok((paths, max_timestamp))
|
||||
}
|
||||
|
||||
/// Renders and saves a region tile image
|
||||
fn render_tile(&self, coords: TileCoords) -> Result<bool> {
|
||||
/// Width/height of a tile image
|
||||
const N: u32 = (BLOCKS_PER_CHUNK * CHUNKS_PER_REGION) as u32;
|
||||
|
||||
let (processed_paths, processed_timestamp) = self.processed_sources(coords)?;
|
||||
|
||||
let output_path = self.config.tile_path(TileKind::Map, 0, coords);
|
||||
let output_timestamp = fs::read_timestamp(&output_path, MAP_FILE_META_VERSION);
|
||||
|
||||
if Some(processed_timestamp) <= output_timestamp {
|
||||
debug!(
|
||||
"Skipping unchanged tile {}",
|
||||
output_path
|
||||
.strip_prefix(&self.config.output_dir)
|
||||
.expect("tile path must be in output directory")
|
||||
.display(),
|
||||
);
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
debug!(
|
||||
"Rendering tile {}",
|
||||
output_path
|
||||
.strip_prefix(&self.config.output_dir)
|
||||
.expect("tile path must be in output directory")
|
||||
.display(),
|
||||
);
|
||||
|
||||
let region_group = self
|
||||
.rt
|
||||
.block_on(self.load_region_group(processed_paths))
|
||||
.with_context(|| format!("Region {:?} from previous step must be loadable", coords))?;
|
||||
let mut image = image::RgbaImage::new(N, N);
|
||||
Self::render_region(&mut image, ®ion_group);
|
||||
|
||||
fs::create_with_timestamp(
|
||||
&output_path,
|
||||
MAP_FILE_META_VERSION,
|
||||
processed_timestamp,
|
||||
|file| {
|
||||
image
|
||||
.write_to(file, self.config.tile_image_format())
|
||||
.context("Failed to save image")
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Runs the tile generation
|
||||
pub fn run(self) -> Result<()> {
|
||||
fs::create_dir_all(&self.config.tile_dir(TileKind::Map, 0))?;
|
||||
|
||||
info!("Rendering map tiles...");
|
||||
|
||||
// Use par_bridge to process items in order (for better use of region cache)
|
||||
let processed = self
|
||||
.regions
|
||||
.iter()
|
||||
.par_bridge()
|
||||
.map(|&coords| {
|
||||
anyhow::Ok(usize::from(
|
||||
self.render_tile(coords)
|
||||
.with_context(|| format!("Failed to render tile {:?}", coords))?,
|
||||
))
|
||||
})
|
||||
.try_reduce(|| 0, |a, b| Ok(a + b))?;
|
||||
|
||||
info!(
|
||||
"Rendered map tiles ({} processed, {} unchanged)",
|
||||
processed,
|
||||
self.regions.len() - processed,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
186
src/io/fs.rs
Normal file
186
src/io/fs.rs
Normal file
|
@ -0,0 +1,186 @@
|
|||
//! Helpers and common functions for filesystem access
|
||||
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
io::{BufReader, BufWriter, Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
use anyhow::{Context, Ok, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A file metadata version number
|
||||
///
|
||||
/// Deserialized metadata with non-current version number are considered invalid
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct FileMetaVersion(pub u32);
|
||||
|
||||
/// Metadata stored with generated files to track required incremental updates
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct FileMeta {
|
||||
/// Version of data described by the FileMeta
|
||||
version: FileMetaVersion,
|
||||
/// Timestamp stored with generated data
|
||||
///
|
||||
/// This timestamp is always the time of last modification of the inputs
|
||||
/// that were used to generate the file described by the FileMeta.
|
||||
timestamp: SystemTime,
|
||||
}
|
||||
|
||||
/// Helper for creating suffixed file paths
|
||||
fn suffix_name(path: &Path, suffix: &str) -> PathBuf {
|
||||
let mut file_name = path.file_name().unwrap_or_default().to_os_string();
|
||||
file_name.push(suffix);
|
||||
|
||||
let mut ret = path.to_path_buf();
|
||||
ret.set_file_name(file_name);
|
||||
ret
|
||||
}
|
||||
|
||||
/// Derives the filename for temporary storage of data during generation
|
||||
fn tmpfile_name(path: &Path) -> PathBuf {
|
||||
suffix_name(path, ".tmp")
|
||||
}
|
||||
|
||||
/// Derives the filename for associated metadata for generated files
|
||||
fn metafile_name(path: &Path) -> PathBuf {
|
||||
suffix_name(path, ".meta")
|
||||
}
|
||||
|
||||
/// Creates a directory including all its parents
|
||||
///
|
||||
/// Wrapper around [fs::create_dir_all] that adds a more descriptive error message
|
||||
pub fn create_dir_all(path: &Path) -> Result<()> {
|
||||
fs::create_dir_all(path)
|
||||
.with_context(|| format!("Failed to create directory {}", path.display(),))
|
||||
}
|
||||
|
||||
/// Renames a file or directory
|
||||
///
|
||||
/// Wrapper around [fs::rename] that adds a more descriptive error message
|
||||
pub fn rename(from: &Path, to: &Path) -> Result<()> {
|
||||
fs::rename(from, to)
|
||||
.with_context(|| format!("Failed to rename {} to {}", from.display(), to.display()))
|
||||
}
|
||||
|
||||
/// Creates a new file
|
||||
///
|
||||
/// The contents of the file are defined by the passed function.
|
||||
pub fn create<T, F>(path: &Path, f: F) -> Result<T>
|
||||
where
|
||||
F: FnOnce(&mut BufWriter<File>) -> Result<T>,
|
||||
{
|
||||
(|| {
|
||||
let file = File::create(path)?;
|
||||
let mut writer = BufWriter::new(file);
|
||||
|
||||
let ret = f(&mut writer)?;
|
||||
writer.flush()?;
|
||||
|
||||
Ok(ret)
|
||||
})()
|
||||
.with_context(|| format!("Failed to write file {}", path.display()))
|
||||
}
|
||||
|
||||
/// Checks whether the contents of two files are equal
|
||||
pub fn equal(path1: &Path, path2: &Path) -> Result<bool> {
|
||||
let mut file1 = BufReader::new(
|
||||
fs::File::open(path1)
|
||||
.with_context(|| format!("Failed to open file {}", path1.display()))?,
|
||||
)
|
||||
.bytes();
|
||||
let mut file2 = BufReader::new(
|
||||
fs::File::open(path2)
|
||||
.with_context(|| format!("Failed to open file {}", path2.display()))?,
|
||||
)
|
||||
.bytes();
|
||||
|
||||
Ok(loop {
|
||||
match (file1.next().transpose()?, file2.next().transpose()?) {
|
||||
(Some(b1), Some(b2)) if b1 == b2 => continue,
|
||||
(None, None) => break true,
|
||||
_ => break false,
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a new file, temporarily storing its contents in a temporary file
|
||||
///
|
||||
/// Storing the data in a temporary file prevents leaving half-written files
|
||||
/// when the function is interrupted. In addition, the old and new contents of
|
||||
/// the file are compared if a file with the same name already exists, and the
|
||||
/// file timestamp is only updated if the contents have changed.
|
||||
pub fn create_with_tmpfile<T, F>(path: &Path, f: F) -> Result<T>
|
||||
where
|
||||
F: FnOnce(&mut BufWriter<File>) -> Result<T>,
|
||||
{
|
||||
let tmp_path = tmpfile_name(path);
|
||||
let mut cleanup = true;
|
||||
|
||||
let ret = (|| {
|
||||
let ret = create(&tmp_path, f)?;
|
||||
if !matches!(equal(path, &tmp_path), Result::Ok(true)) {
|
||||
rename(&tmp_path, path)?;
|
||||
cleanup = false;
|
||||
}
|
||||
Ok(ret)
|
||||
})();
|
||||
|
||||
if cleanup {
|
||||
let _ = fs::remove_file(&tmp_path);
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
/// Returns the time of last modification for a given file path
|
||||
pub fn modified_timestamp(path: &Path) -> Result<SystemTime> {
|
||||
fs::metadata(path)
|
||||
.and_then(|meta| meta.modified())
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to get modified timestamp of file {}",
|
||||
path.display()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Reads the stored timestamp from file metadata for a file previously written
|
||||
/// using [create_with_timestamp]
|
||||
pub fn read_timestamp(path: &Path, version: FileMetaVersion) -> Option<SystemTime> {
|
||||
let meta_path = metafile_name(path);
|
||||
let mut file = BufReader::new(fs::File::open(meta_path).ok()?);
|
||||
|
||||
let meta: FileMeta = serde_json::from_reader(&mut file).ok()?;
|
||||
if meta.version != version {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(meta.timestamp)
|
||||
}
|
||||
|
||||
/// Creates a new file, temporarily storing its contents in a temporary file
|
||||
/// like [create_with_tmpfile], and storing a timestamp in a metadata file
|
||||
/// if successful
|
||||
///
|
||||
/// The timestamp can be retrieved later using [read_timestamp].
|
||||
pub fn create_with_timestamp<T, F>(
|
||||
path: &Path,
|
||||
version: FileMetaVersion,
|
||||
timestamp: SystemTime,
|
||||
f: F,
|
||||
) -> Result<T>
|
||||
where
|
||||
F: FnOnce(&mut BufWriter<File>) -> Result<T>,
|
||||
{
|
||||
let ret = create_with_tmpfile(path, f)?;
|
||||
|
||||
let meta_path = metafile_name(path);
|
||||
create(&meta_path, |file| {
|
||||
serde_json::to_writer(file, &FileMeta { version, timestamp })?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Ok(ret)
|
||||
}
|
4
src/io/mod.rs
Normal file
4
src/io/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
//! Input/output functions
|
||||
|
||||
pub mod fs;
|
||||
pub mod storage;
|
73
src/io/storage.rs
Normal file
73
src/io/storage.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
//! Functions for serializing and deserializing MinedMap data structures efficiently
|
||||
//!
|
||||
//! Data is serialized using Bincode and compressed using zstd.
|
||||
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{Read, Write},
|
||||
path::Path,
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use bincode::{Decode, Encode};
|
||||
|
||||
use super::fs;
|
||||
|
||||
/// Bincode configuration
|
||||
const BINCODE_CONFIG: bincode::config::Configuration = bincode::config::standard();
|
||||
|
||||
/// Serializes data and writes it to a writer
|
||||
pub fn write<W: Write, T: Encode>(writer: &mut W, value: &T) -> Result<()> {
|
||||
let data = bincode::encode_to_vec(value, BINCODE_CONFIG)?;
|
||||
let len = u32::try_from(data.len())?;
|
||||
let compressed = zstd::bulk::compress(&data, 1)?;
|
||||
drop(data);
|
||||
|
||||
writer.write_all(&len.to_be_bytes())?;
|
||||
writer.write_all(&compressed)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Serializes data and stores it in a file
|
||||
///
|
||||
/// A timestamp is stored in an assiciated metadata file.
|
||||
pub fn write_file<T: Encode>(
|
||||
path: &Path,
|
||||
value: &T,
|
||||
version: fs::FileMetaVersion,
|
||||
timestamp: SystemTime,
|
||||
) -> Result<()> {
|
||||
fs::create_with_timestamp(path, version, timestamp, |file| write(file, value))
|
||||
}
|
||||
|
||||
/// Reads data from a reader and deserializes it
|
||||
pub fn read<R, T>(reader: &mut R) -> Result<T>
|
||||
where
|
||||
R: Read,
|
||||
T: Decode<()>,
|
||||
{
|
||||
let mut len_buf = [0u8; 4];
|
||||
reader.read_exact(&mut len_buf)?;
|
||||
let len = usize::try_from(u32::from_be_bytes(len_buf))?;
|
||||
|
||||
let mut compressed = vec![];
|
||||
reader.read_to_end(&mut compressed)?;
|
||||
let data = zstd::bulk::decompress(&compressed, len)?;
|
||||
drop(compressed);
|
||||
|
||||
Ok(bincode::decode_from_slice(&data, BINCODE_CONFIG)?.0)
|
||||
}
|
||||
|
||||
/// Reads data from a file and deserializes it
|
||||
pub fn read_file<T>(path: &Path) -> Result<T>
|
||||
where
|
||||
T: Decode<()>,
|
||||
{
|
||||
(|| -> Result<T> {
|
||||
let mut file = File::open(path)?;
|
||||
read(&mut file)
|
||||
})()
|
||||
.with_context(|| format!("Failed to read file {}", path.display()))
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue