Compare commits

...

517 commits
v1.15 ... main

Author SHA1 Message Date
118034dd27
Merge pull request #77 from neocturne/textvalue
Some checks failed
MinedMap / viewer (push) Has been cancelled
MinedMap / fmt (push) Has been cancelled
MinedMap / clippy (push) Has been cancelled
MinedMap / docs (push) Has been cancelled
MinedMap / test (macOS-latest) (push) Has been cancelled
MinedMap / test (ubuntu-latest) (push) Has been cancelled
MinedMap / test (windows-latest) (push) Has been cancelled
MinedMap / build (.exe, windows-2019, i686-pc-windows-msvc) (push) Has been cancelled
MinedMap / build (.exe, windows-2019, x86_64-pc-windows-msvc) (push) Has been cancelled
MinedMap / build (macos-13, aarch64-apple-darwin) (push) Has been cancelled
MinedMap / build (macos-13, x86_64-apple-darwin) (push) Has been cancelled
MinedMap / build (ubuntu-22.04, x86_64-unknown-linux-gnu) (push) Has been cancelled
MinedMap / build-container (push) Has been cancelled
MinedMap / viewer-container (push) Has been cancelled
Update to Rust 1.86, make TextValue handling more correct
2025-04-03 19:00:14 +02:00
ca880ab3b4
world/text_value: do not fall back to NBT deserialization after DataVersion 4290
An invalid JSON string should not be emitted verbatim; ignore the
content instead.

Also increment entity meta version, which had been forgotten in the
previous commit.
2025-04-03 18:52:39 +02:00
dd56e842b5
ci: update to Rust 1.86
There is no official 1.86 Docker image yet.
2025-04-03 18:52:33 +02:00
69b62576ea
world/chunk: fix new Rust 1.86 clippy warning 2025-04-03 18:37:54 +02:00
58f2ff53b9
Merge pull request #76 from neocturne/mc-1.21.5
Some checks are pending
MinedMap / viewer (push) Waiting to run
MinedMap / fmt (push) Waiting to run
MinedMap / clippy (push) Waiting to run
MinedMap / docs (push) Waiting to run
MinedMap / test (macOS-latest) (push) Waiting to run
MinedMap / test (ubuntu-latest) (push) Waiting to run
MinedMap / test (windows-latest) (push) Waiting to run
MinedMap / build (.exe, windows-2019, i686-pc-windows-msvc) (push) Waiting to run
MinedMap / build (.exe, windows-2019, x86_64-pc-windows-msvc) (push) Waiting to run
MinedMap / build (macos-13, aarch64-apple-darwin) (push) Waiting to run
MinedMap / build (macos-13, x86_64-apple-darwin) (push) Waiting to run
MinedMap / build (ubuntu-22.04, x86_64-unknown-linux-gnu) (push) Waiting to run
MinedMap / build-container (push) Blocked by required conditions
MinedMap / viewer-container (push) Blocked by required conditions
Updates and Minecraft 1.21.5 support
2025-04-02 23:06:58 +02:00
42b302f493
resource: add new Minecraft 1.21.5 block types 2025-04-02 22:59:28 +02:00
5f84ec8ed2
world/text_value: add support for new NBT text serialization
Starting with DataVersion 4290, text is stored as NBT instead of JSON.
The structure remains the same.
2025-04-02 22:52:22 +02:00
ba6e4bae7f
world: rename JSONText to TextValue
New Minecraft version do not store text as JSON anymore.
2025-04-02 22:52:22 +02:00
442009eb08
Update dependencies 2025-04-02 19:06:01 +02:00
23b2f274be
ci, docker: update to Rust 1.85.1 2025-04-02 19:04:15 +02:00
3008203080
minedmap 2.5.0
Some checks failed
MinedMap / viewer (push) Has been cancelled
MinedMap / fmt (push) Has been cancelled
MinedMap / clippy (push) Has been cancelled
MinedMap / docs (push) Has been cancelled
MinedMap / test (macOS-latest) (push) Has been cancelled
MinedMap / test (ubuntu-latest) (push) Has been cancelled
MinedMap / test (windows-latest) (push) Has been cancelled
MinedMap / build (.exe, windows-2019, i686-pc-windows-msvc) (push) Has been cancelled
MinedMap / build (.exe, windows-2019, x86_64-pc-windows-msvc) (push) Has been cancelled
MinedMap / build (macos-13, aarch64-apple-darwin) (push) Has been cancelled
MinedMap / build (macos-13, x86_64-apple-darwin) (push) Has been cancelled
MinedMap / build (ubuntu-22.04, x86_64-unknown-linux-gnu) (push) Has been cancelled
MinedMap / build-container (push) Has been cancelled
MinedMap / viewer-container (push) Has been cancelled
2025-03-16 12:24:29 +01:00
d638c810f2
Merge pull request #74 from neocturne/no-wildcard-dep
default-alloc: fix wildcard dependency
2025-03-16 12:23:40 +01:00
6e5b958912
default-alloc: fix wildcard dependency 2025-03-16 12:17:20 +01:00
974a0f37df
minedmap-nbt 0.2.0 2025-03-14 20:02:49 +01:00
54ea2b2f28
minedmap-resource 0.7.0 2025-03-14 20:02:36 +01:00
6a54f57c50
minedmap-types 0.2.0 2025-03-14 20:02:13 +01:00
dca365f4e2
CHANGELOG.md: mention new Docker images and docker-compose example
Some checks failed
MinedMap / viewer (push) Has been cancelled
MinedMap / fmt (push) Has been cancelled
MinedMap / clippy (push) Has been cancelled
MinedMap / docs (push) Has been cancelled
MinedMap / test (macOS-latest) (push) Has been cancelled
MinedMap / test (ubuntu-latest) (push) Has been cancelled
MinedMap / test (windows-latest) (push) Has been cancelled
MinedMap / build (.exe, windows-2019, i686-pc-windows-msvc) (push) Has been cancelled
MinedMap / build (.exe, windows-2019, x86_64-pc-windows-msvc) (push) Has been cancelled
MinedMap / build (macos-13, aarch64-apple-darwin) (push) Has been cancelled
MinedMap / build (macos-13, x86_64-apple-darwin) (push) Has been cancelled
MinedMap / build (ubuntu-22.04, x86_64-unknown-linux-gnu) (push) Has been cancelled
MinedMap / build-container (push) Has been cancelled
MinedMap / viewer-container (push) Has been cancelled
2025-03-14 16:29:23 +01:00
e600a9dabb
CHANGELOG.md: mention Bincode update
While the update is an internal change, it does affect the MSRV, so it
should be mentioned in the changelog.
2025-03-14 16:24:22 +01:00
0f52f6a2c1
Merge pull request #72 from neocturne/ci-cache-key
ci: include target in cache key
2025-03-14 12:26:57 +01:00
d6cd0fc53b
ci: include target in cache key
Avoid cache keys colliding between build jobs running on the same OS.
2025-03-14 12:19:39 +01:00
c49ad6e608
Merge pull request #71 from neocturne/bincode2
Some checks are pending
MinedMap / viewer (push) Waiting to run
MinedMap / fmt (push) Waiting to run
MinedMap / clippy (push) Waiting to run
MinedMap / docs (push) Waiting to run
MinedMap / test (macOS-latest) (push) Waiting to run
MinedMap / test (ubuntu-latest) (push) Waiting to run
MinedMap / test (windows-latest) (push) Waiting to run
MinedMap / build (.exe, windows-2019, i686-pc-windows-msvc) (push) Waiting to run
MinedMap / build (.exe, windows-2019, x86_64-pc-windows-msvc) (push) Waiting to run
MinedMap / build (macos-13, aarch64-apple-darwin) (push) Waiting to run
MinedMap / build (macos-13, x86_64-apple-darwin) (push) Waiting to run
MinedMap / build (ubuntu-22.04, x86_64-unknown-linux-gnu) (push) Waiting to run
MinedMap / build-container (push) Blocked by required conditions
MinedMap / viewer-container (push) Blocked by required conditions
Bincode 2
2025-03-13 22:09:18 +01:00
5ee826a11b
ci: set fixed Rust version 1.85
Avoid CI failure due to new warnings or fmt changes in new Rust version.
2025-03-13 21:55:36 +01:00
775fcb2d1b
treewide: cargo fmt with 1.85 2025-03-13 21:50:50 +01:00
7bba5bae55
Update to Rust 2024
With bincode 2, we require rust 1.85 anyways, so we might as well
upgrade, too.
2025-03-13 21:50:50 +01:00
5c85687554
Clean up dependency features 2025-03-13 21:50:50 +01:00
708fb9645d
core/region_processor: refactor
Separate configuration and mutable state, also allowing to avoid a few
mem::take().
2025-03-13 21:50:50 +01:00
53a0f24600
treewide: update to bincode 2
Consistently use bincode's Encode/Decode to avoid issues with
incompatible serde features. Support for storing some temporary files as
JSON is removed.

The size of the "processed" directory is reduced by ~8% with the new
default encoding of bincode 2. Performance is more or less unaffected.
2025-03-13 21:50:50 +01:00
404ad74235
core: deserialize biome list into Vec
Only use IndexSet for deduplication while processing the biome; when
deserializing, no deduplication is required, so using a Vec is faster
(besides IndexSet missing non-serde support for bincode 2).
2025-03-13 21:50:50 +01:00
deb232ddf3
Update dependencies 2025-03-13 21:50:50 +01:00
fbdd5ed457
Dockerfile: switch back to docker.io/library/rust:alpine image
bincode 2 required Rust 1.85, so our options are to switch to Alpine
edge or to use the rust image. While using the rust image results in a
statically linked binary, this does not actually increase the size of
the image, as we were already using jemalloc, so almost nothing of libc
is actually used.
2025-03-13 21:50:50 +01:00
b1c0f316cb
Merge pull request #70 from neocturne/seagrass
resource: make seagrass opaque
2025-02-28 16:27:32 +01:00
7686996fd3
resource: make seagrass opaque
See changelog for rationale.
2025-02-28 16:16:29 +01:00
b8a5d1580b
Merge pull request #69 from neocturne/zlib-rs
Switch to zlib-rs
2025-02-28 13:54:53 +01:00
f8c8ca78ba
Switch from zlib-ng to zlib-rs
zlib-rs provides the same performance as zlib-ng with minedmap, while
reducing the amount of C code and avoiding the external build dependency
on CMake.
2025-02-28 12:43:47 +01:00
40bc6cd2a9
Update dependencies 2025-02-28 11:55:42 +01:00
5ee8e493d4
docker-compose.yml: mount volumes read-only where appropriate 2025-02-25 18:23:54 +01:00
6e2f2697fc
Merge pull request #68 from neocturne/docker
docker: set ARG after apk add
2025-02-22 11:34:55 +01:00
24c266fc78
docker: set ARG after apk add
Allow reusing apk add layer when MINEDMAP_VERSION has changed.

Signed-off-by: Matthias Schiffer <mschiffer@universe-factory.net>
2025-02-22 11:29:10 +01:00
0a08e8cc46
Merge pull request #67 from neocturne/docker
Docker image improvements and docker-compose.yml example
2025-02-22 11:23:51 +01:00
90f2c5fdd0
docker: add example docker-compose.yml 2025-02-22 11:18:03 +01:00
8cb1eee60b
docker, ci: fix --version output
When building the docker image manually, MINEDMAP_VERSION needs to be
set explicitly to get a proper version string.
2025-02-22 11:15:47 +01:00
282f62fc30
docker: run minedmap as unpriviledged user 2025-02-22 04:02:10 +01:00
7bc15f97de
docker: viewer: use nginx:alpine-slim as base 2025-02-22 03:13:43 +01:00
901489dbc3
Merge pull request #66 from neocturne/docker
Watch mode and Docker publish
2025-02-21 21:52:32 +01:00
dba3dd551e
ci: build Docker images, publish to GHCR 2025-02-21 21:43:05 +01:00
850b1a668b
docker: use lowercase stage name 2025-02-21 19:08:51 +01:00
3b5ce82873
docker: include Alpine base tools, tini
Including tini fixes forwarding signals to MinedMap, allowing to
interrupt it using Ctrl-C. The base tools may be used to add a wrapper
script to configure MinedMap with environment variables.

As the Alpine base is included now, we can switch from the rust:alpine
image to alpine:latest, resulting in MinedMap to be linked dynamically.
2025-02-21 10:55:50 +01:00
cb0aa235db
docker: move viewer Dockerfile to viewer subdirectory 2025-02-21 10:55:50 +01:00
d02ca9aea2
ci: update OS 2025-02-21 10:55:50 +01:00
c10e9e4902
Implement watch mode 2025-02-21 10:55:50 +01:00
37126f69fc
MetadataWriter: add fallback to level.dat_old
Looking at inotify dumps, it appears like because of bad implementation
choices, Minecraft's level.dat may not exist for a brief moment between
moving the old file to level.dat_old and moving a new version into place.

Add a fallback to level.dat_old, so generation will not fail if were
unlucky enough to hit this moment.
2025-02-21 10:55:50 +01:00
971afea727
Fix new clippy warnings 2025-02-21 10:55:50 +01:00
f661f854a4
Merge pull request #65 from neocturne/jemalloc
Add jemalloc and jemalloc-auto features
2025-02-12 20:51:51 +01:00
1d9be9a41c
Add jemalloc and jemalloc-auto features
Introduce the new features jemalloc (set jemalloc global allocator
unconditionally) and jemalloc-auto (set jemalloc global allocator on
musl-based targets to fix multithreaded performance, see [1]).

Because cargo does not support target-specific features or feature
defaults, the default is handled using a helper crate
minedmap-default-alloc.

[1] https://nickb.dev/blog/default-musl-allocator-considered-harmful-to-performance/
2025-02-12 20:34:09 +01:00
a25b3cdbd7
Merge pull request #64 from neocturne/fallback-biome
resource, world: implement fallback to plains for unknown biomes
2025-02-11 23:25:17 +01:00
d96bb727f7
ci: upgrade to upload-artifact v4 2025-02-11 23:04:08 +01:00
0dd36a409a
Update dependencies
Signed-off-by: Matthias Schiffer <mschiffer@universe-factory.net>
2025-02-11 23:03:33 +01:00
a10151a4f3
resource, world: implement fallback to plains for unknown biomes
Closes #63
2025-02-11 23:00:21 +01:00
d7fc95c950
README.md: fix size reduction estimate for WebP tiles
I accidentally measured the output size including the `processed`
directory for the previous numbers.
2025-01-11 02:10:00 +01:00
561a1e6577
minedmap 2.4.0 2025-01-11 01:54:53 +01:00
0bf1d46aad
minedmap-types 0.1.4 2025-01-11 01:53:05 +01:00
a2f0ad401d
CHANGELOG.md: fix missing word 2025-01-11 01:51:35 +01:00
ec309dc15f
Merge pull request #62 from neocturne/webp
core, viewer: add support for WebP output
2025-01-11 01:50:36 +01:00
6763e2b4ec
Update dependencies 2025-01-11 01:42:42 +01:00
c23b53a8c3
core, viewer: add support for WebP output
WebP can be selected by passing `--image-format webp` on the command
line. For typical Minecraft worlds, this results in a size reduction of
10-15% without increasing processing time.
2025-01-11 01:41:04 +01:00
bb11b29e92
Merge pull request #60 from neocturne/updates
Updates
2025-01-09 21:40:34 +01:00
28a191a23a
resource: ignore array size clippy warning
The const is used only once, so there is no reason not to inline it.
2025-01-09 21:09:48 +01:00
52572a9e81
Update dependencies 2025-01-09 20:57:12 +01:00
b9cd94b235
CHANGELOG.md: remove reference to Minecraft internals
Remove a sentence that refers to internal color names, as the
information is more confusing than helpful in a user-facing changlog.
2025-01-06 22:31:56 +01:00
f9954d1ce4
minedmap 2.3.1 2025-01-06 21:25:29 +01:00
a67bdb3b67
minedmap-resource 0.6.0 2025-01-06 21:25:20 +01:00
0a3f6d7765
Merge pull request #59 from neocturne/sign-colors
Sign color fixes
2025-01-06 21:24:46 +01:00
ff6e28d381
world, viewer: fix sign text colors
- 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. Dyed signs use different
color names.

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.
2025-01-06 21:16:41 +01:00
9375af8d54
resource: impl Ord for Color
Allow using Color in FormattedText.
2025-01-06 20:30:32 +01:00
7f329ac8e7
CHANGELOG.md: fix heading for previous release 2025-01-06 20:30:32 +01:00
650cd23198
minedmap 2.3.0 2025-01-02 13:42:11 +01:00
941cd16337
minedmap-resource 0.5.0 2025-01-02 13:41:56 +01:00
c1d2eaa17e
Merge pull request #58 from neocturne/update-1.21.4
Updates + 1.21.4 support
2025-01-02 01:58:26 +01:00
00237101f2
README.md: update for 1.21.4 support 2025-01-02 01:31:26 +01:00
8f408e78a0
resource: codegen biome list 2025-01-02 01:31:26 +01:00
7b98954c80
resource: add comments to generated file 2025-01-02 01:31:26 +01:00
00216341ca
resource: separate current biome data from aliases/legacy IDs
Prepare for automatic generation of current biome data.
2025-01-02 01:31:26 +01:00
83bd936f80
viewer: add images for pale oak signs 2024-12-31 12:21:09 +01:00
9b3b345318
resource: update block types and biomes for 1.21.4 2024-12-31 12:21:09 +01:00
11e25106cf
Update dependencies 2024-12-31 03:05:42 +01:00
491ec5cdca
Merge pull request #57 from neocturne/clippy-fixes
treewide: clippy fixes + dependency updates
2024-12-17 00:40:40 +01:00
feaf90c96c
Update dependencies 2024-12-17 00:31:38 +01:00
4933d8e15f
treewide: clippy fixes
Fix all clippy warnings as of Rust 1.83.0.
2024-12-17 00:24:56 +01:00
kek.rs
2156bff59a
Add Dockerfiles (#56) 2024-12-17 00:05:12 +01:00
7b44ee30d6
Add LICENSE file again
It can be useful for Github to show the license next to the repo, and
it's clearer that the license applies both to the Rust code and the
viewer this way.

The project was relicensed from BSD-2-Clause to MIT in 2023, but the
first year of the copyright is when the project started in 2015.
2024-11-20 14:27:30 +01:00
f9a03b332c
README.md: fix incorrect --sign-transform example 2024-07-18 23:22:05 +02:00
f43e84eb2b
minedmap 2.2.0 2024-06-23 11:31:45 +02:00
269f08d356
minedmap-resource 0.4.0 2024-06-23 11:30:41 +02:00
f1bc18add1
minedmap-types 0.1.3 2024-06-23 11:30:20 +02:00
6d6f3e5b49
Merge pull request #54 from neocturne/minecraft-1.21
Update dependencies and add Minecraft 1.21 support
2024-06-23 11:23:53 +02:00
414ad5a493
resource: add Minecraft 1.21 block types 2024-06-23 01:24:07 +02:00
7f3e47fcb4
treewide: update dependencies 2024-06-23 01:21:58 +02:00
bc8219772f
README.md: mention argument quoting difference on Windows 2024-06-23 01:21:58 +02:00
661da4698d
workflows: update MacOS jobs to macos-13 2024-06-19 19:39:30 +02:00
e9abe6b502
minedmap 2.1.1 2024-06-14 16:31:18 +02:00
4eb963f147
Merge pull request #53 from neocturne/empty-region-fixes
Empty region fixes
2024-06-14 16:30:34 +02:00
e74e7be686
core: region_processor: ignore empty region files
Minecraft generates empty region files in some cases. Just ignore these
files instead of printing an error message for each.
2024-06-14 16:19:55 +02:00
66189d279c
core: region_processor: fix crash due to incorrect counting in info message
Fixes #52
2024-06-14 16:02:56 +02:00
d435643cfc
Merge pull request #48 from neocturne/update
Update dependencies + fix new clippy lint
2024-03-04 00:04:56 +01:00
39b073f24b
Update dependencies 2024-03-03 23:56:59 +01:00
44e914599b
types: fix new clippy lint 2024-03-03 23:55:59 +01:00
bccd6d6cb4
README.md: some rewording 2024-03-03 23:44:56 +01:00
Myrddin Emrys
00eea45375
Improve README.md instructions.
Added instructions that would have made my initial attempts to use the tool less confusing.
2024-02-14 18:01:25 -06:00
9fd3989a95
minedmap 2.1.0 2024-01-27 14:16:03 +01:00
d6716a598b
minedmap-resource 0.3.0 2024-01-27 14:15:28 +01:00
f186681b41
release: move commit message configuration to workspace 2024-01-27 14:14:48 +01:00
3d84e9c9e4
CHANGELOG.md: mention --sign-transform 2024-01-27 14:08:42 +01:00
7009dd791d
Merge pull request #45 from neocturne/sign-transform
Sign text transformation support
2024-01-26 23:00:07 +01:00
8a1a26c13c
core: add support for sign text transformations
Add support for regexp replacement patterns, which can be useful when
the text matched by --sign-filter or --sign-prefix should not be
displayed.
2024-01-26 22:40:19 +01:00
a99a734df8
Update dependencies 2024-01-25 19:44:37 +01:00
3ceb7ae188
Merge pull request #40 from neocturne/sign-marker
Display markers for signs
2024-01-11 12:59:32 +01:00
e18761a3e4
resource: extract.py: pass directory of unpacked Minecraft JAR, not full path to assets
Make the script easier to use, and more consistent with sign_textures.py.
2024-01-11 12:44:42 +01:00
05d8faeb5c
resource: README.md: sign-related updates 2024-01-11 12:44:42 +01:00
6299c871a9
CHANGELOG.md: add sign layer 2024-01-11 00:02:25 +01:00
03521684b9
README.md: add description of sign feature 2024-01-10 23:57:23 +01:00
643035eaed
viewer: switch to modern fetch API, do not cache metadata and entity files 2024-01-10 22:56:33 +01:00
3d024c6cd8
viewer: update opened marker popup on URL hash change 2024-01-10 22:56:33 +01:00
08f84fa339
viewer: do not show sign layer when the feature is disabled 2024-01-10 22:56:32 +01:00
625f2a13a3
core/metadata_writer: add enabled features to metadata
Only consider sign support enabled when at least one pattern is
configured.
2024-01-10 22:56:32 +01:00
7daddd6bbc
viewer: include open marker in URL
Allow sharing URLs that reference a marker.
2024-01-10 22:56:32 +01:00
7b3ac8647e
viewer: store URL paramters in a 'params' variable 2024-01-10 22:56:32 +01:00
76df56c9ce
viewer: sort signs at the same x/z coordinates from top to bottom 2024-01-10 22:56:32 +01:00
31de0dc0bd
viewer: sign popup styling 2024-01-10 22:56:31 +01:00
ac0fd06b16
viewer: add icon shadows 2024-01-10 22:56:31 +01:00
989428f78d
viewer: use sign icons instead of default markers 2024-01-10 22:56:31 +01:00
43d7dc751e
viewer: allow zooming to level 5
Allow zooming in far enough so neighboring signs don't overlap.
2024-01-10 22:56:31 +01:00
7834315dd3
viewer: add sign images 2024-01-10 22:56:31 +01:00
dca9c394f2
viewer: display markers for signs
Still needs better styling. It might also be nice to include the currently
opened marker in the URL.
2024-01-10 22:56:30 +01:00
87d4371922
core/metadata_writer: implement filtering for signs 2024-01-10 22:56:30 +01:00
a1dd77c8fd
world/sign: implement Display for SignText 2024-01-10 22:56:30 +01:00
9fd5689ebb
core: add sign pattern command line argument handling 2024-01-10 22:56:30 +01:00
6da921cca3
core: wrap long help texts 2024-01-10 22:56:30 +01:00
fb361145eb
world: add sign position and material information to block entities 2024-01-10 22:56:29 +01:00
9aacd9cc2b
resource: make regular signs transparent
For consistency with other sign types. Signs will be shown on a separate
layer.
2024-01-10 22:56:29 +01:00
48a6e242ea
resource: add WallSign flag to block types
Allow to distinguish wall signs from freestanding or -hanging signs.
2024-01-10 22:56:29 +01:00
abf87e75ee
resource: add sign material to block types 2024-01-10 22:56:29 +01:00
8814dcff89
resource: split BlockType in Copy and !Copy parts
Preparation for actually adding !Copy fields to BlockType.

Only the Copy parts are added to the processed data, avoiding .clone()
for the most part.
2024-01-10 22:56:29 +01:00
1874d3082d
resource: fix typo in doc comment 2024-01-10 22:56:28 +01:00
d29c0df25d
core/metadata_writer: write entity list for viewer 2024-01-10 22:56:28 +01:00
cde6a4b6e6
core: merge entity data for all regions into one file
Introduce the EntityCollector, using the TileCollector and TileMerger
traits.
2024-01-10 22:56:28 +01:00
1143396068
core: use debug format for logging errors
Show the full cause stack.
2024-01-10 22:56:28 +01:00
0f308788ef
core: split TileMerger out of TileMipmapper
Reusable mipmap-style tile merging
2024-01-10 22:56:28 +01:00
7740ce0522
core/region_processor: sort processed block entities
Make the block entity list more reproducible when the stored chunk order
changes.
2024-01-10 22:56:27 +01:00
e36ae4601d
world: add Eq + Ord to all block entity types 2024-01-10 22:56:27 +01:00
825cf70e51
core: split TileCollector trait out of TileMipmapper
Make mipmap-style recursive processing of tiles reusable.
2024-01-10 22:56:27 +01:00
7297c03567
core/region_processor: store processed block entities for each region 2024-01-10 22:56:27 +01:00
c44f6ab859
io/storage: add JSON support
Bincode can't deal with a number of serde's features that would be
desirable if we're using the same Serialize impls for generating JSON
data for the viewer.
2024-01-10 22:56:27 +01:00
5d40d061a4
io/storage: split out inner part of read/write
Allow reusing the read/write logic without requiring control over the file
open.
2024-01-10 22:56:26 +01:00
f0e0db63d3
world: process sign data, prepare for serialization 2024-01-10 22:56:26 +01:00
638d5046c9
world/chunk: change Chunk into a struct
The version-specific part is extracted as an enum ChunkInner.
2024-01-10 22:56:26 +01:00
61d456846a
world: implement decoding raw JSON text into a linear list of formatted strings 2024-01-10 22:56:26 +01:00
f78dd795ca
world/de: add deserialization of sign block entities 2024-01-10 22:56:26 +01:00
0988ebe095
core/tile_mipmapper: fix file meta version for mipmap tiles
The meta version should only change when the outputs must be regenerated
even if the inputs stay the same. This should never be the case for
mipmap tiles, so we separate the meta version from map/lightmap tiles.
2024-01-10 22:56:26 +01:00
f79edb462c
core/common: add better explanation to file meta version definitions 2024-01-10 22:56:25 +01:00
e5d9c813ba
docs/images: add screenshot for sign marker documentation 2024-01-10 22:55:42 +01:00
7b12c4aea3
README.md: some improvements to the "How to use" and "Installation" sections 2024-01-10 22:42:58 +01:00
2e6a200c6c
README.md: reference screenshot by commit ID 2024-01-10 22:07:20 +01:00
f08acd06e2
Merge pull request #44 from neocturne/hide-unknown-warnings
Hide unknown block/biome type warnings by default
2024-01-10 13:14:59 +01:00
e8165aa47d
world: hide unknown block/biome type warnings by default
While using MinedMap with modded Minecraft version is not officially
supported, it should still work reasonably well if you're okay with
custom block types being invisible and custom biomes using default
grass/color/foliage colors.

Avoid spamming the log with messages for each section in this case
without --verbose, and instead just print a single warning at the end of
processing.
2024-01-10 13:02:29 +01:00
1e41e1bc6a
world/chunk: move SectionIter structs to impl 2024-01-10 13:02:29 +01:00
5273e4b366
CHANGELOG.md: reformat previous release changes
Use the "Added" and "Changed" headings like in previous releases.
2024-01-10 13:02:29 +01:00
3a796325ba
Update dependencies 2024-01-10 13:02:29 +01:00
7ea09a46f9
minedmap 2.0.2 2024-01-07 22:28:53 +01:00
96c60b048e
minedmap-nbt 0.1.1 2024-01-07 22:26:12 +01:00
b48cde7ba8
minedmap-types 0.1.2 2024-01-07 22:23:45 +01:00
e856800c3d
minedmap-resource 0.2.0 2024-01-07 22:21:46 +01:00
5602d3d499
CHANGELOG.md: update 2024-01-07 22:15:39 +01:00
fbb99bb467
Update remaining repository URLs 2024-01-07 22:08:55 +01:00
a37dd79a02
Merge pull request #43 from neocturne/mc1.20.3
Updates & Minecraft 1.20.3 support
2024-01-05 22:07:29 +01:00
997d807dc7
resource: add short_grass block type
Besides adding a number of experimental block types for the 1.21 preview,
Minecraft 1.20.3 also renamed the "grass" block to "short grass"
internally. Add the new block name to avoid warnings during generation.
2024-01-05 21:54:17 +01:00
e5b7838ac5
Update dependencies 2024-01-05 21:54:17 +01:00
cb7f428974
viewer: update to leaflet 1.9.4 2024-01-03 04:57:15 +01:00
a8e0af287b
viewer: update attribution link 2024-01-03 04:56:51 +01:00
fdf44ebc80
viewer: update to use "modern" ECMAScript features 2024-01-03 04:56:47 +01:00
c67f3b8c48
Merge pull request #42 from neocturne/updates
Dependency updates & some region processor refactoring
2023-12-30 01:28:04 +01:00
1812e5c6d6
Add type alias for f32 colors to minedmap-resource
Allow removing the dependency on a specific glam version from the main
crate.
2023-12-29 20:13:03 +01:00
1432df7c93
Update dependencies 2023-12-29 20:06:52 +01:00
93c1ce9437
core/region_processor: further split up SingleRegionProcessor::run() 2023-11-25 23:17:04 +01:00
25f675bd3b
core/region_processor: make logic for skipping updates more fine-grained
Allow skipping parts of the processing separately for processed region
and lightmap files.
2023-11-25 22:59:12 +01:00
a5ad057e0c
core/region_processor: refactor RegionProcessor::process_region() 2023-11-25 22:54:08 +01:00
fa15a4e6e5
core/common: separate FileMetaVersion for different outputs 2023-11-25 22:13:02 +01:00
4574c06f5d
core/common: remove Clone from ProcessedChunk/ProcessedRegion
These can be rather large, and there is no reason to ever clone them.
2023-11-25 22:11:56 +01:00
74a4fc93d0
world/de: rename Variants enums to Variant 2023-11-25 19:01:36 +01:00
b32bdb4d8e
README.md: mention minimum supported Rust version 2023-11-18 21:15:04 +01:00
196e21718f
minedmap 2.0.1 2023-11-18 19:06:25 +01:00
ec4b1053c5
CHANGELOG.md: update for invalid region handling fix 2023-11-18 19:04:59 +01:00
cc9d2c00b4
Cargo.lock: update 2023-11-18 19:04:59 +01:00
5f2dea6b4f
minedmap-types 0.1.1 2023-11-18 18:59:54 +01:00
a21feceeb6
Merge pull request #33 from neocturne/ignore-broken-regions
Ignore broken regions (+ related cleanup)
2023-11-18 18:57:10 +01:00
412568603e
README.md: minor updates 2023-11-18 18:51:11 +01:00
2c1c8c17ef
Update dependencies 2023-11-18 18:49:25 +01:00
fba9b6cb55
core/region_processor: reenable stat output 2023-10-12 21:49:42 +02:00
506631a18f
core/region_processor: only return available regions
Ignore regions that failed to process and have no old processed data.
2023-10-12 21:49:42 +02:00
fd48f94f16
core/region_processor: make regular processing errors non-fatal again
Never fail because of invalid save files.
2023-10-12 21:49:42 +02:00
09374d755e
core/region_processor: introduce RegionProcessorStatus enum 2023-10-12 20:10:28 +02:00
284892cea6
core/region_processor: make all process_region() error fatal
Less severe errors will be changed not to be passed up to run().
2023-10-12 20:10:28 +02:00
46c04e632f
core/region_processor: temporarily disable stat output 2023-10-12 20:10:28 +02:00
38da1616d5
core/region_processor: rename path to input_path in process_region()
Make the variable names more consistent.
2023-10-12 20:10:28 +02:00
920547f64a
core/tile_mipmapper: use mpsc channels for counters
Make the code a bit easier to understand.
2023-10-12 20:10:28 +02:00
30e5aee09e
Update dependencies 2023-10-12 18:27:49 +02:00
ceb71f035c
minedmap 2.0.0 2023-09-30 16:53:07 +02:00
1559486727
README.md: update for release 2023-09-30 16:17:49 +02:00
d9d7cda5ee
Add CHANGELOG.md 2023-09-30 16:15:56 +02:00
8873ff74c4
Update dependencies 2023-09-30 10:09:04 +02:00
c7145c5c4a
Cargo.toml: add cargo-release config 2023-09-29 22:15:08 +02:00
7d37f6a5d0
Add summary messages 2023-09-20 00:38:19 +02:00
a8eb2da95d
Use tracing for configurable logging 2023-09-19 23:29:05 +02:00
e57ec81d96
ci: strip release binaries 2023-09-17 22:33:58 +02:00
fcd05b5e1c
Revert "ci: add shorter labels for target OS names again"
This reverts commit f898116209.

Let's keep the full target names to stay compatible with cargo-binstall
(and possibly cargo-dist in the future).
2023-09-17 21:45:36 +02:00
f898116209
ci: add shorter labels for target OS names again 2023-09-17 20:01:59 +02:00
92fb2a9ba5
ci: remove unused env GH_TOKEN 2023-09-17 19:58:21 +02:00
63b9f085e1
ci: add Rust build 2023-09-17 19:24:16 +02:00
a153889ce6
Update dependencies 2023-09-17 19:24:16 +02:00
646534c755
Cargo.toml: add workspace readme key
cargo-outdated requires the key to be set to allow inheriting it.
2023-09-17 19:24:16 +02:00
7cc6fca7fe
ci: merge ci.yml into MinedMap.yml, update actions 2023-09-17 19:24:15 +02:00
b5a5c72142
ci: update to actions/checkout@v4 2023-09-17 13:27:47 +02:00
4ef0200804
core: display version number based on git-describe 2023-08-27 14:27:40 +02:00
007c5f72fc
Add Code of Conduct
Add the Contributor Covenant as a Code of Conduct.
2023-08-25 21:43:31 +02:00
d39e35508f
ci: add Rust CI
Check rustfmt/clippy/tests.
2023-08-22 12:31:40 +02:00
14e57d2085
Cargo.toml: add excludes 2023-08-21 22:01:37 +02:00
b0ad1d8765
Cargo.toml: remove redundant default-run 2023-08-21 21:53:22 +02:00
c010ee9194
Prepare for crates.io release 2023-08-21 21:51:01 +02:00
8e369f33fd
Add license and repository info to all crates 2023-08-21 21:50:57 +02:00
4f3b43e4a4
Add missing serde derive feature to Cargo.toml 2023-08-21 17:07:03 +02:00
43dfa6c544
Update dependencies 2023-08-21 15:02:06 +02:00
2544ee9e80
Move nbtdump and regiondump examples to minedmap-nbt crate 2023-08-21 15:01:48 +02:00
810a05ab36
Move nbtdump and regiondump commands to examples
Do not install the development utilities with minedmap
2023-08-21 13:21:18 +02:00
1616f3b2e2
Add --version command line argument 2023-08-21 13:19:00 +02:00
003b48951b
world: fix deserialize of unpopulated 1.18+ sections 2023-08-20 19:11:12 +02:00
42a800e08c
README.md: fix link syntax 2023-08-20 17:57:59 +02:00
0273e7bc08
README.md: update for Rust rewrite 2023-08-20 17:49:37 +02:00
5fc296fc44
Fix binary usage comments 2023-08-20 17:04:55 +02:00
228f31c568
Move resource module to a separate crate
The resource module does not depend on any other part of MinedMap.
2023-08-20 17:00:10 +02:00
248a641035
Restructure crates
Get rid of the arbitrary bin/lib split and instead move as much as
possible into the bin crate, which becomes the main crate again.

The types and NBT handling are moved into separate crates, so they can
be reused by nbtdump and regiondump.
2023-08-20 17:00:10 +02:00
09399f5ae9
Update dependencies 2023-08-20 16:28:20 +02:00
9ec1c03ce1
Only name library crate rather than whole package "minedmap-core" 2023-08-20 14:15:29 +02:00
3cb65ec70e
resource/block_types: update for Minecraft 1.20 2023-08-20 13:42:06 +02:00
204d0d06d5
resource/biomes: add Minecraft 1.20 support
Add the new Cherry Grove biome.
2023-08-20 13:42:04 +02:00
05a8056cbf
Add documentation comments for all items 2023-08-20 11:21:29 +02:00
ba86dc8c06
resource: use serde feature of enumflags2 crate 2023-08-19 16:01:51 +02:00
0842cb4ec2
Rename library crate to minedmap-core
Rustdoc can't deal with a bin and lib crate with the same name.
2023-08-18 19:20:21 +02:00
0542f2ea11
resource/README.md: update for Rust implementation 2023-08-18 18:34:47 +02:00
2a92eefd09
resource/generate.py: fix usage message 2023-08-18 18:34:12 +02:00
427a992897
minedmap/tile_renderer: keep HashSet of populated regions
Rather than attemping to read the file metadata, it's more elegant to
check whether a tile is supposed to be available first.
2023-08-15 20:48:41 +02:00
722fe00d77
minedmap: move inputs to processing steps from run() to new() 2023-08-15 18:45:38 +02:00
6a82fcc9b4
minedmap/tile_renderer: terminate on errors
Only the processing step may fail for individual tiles (when a region is
currently being saved when attempting to process it), the other steps
should never fail.
2023-08-15 18:31:53 +02:00
155171803d
minedmap/tile_renderer: parallel processing 2023-08-15 17:43:58 +02:00
b5980b82af
minedmap/tile_renderer: async region cache
Prepare for sharing the region cache between multiple threads by making
lookup/load async.
2023-08-15 17:42:27 +02:00
dcc9e6e794
minedmap/region_group: add async map functions 2023-08-15 17:39:42 +02:00
78fe1ec50e
minedmap: add support for parallel processing
For now, only RegionProcessor and TileMipmapper are run in parallel.
2023-08-14 22:38:21 +02:00
c1260a63b5
minedmap: separate collection for region list from preprocessing
Preparation for parallel processing, as well as a fix for regions
missing from later steps when the initial processing failed (rather than
using the processed data from a previous run).
2023-08-14 14:27:52 +02:00
d5ac38ed9b
util: split to_flat_coord() and from_flat_coord() out of coord_offset() 2023-08-13 23:01:45 +02:00
4a824680a9
Update dependencies 2023-08-13 12:16:11 +02:00
9c4161f688
util: turn range check into debug_assert!()
More performance optimization.
2023-08-06 12:33:29 +02:00
21035a1f7f
Move coord offset to new util module, make more efficient
div_floor_mod() generates inefficient code. For power-of-2 divisors,
shift and mask can be used instead.
2023-08-06 11:26:08 +02:00
b80d9ee420
minedmap/tile_renderer: make biome HashMap more efficient
Using a u32 instead of a tuple makes the hash calculation faster,
because the fields don't have to be hashed separately.
2023-08-06 00:07:46 +02:00
84bee6d6d9
minedmap: add region cache
Cache the last loaded processed regions.
2023-08-05 20:26:59 +02:00
521e799d4b
minedmap/region_group: make indexing into the neighbor array more uniform
Add a 9th element, so the 3x3 entries can always be indexed the same way,
leading to a slight performance improvement.

The center element is always None.
2023-08-05 18:21:07 +02:00
7b46adf6e7
minedmap: split up big types by adding Boxes
Make these values faster to move around, and optimize their size when
used in Options.
2023-08-05 18:11:39 +02:00
cd1a5e869d
minedmap/region_group: optimize get() implementation 2023-08-05 14:07:55 +02:00
7d231b92ea
Format let-else blocks
Use nightly rustfmt to format let-else.
2023-08-05 10:46:47 +02:00
ab3b273992
Update dependencies 2023-08-04 16:13:08 +02:00
dc68e67a23
minedmap/region_group: clippy fixes 2023-08-04 00:46:58 +02:00
deb33814ee
minedmap/tile_renderer: avoid calling block_color() more often than necessary
Collect all biome indices/weights for a chunk and deduplicate using a
hash map.
2023-08-03 23:07:16 +02:00
fb712cd2f5
Store per-region biome list in IndexSet
Index into the biome list instead of duplicating the biome data for each
coordinate. This reduces the size of the processed data and speeds up
encoding/decoding.
2023-08-03 23:07:16 +02:00
c38f00e411
minedmap/tile_renderer: implement biome smoothing
Rather than the diamond-shaped kernel used by the old implemenation, we
now use the approximation of a Gauss filter. In addition, the kernel
size is decreased, reducing the strength of the blur effect.
2023-08-03 19:06:20 +02:00
b650b096ef
resource: return block color as float vector
Deferring conversion to integers is convenient for biome smoothing.
2023-08-03 18:31:45 +02:00
0a485343a0
resource: do not require Biome to get color for all block types
Only grass, foliage and water have biome-dependent colors.
2023-08-03 18:28:57 +02:00
45171aa26a
minedmap/tile_renderer: factor out block_color_at() from render_chunk()
Also pass in a few more values to prepare for biome smoothing.
2023-08-03 18:12:56 +02:00
8e848394cd
minedmap/tile_renderer: add biome_at() helper
Retrieve the biome at a given coordinate, possibly offset by a number of
blocks.
2023-08-03 18:02:26 +02:00
dee00e7a02
minedmap/tile_renderer: add coord_offset() helper 2023-08-03 18:02:26 +02:00
35bbd167ba
minedmap/tile_renderer: load 3x3 neighbourhoods of regions
Prepare for biome smoothing by always loading neighbouring regions.
2023-08-03 18:02:26 +02:00
a30266f67f
minedmap/tile_renderer: take into account neighboring regions
Biome smoothing uses biome data from neighboring regions. Collect paths
and timestamps from 3x3 region groups.
2023-08-03 17:55:39 +02:00
5a765c3862
minedmap/region_group: add RegionGroup type
A generic array of 3x3 elements.
2023-08-03 17:55:39 +02:00
e39f48d8fe
types: use const generics for more generic coordinate types
Enum support in const generics would be even nicer, but for now the
integer generic is the best Rust can do.
2023-08-02 02:55:13 +02:00
80781c9c20
minedmap: skip generation steps when the inputs are unchanged 2023-07-31 00:23:06 +02:00
6077138292
Add version field to file metadata 2023-07-30 21:48:58 +02:00
4d6644f427
minedmap/tile_mipmapper: store source modified timestamp with mipmapped tiles 2023-07-30 21:38:42 +02:00
628a702fd7
Store source modified timestamp with processed, lightmap and map tiles
Allows to check whether the source is newer than the last update of the
output files.
2023-07-30 21:19:24 +02:00
e18d4cea82
io/fs: do not replace files with unchanged contents
Do not update file timestamps when contents have not actually changed.
2023-07-30 20:31:40 +02:00
cb791f48e9
Update dependencies 2023-07-30 20:03:23 +02:00
c472b61ef7
resource/block_color: actually apply grass color modifiers to grass
Apply the modifiers to grass, not to foliage.
2023-07-04 00:21:27 +02:00
d84e2ca49d
resource/block_color: fix default foliage colors
The code accidentally used the grass colors.
2023-07-03 23:49:36 +02:00
c471f84573
resource/block_color: fix scaling of biome-specific colors
All biome-specific colors (water/foliage/grass) were too bright by a
factor of 255 (i.e., white).
2023-07-03 23:45:00 +02:00
99c4de0b07
Update dependencies 2023-07-03 23:27:59 +02:00
757f6ff166
minedmap: write info.json file with tilemap metadata
With this change, the new minedmap implementation can generate all
necessary data for the frontend to work.
2023-07-02 23:09:14 +02:00
429b7888f6
world/de: add level.dat data structures
We only need the spawn point from level.dat.
2023-07-02 23:08:43 +02:00
f9fc9efe8d
Revert "minedmap: store region list in BTreeSet"
This reverts commit b53d34da3d.

With the change of the mipmapper data structure, we need a conversion
step anyways, so we can keep using the Vec before mipmapping.
2023-07-02 22:19:35 +02:00
b63a18ad6f
tile_mipmapper: store tile coordinates in nested map/set
Use the same data structure as the frontend.
2023-07-02 22:15:25 +02:00
216aa6ceec
minedmap: add mipmapping 2023-07-02 21:32:40 +02:00
86382772c3
minedmap/tile_renderer: print tile path relative to output directory
We will use strings of the same format for mipmap tile paths.
2023-07-02 20:05:57 +02:00
b1f7f759f1
minedmap: introduce generic tile path function, pass mipmap level 2023-07-02 18:58:08 +02:00
b53d34da3d
minedmap: store region list in BTreeSet
We want to have a sorted list in the end anyways to make metadata
generation deterministic, and we don't have to worry about deduplication
of coordinates when generating mipmap tile lists.
2023-07-02 18:16:35 +02:00
e5c96ecb99
minedmap: replace TileCoords type alias with a struct 2023-07-02 18:08:54 +02:00
007ce010d4
minedmap: rename RegionCoords to TileCoords
With mipmapping, coords will not always correspond to regions anymore.
2023-07-02 15:11:16 +02:00
dede21806c
Update dependencies 2023-07-01 23:05:39 +02:00
17a02dc74c
minedmap: use fs helpers 2023-06-01 23:14:35 +02:00
587db0464c
io/storage: use fs::create_with_tmpfile() helper 2023-06-01 23:10:47 +02:00
1abb260997
io/fs: add new module with filesystem access helpers
Wrappers around std::fs with error messages, as well as a
create_with_tmpfile() helper.
2023-06-01 22:59:19 +02:00
59e919e9ea
Update dependencies 2023-06-01 22:53:39 +02:00
42cb342749
world/layer: avoid iproduct-based iterator in inner loop
iproduct iterators are fairly slow. As the iterator implementation is
now unused, it is removed as well.
2023-05-10 00:48:53 +02:00
31eb92864c
Split up BlockInfo type
By storing block types and depth values separately, the processed world
data can be compressed better, decreasing data size by ~10%.
2023-05-06 00:52:40 +02:00
0b392d7a3a
world/layer: move more top_layer() logic into LayerEntry::fill() 2023-05-06 00:33:34 +02:00
0437ec70b6
world/layer: introduce LayerEntry struct 2023-05-06 00:33:13 +02:00
2d5eec13c2
world/layer: move Option into BlockInfo struct
Preparation for top_layer() cleanup.
2023-05-05 23:29:57 +02:00
263fc6d6ce
world/layer: remove unused y coordinate from BlockInfo 2023-05-05 23:15:34 +02:00
924ee01f91
world/layer: introduce LayerData struct to fix clippy type complexity warning 2023-05-05 23:08:39 +02:00
b66bdf5ce1
resource: implement water/foliage/grass color computation 2023-05-04 23:18:04 +02:00
25710bb1ed
Move block_color() to new module 2023-05-04 22:18:15 +02:00
e801631561
Update dependencies 2023-05-04 22:04:20 +02:00
2dd9283d95
minedmap: move some internal functions out of impl blocks 2023-05-01 17:51:38 +02:00
1a5e8894fe
minedmap: split up main module 2023-04-30 11:07:47 +02:00
61fb23b94b
Move main source file to bin subdirectory 2023-04-30 10:46:56 +02:00
2e05998b48
Update depenencies 2023-04-30 08:22:59 +02:00
d79377ad6b
main: pass biome into block_color()
No biome edge smoothing yet.
2023-04-10 20:58:07 +02:00
0d81dfa35b
main: store biome data in processed data files 2023-04-09 23:20:20 +02:00
e912f60ba3
world/layer: add biome data to returned layer 2023-04-09 22:56:02 +02:00
0d2c99dacf
world/chunk: include biomes in section iterator item 2023-04-08 21:20:00 +02:00
ddc515a9d7
world/section: implement biome_at() 2023-04-08 21:20:00 +02:00
b93e613152
types: add LayerBlockCoords::offset() helper 2023-04-08 21:20:00 +02:00
aa2edc3d13
world/section: move biome_types field out of BiomesV0 enum 2023-04-08 21:20:00 +02:00
3ea93296d7
Update dependencies 2023-04-08 21:19:55 +02:00
c6843cfb9c
world: resolve biome palette entries for v1.18 biome data 2023-04-07 09:38:31 +02:00
b8b0e0627f
world: correctly name BiomesV1_18 2023-04-07 09:37:02 +02:00
e117c76937 world: pass biome data into chunk/section structures 2023-04-07 09:34:56 +02:00
524b1b1913 resource: add biome data 2023-04-07 09:34:56 +02:00
e462e17ee2
resource: rename BlockColor to Color
The same struct will be used for biomes.
2023-04-05 20:05:59 +02:00
f2b7808e84
world: distinguish pre-v1.18 biome data structures 2023-04-02 18:17:13 +02:00
e62e19796c
Update dependencies 2023-04-01 16:15:29 +02:00
bcec704d27
main: add height-based color modification
Modify terrain color depending on the height to give a sense of
elevation.
2023-03-04 21:45:59 +01:00
46802116d9
resource: change BlockColor into an array 2023-03-04 21:32:15 +01:00
fbf212b55f
main: render light map 2023-03-04 20:36:25 +01:00
116e7e5fb6
world/layer: collect block light data with top layer 2023-03-04 20:36:05 +01:00
4fd316f3fc
world/layer: return None from top_layer() for empty chunks
Allow ignoring these chunks for the light map as well.
2023-03-04 20:33:10 +01:00
202364bfca
main: factor out overlay_chunk() function to build region images 2023-03-04 20:30:39 +01:00
51602d5fc1 world/chunk: add block light to section iterator items 2023-03-04 17:38:56 +01:00
482471492c world/chunk: store BlockLight data in section map 2023-03-04 17:38:56 +01:00
b4eb0d39f9 world/section: add BlockLight type 2023-03-04 17:38:56 +01:00
66f8d155f5
world/section: reorder type definitions 2023-03-04 16:58:52 +01:00
47dc3795a3
world: introduce SectionIterItem struct 2023-03-04 16:57:59 +01:00
ed422be451
TODO.md: update
- fastnbt decode doesn't really appear in profiles, so optimizing it
  using borrowed types is moot
- What does appear prominently in the profile though is top_layer() and
  block_at()
2023-03-04 11:39:47 +01:00
56573640fd
world/section: prefer slice references to Vec and fastnbt types
Makes the code a bit nicer and saves repeated deref calls into fastnbt.
2023-03-04 00:36:58 +01:00
a4b726992a
io/storage: compress/decompress data in bulk
The bulk API is signifiantly faster than the stream API.
2023-03-03 20:58:35 +01:00
fa04e3e94a
main: actually render tiles 2023-03-03 20:41:08 +01:00
26555f1745
src/world/layer: make BlockInfo fields public
There were left private by accident.
2023-03-03 20:36:09 +01:00
e6e1f55fe9
Use zlib-ng backend for flate2 by default
Still allow disabling the feature to avoid the cmake dependency.
2023-03-03 20:00:03 +01:00
d5406851b4
main: load processed regions from render_tile() 2023-03-03 19:45:24 +01:00
89f35024b7
main: introduce ProcessedRegion type alias 2023-03-03 19:45:00 +01:00
e0467de080
main: rename save_region() argument from region to coords
For consistency.
2023-03-03 19:22:45 +01:00
657d8af8a7
io/storage: add read() function 2023-03-03 19:20:56 +01:00
dda81546de
main: add map_path() method to Config struct 2023-03-03 19:13:15 +01:00
51a0e178b1
main: do not rebuild region filename from coords 2023-03-03 19:06:54 +01:00
2ccb282f6f
main: rename Paths to Config 2023-03-03 19:02:55 +01:00
ec09afcf15
main: fully print error messages 2023-03-02 01:23:24 +01:00
1d4c7a86ff
world/layer: replace iproduct!() with nested loops
iproduct!() is slow for more than 2 parameters.
2023-03-02 01:00:55 +01:00
ed5fb9a6cf
resource: remove LEGACY_BLOCK_TYPES reexport
As the array is only used internally now, we can also remove the
"minecraft:" prefix from the entries.

Signed-off-by: Matthias Schiffer <mschiffer@universe-factory.net>
2023-03-02 00:41:39 +01:00
04bc3e5d53
world/section: perform palette lookups early
Look up block types for all palette entries once during section
construction, rather than on block_at(), reducing the number of HashMap
lookups by ~1000.
2023-03-02 00:37:25 +01:00
5c82b80924
world/section: replace block_id_at() method with block_at()
Directly return a BlockType, preparing for early lookup of palettes.
2023-03-02 00:31:34 +01:00
f2ab424590
world: store BlockTypes in section types
Prepare for sections returning BlockType data directly instead of block IDs.
2023-03-02 00:24:59 +01:00
89af4aaee2
resource: extend BlockTypes with legacy block type data
Allow to directly map from id/data values to BlockType.
2023-03-02 00:24:56 +01:00
4c2fd6c1a9
resource: rename BlockTypeMap to BlockTypes
The block_types() function it turned into a Default implementation.
2023-03-01 23:59:27 +01:00
95e4e45974
resource: remove "minecraft:" prefix from hash keys
Shorter keys mean less data to hash.
2023-03-01 22:03:09 +01:00
483cdf48c8
resource: update block_types with Minecraft 1.19.2 data
An older Minecraft version had been used to generate the data for the
Rust rewrite by accident.
2023-03-01 22:01:30 +01:00
cbbc6d8f35
resource: make BlockTypeMap return BlockType without reference 2023-03-01 21:58:29 +01:00
0673c89bd8
main: add stub tile renderer struct
This only prints messages so far.
2023-03-01 21:45:38 +01:00
73b13c1afb
main: move path handling to separate struct 2023-03-01 21:02:24 +01:00
c5ca1d9821
main: pass region_dir to RegionProcessor::new(), consume when processing 2023-03-01 19:27:09 +01:00
a2ba7e4738
main: build processed_dir path outside of RegionProcessor::new() 2023-03-01 19:17:50 +01:00
ea604b88f2
main: rename regiondir variable to region_dir
For consistency with other _dir variables.
2023-03-01 19:17:04 +01:00
ca6afa0cf9
main: return list of region coordinates from process_region_dir() 2023-03-01 19:16:09 +01:00
194715ad09
main: store processed region data 2023-03-01 00:13:06 +01:00
e02a1fc0c9
main: collect processed chunks in array 2023-03-01 00:11:42 +01:00
18cecb947e
main: factor out process_chunk() method 2023-03-01 00:09:11 +01:00
d77dcce778
main: print message when processing region 2023-03-01 00:06:48 +01:00
447a9482fe
main: create output directory for processed data files 2023-03-01 00:02:23 +01:00
04ab8c321f
main: fix formatting
rustfmt still doesn't handle let-else...
2023-03-01 00:00:40 +01:00
b68f04496c
main: introduce RegionCoords type alias 2023-02-28 23:57:48 +01:00
768ab13205
main: rename savedir argument to input_dir 2023-02-28 23:54:21 +01:00
21fd3a42ca
io: add storage helper
Use bincode+zstd to serialize data structures for temporary storage.
2023-02-28 21:59:45 +01:00
f47b38b2ca
world/layer: return boxed BlockInfoArray from top_layer()
Avoid copying around large structures, and allow creating
arrays of BlockInfoArrays without overflowing the stack.
2023-02-27 19:17:17 +01:00
fd0c9fbf1b
main: improve process_region() error handling 2023-02-26 01:11:59 +01:00
b2d849081d
io/region: allow stopping foreach_chunk early
Errors returned from the callback stop the loop early.
2023-02-26 01:01:37 +01:00
551056803d
Add Serialize/Deserialize impls to various types
We want to be able to store this data to disk temporarily.
2023-02-26 00:46:50 +01:00
2d0f2fb865
main: iterate over regions of a Minecraft save dir 2023-02-25 17:46:00 +01:00
f3a6711458
Update dependencies 2023-02-25 13:30:58 +01:00
f48aa877d2
world: implement top_layer function
Implement one of the core functions of MinedMap: finding the topmost
visible block at each coordinate.
2023-02-18 11:51:24 +01:00
2530a557a9
types: fix dangling doc reference 2023-02-18 11:08:45 +01:00
decb5c67a6
world/section: to not use u128 type
Getting the block index for unaligned blocks can be done just fine with
only u64.
2023-02-18 10:57:11 +01:00
3ef48783b7
types: introduce LayerBlockArray
A LayerBlockArray is a generic array of data stored for each block of a
chunk layer. It provides the same methods as a ChunkArray.
2023-02-15 00:49:07 +01:00
789a4002c7
types: add Clone + Debug to ChunkArray iterators
Added for consistency with LayerBlockArray.
2023-02-15 00:48:30 +01:00
921a218d56
types: split BlockCoords into SectionBlockCoords and LayerBlockCoords 2023-02-15 00:35:18 +01:00
339e0b6a05
resource: add BlockType::is() method to check for flags 2023-02-15 00:34:59 +01:00
2a941e5283
resource: add BlockTypeMap type for convenience 2023-02-15 00:34:59 +01:00
db289bc077
resource: rename BlockFlags to BlockFlag
The enum only holds a single flag at a time.
2023-02-15 00:34:59 +01:00
102baa9197
types: add iter() associated function to coordinate newtypes 2023-02-15 00:34:53 +01:00
9f8446885e
treewide: remove get_ prefix from function names
Follow the conventions of the std library.
2023-02-12 23:36:58 +01:00
96736bd7ed
Replace DivRem trait with num-integer crate 2023-02-12 23:08:53 +01:00
1049aad03d
Update dependencies 2023-02-12 23:03:58 +01:00
9146c06bec
treewide: rename "old" data structures to "v0"
"v0" doesn't refer to a specific version of Minecraft; it is just the
oldest data format supported by MinedMap.
2023-02-12 23:03:58 +01:00
493608ebc8
treewide: add more documentation 2023-02-12 23:03:58 +01:00
5ee114ed0a
types: slightly simplify DivRev trait 2023-02-12 23:03:58 +01:00
c34f531546
world/section: temporarily rename PaletteSectionBiomes fields
These fields are unused for now. Add an underscore to supress linter
warnings.
2023-02-12 23:03:58 +01:00
65c39ea153
types: make CHUNKS_PER_REGION and BLOCKS_PER_CHUNK usize
Having these as usize is more convenient.
2023-02-12 23:03:58 +01:00
b918ff6106
world/section: add method to get block ID at coordinate 2023-02-12 23:03:58 +01:00
3cee4a4247
resource: port legacy block type mapping from old implementation 2023-02-12 23:03:57 +01:00
8add7679a3
types: add Eq to coordinate types 2023-02-12 23:03:57 +01:00
c130f3cdae
world: add section iterator
An (empty for now) trait is introduced to abstract over the two section
types.
2023-02-12 23:03:57 +01:00
2d782f25b1
world: add types to wrap common parts of section data 2023-02-12 23:03:57 +01:00
b040a635ed
types: add types for block and section coordinates 2023-02-12 23:03:57 +01:00
357ef46731
types: add helper for division + remainder
We frequently require these operations together when converting between
bit and byte offsets, or similar purposes.
2023-02-12 23:03:57 +01:00
a174d627cd
Cargo.toml: change license to MIT
The copy of the license text in the repository is dropped.
2023-02-12 23:03:12 +01:00
718ecf5909
resource: update block type code generator for Rust 2023-02-07 22:18:44 +01:00
6379472282
world/de: new module for deserialization data structures
The new structures contain only the fields that MinedMap needs.
2023-02-07 18:32:01 +01:00
8c34f74952
io/region: return sorted Vec instead of HashMap from parse_header()
Instead of counting and looking up indices in the HashMap, we can just
iterate over all offsets in the returned Vec.
2023-01-30 19:08:51 +01:00
6186a0d916
io/region: accept incomplete block for final chunk 2023-01-30 00:45:05 +01:00
92a9bb3bb3
io/region: add chunk coords to error descriptions 2023-01-29 17:08:39 +01:00
1d126ba771
io/region: remove error handling for impossible cases from decode_chunk() 2023-01-29 17:04:28 +01:00
7c7e36f6be
io/region: avoid panic for invalid chunk lengths 2023-01-29 00:54:18 +01:00
da8ac506d9
io: remove a few unnecessary [..] for Vec -> slice deref 2023-01-28 01:15:00 +01:00
3cdafa7be9
Fix clippy warnings 2023-01-27 23:01:01 +01:00
daa188eb1d
io/region: use ChunkArray to parse region header
A layout of a ChunkArray::<u32> matches the binary format of the region
header.
2023-01-27 22:54:08 +01:00
28b22ce423
Introduce ChunkArray type
A generic array for per-chunk data, indexed by ChunkCoords.
2023-01-27 22:05:49 +01:00
48e03aa266
Introduce ChunkCoords type 2023-01-27 22:01:41 +01:00
0d8b989c10
io/region: remove count variable from Region::foreach_chunk() 2023-01-25 21:47:17 +01:00
5e96be3fda
Refactor logic from new dump tools into library crate 2023-01-25 21:42:16 +01:00
5a364e2434 Add new regiondump implemention 2023-01-25 21:41:34 +01:00
ddd78079dc Add new nbtdump util 2023-01-25 03:00:07 +01:00
40105aaccd Initial Rust setup 2023-01-25 00:06:27 +01:00
4b83f6376f Remove C++ implementation 2023-01-25 00:06:27 +01:00
c7e04649ff
Resource: Biome: add 1.19 biomes
Add the new biome temperature and rainfall values, as found in
decompiled Minecraft code:

- Deep Dark uses the same the values as plains
- Mangrove Swamp has the same temperature/rainfall as regular Swamp, but a
  different water color
2022-10-16 13:14:31 +02:00
4ce08a7d5d
workflows: replace deprecated set-output 2022-10-16 12:49:13 +02:00
c43c185d9b
workflows: update checkout/upload actions 2022-10-16 12:44:05 +02:00
6605483b4d
workflows: force tag update to get version
When building from a tag, Github will create a lightweight tag in place
of the proper one when fetching, breaking `git describe`. Force an update
of all tags to avoid this.
2022-10-16 12:24:48 +02:00
6125b1b027
README.md: add 1.19 support 2022-10-16 11:11:09 +02:00
Andrew Cox
600a20cde6
Update to compile on Mac OS (#26) 2022-07-01 23:27:58 +02:00
71fb865112
Merge pull request #24 from saadbruno/1.19
Add Minecraft 1.19 support
2022-06-25 21:20:21 +02:00
a6a1deaff5
Merge pull request #25 from saadbruno/gitignore-update
Quality of life updates to Gitignore
2022-06-25 16:29:34 +02:00
Bruno Saad
74bd6205b0 updates the gitignore to include common used directories in the documentation 2022-06-07 22:43:02 +00:00
Bruno Saad
7bba6d0863 Adding new blocks for 1.19 2022-06-07 16:48:43 +00:00
fbef307c90
UniqueCPtr: add doc comment
Explain what a UniqueCPtr is.
2022-02-09 21:26:12 +01:00
3160b59cdd
README.md: add 1.18 support 2021-12-22 16:08:56 +01:00
429e449c64
ci: add Win32 build 2021-12-21 23:57:50 +01:00
056cee8585
build: replace FindPkgConfig hack with BUILD_STATIC variable set by CI toolchain file 2021-12-21 23:35:00 +01:00
7c13d850b7
ci: add viewer artifact 2021-12-19 23:22:49 +01:00
00b93c507d
ci: add GitHub actions for Linux and Windows builds
We build a "static" binary for Windows now to avoid distributing the
dependencies as DLLs.
2021-12-19 23:19:49 +01:00
37340926f4
MinedMap: fix warning for unused fscanf result 2021-12-19 23:11:21 +01:00
fac2e4c8da
World: Chunk: add support for unordered sections
MC1.18 doesn't always store sections sorted by their Y value, possibly
related to "optimize world".
2021-12-12 12:18:18 +01:00
a2fe33ef1f
World: Section: add support for Int tag section Y values
For some reason, MC1.18 sometimes uses Int instead of Byte tags for
section Y values after "optimize world". Add support for this (but still
only accept values that fit in a int8_t).
2021-12-12 12:15:31 +01:00
d18e004743
World: Section: fix biome palette size check
We were accidentally checking the block type palette size.
2021-12-12 10:28:17 +01:00
73dfecf379
Resource: BlockType: update colors with MC1.18 texture data 2021-12-11 22:50:24 +01:00
baa20494bf
World: Section: implement new section-based biome data format 2021-12-11 22:50:24 +01:00
76e5d322b1
Resource: Biome: add biome palette name mapping
For now we will continue to use the numerical biome IDs internally, as
our biome storage format is based on these IDs.
2021-12-11 22:50:23 +01:00
aacea26716
World: add support for negative Y coordinates and MC 1.18 chunks
MC 1.18 biomes are not handled yet.
2021-12-11 22:50:23 +01:00
9ee1007ade
World: Chunk: use enum to store biome data format 2021-12-11 22:50:23 +01:00
21966252ea
World: ChunkData: make getRoot() return shared pointer reference 2021-12-11 22:50:23 +01:00
16a1258f08
World: Chunk: remove unnecessary level class member 2021-12-11 22:50:23 +01:00
0f6c467566
World: Chunk: replace division/modulo with shift/mask for height values
Preparation for negative y coordinates.
2021-12-11 22:50:23 +01:00
457e993c92
Introduce separate types for block/section/chunk indices
Newtypes are cumbersome in C++, so this is mostly documentation for now.

Also replace lots of questionable uses of size_t with int or types with
explicit width and add a few comments for constants.
2021-12-11 22:50:23 +01:00
d4be401bcd
Resource: Biome: fix integer underflow
Explicitly cast height value to signed integer to fix incorrect biome color
values below a height of 64.
2021-12-11 22:28:22 +01:00
fd4773c8ca
treewide: replace license headers with SPDX-License-Identifier 2021-11-17 12:07:56 +01:00
38d8bd0738
Chunk: replace withDepth argument with extensible flags field 2021-08-24 21:03:18 +02:00
fdce587c74
build: update to modern CMake, use pkg-config for zlib and libpng
Starting with CMake 3.7, we can avoid dealing with individual variables
for include dirs, libraries, ... for dependencies found using
pkg-config, and instead just reference them using an imported target.
2021-08-24 13:14:48 +02:00
5263538a29
README.md: add 1.17 support 2021-06-26 16:43:06 +02:00
73e5c12cde
Rename .inc files to .inc.cpp for better language detection by editors and Github 2021-06-25 19:43:14 +02:00
bfc9fcafb9
Merge pull request #17 from NeoRaider/mc1.17
Add Minecraft 1.17 support
2021-06-25 19:41:00 +02:00
af1afc3315 Biome: add Dripstone Caves and Lush Caves
Temperature and downfall values were taken from the Caves & Cliffs: Part II
Preview datapack.
2021-06-24 19:28:25 +02:00
0a42605a2b Add Minecraft 1.17 block types, update all color values 2021-06-24 19:28:25 +02:00
73343dede2 resource: add README.md 2021-06-24 19:28:25 +02:00
5c55ce8140
resource: add blocklist tool to list and diff the supported block IDs of a Minecraft version 2021-06-23 21:34:45 +02:00
9ac3b00b6e
Make region iteration more efficient for sparse worlds
Do not iterate over each region coordinate in the bounding box.

Closes #12
2021-02-13 11:06:30 +01:00
c06af49068
Info: restructure mipmap level data structure 2021-02-13 11:06:30 +01:00
cbc4a946c6
Info: rename mipmap "info" key to "bounds" 2021-02-13 11:06:30 +01:00
d2802b73f5
Info: make tile existence map mor efficient for sparse worlds
Explicitly list the coordinates where tiles exist instead of including a
full array with the size of the bounding box.
2021-02-13 11:06:21 +01:00
0ebc895ec0
Info: print concise JSON
Reduce size by removing pretty-printing
2021-02-13 10:20:19 +01:00
07e039d442
CMakeLists.txt: update CMake version requirement to remove future compat warning 2021-02-13 10:20:19 +01:00
67f4324441
Merge pull request #10 from AaronWebster/patch-1
Change BlockType from struct to class
2020-09-17 07:47:29 +02:00
Aaron Webster
367078178f
Change BlockType from struct to class
Fixes the -Wmismatched-tags compilation warning.
2020-09-16 18:14:09 -07:00
bc8e9c5cb3
README.md: add 1.16 support 2020-07-04 18:04:48 +02:00
997a4fb24e
docs: update screenshot 2020-07-04 18:04:14 +02:00
d33fa1429d
viewer: use === instead of == where appropriate 2020-06-28 17:27:22 +02:00
e5a15beddb
viewer: fix maxNativeZoom in Retina mode
Fixes: #8
2020-06-28 17:24:05 +02:00
6a160142ed
viewer: set image-rendering to pixelated
Chrome smooths the image when using crisp-edges.

crisp-edges is still set as a fallback for browers that don't support
pixelated (including Firefox).

Fixes: #7
2020-06-28 09:48:17 +02:00
3ec83c8cf8
Compare file contents with old data before writing
Do not writeoutput files when their content hasn't changed. This saves
time for incremental generation (as compressing a PNG takes much longer
than decompressing it) and avoids modification timestamp updates that
might trigger subsequent generation steps.
2020-06-20 15:01:52 +02:00
ef69a34034
PNG: export format byte count 2020-06-20 15:01:09 +02:00
98e21236d9
Do not generate light data for invisible blocks 2020-06-20 14:48:26 +02:00
3c837bb92e
viewer: update Leaflet to 1.6.0 2020-06-20 14:17:34 +02:00
03ae9cf302
Exclude unpopulated chunks from biome averaging
Avoids weirdly colored stripes at the edges of the map.
2020-06-20 12:17:24 +02:00
eb831608e0
Add smooth transitions between biomes
Simliar to the way biome transitions are rendered in Minecraft itself,
add smoothing by averaging biome-based colors over all biome values with
distance ≤3 (1-norm).
2020-06-20 12:15:32 +02:00
623d70ec9e
More preparation for biome smoothing 2020-06-20 11:40:29 +02:00
19ef022a67
Biome: return FloatColor for block colors 2020-06-20 11:40:29 +02:00
50f798f89e
Do not store biome in block structure
Use biome data from biome map.
2020-06-20 11:40:29 +02:00
a3cf5edda4
Read biome maps when iterating regions 2020-06-20 11:40:29 +02:00
072d664199
Separate top layer search from block data retrieval 2020-06-20 11:40:29 +02:00
569d0f1198
Pass region indices as int instead of size_t
Region indices can be negative.
2020-06-20 11:40:29 +02:00
4f7663f82f
Merge doRegion{,Biome}() into makeBiome()/makeMap() 2020-06-20 11:40:27 +02:00
2f0d1892f4
Split getModTime() out of checkRegion() 2020-06-20 00:58:08 +02:00
f539d668eb
Collect biome data
We store the biome data as grayscale images, as biome indices are
represented as bytes and we get PNG compression for free.
2020-06-20 00:58:08 +02:00
dc97c4a8c1
PNG: add support for grayscale images without alpha 2020-06-20 00:58:08 +02:00
4f49434dbf
Clean up doLevel() 2020-06-19 20:06:44 +02:00
0245c5c926
Add message before updating mipmaps 2020-06-19 19:09:40 +02:00
5946d19629
Biome: add biome-specific water colors
Also fix default and swamp water colors.
2020-06-19 01:21:35 +02:00
685a4959f1
Add correct foliage colors for birch and spruce 2020-06-19 00:00:30 +02:00
ef4b6eac02
BlockType: replace list of booleans with flags bitfield 2020-06-18 23:38:21 +02:00
446e74791f
Add Minecraft 1.16 biomes
These are Nether biomes. We still add them for completeness.
2020-06-18 23:18:11 +02:00
985eb18e52
Update temperature values for 1.15 ocean biomes 2020-06-18 23:11:34 +02:00
a47b34de0d
Add block types for Minecraft 1.16 (rc1) 2020-06-18 22:57:38 +02:00
7a99a19998
Section: implement new Minecraft 1.16 block state format 2020-06-18 22:57:38 +02:00
73b8b677da
resource: format blocks.json using jq --tab --sort-keys 2020-06-18 19:03:22 +02:00
11f805821e
resource: remove redundant honeycomb_block override 2020-06-18 18:57:03 +02:00
58f19e50b2
Region: allow the last 4096 byte block to be incomplete 2019-12-14 23:19:09 +01:00
f0c08f78e0
GZip: improve file open error message 2019-12-14 22:19:34 +01:00
d9af9cbc9d
Catch normal runtime errors instead of exiting with SIGABRT 2019-12-14 22:16:41 +01:00
228 changed files with 22663 additions and 5908 deletions

4
.dockerignore Normal file
View file

@ -0,0 +1,4 @@
*
!/Cargo.*
!/src
!/crates

238
.github/workflows/MinedMap.yml vendored Normal file
View file

@ -0,0 +1,238 @@
name: 'MinedMap'
on:
push:
branches:
- 'main'
tags:
- 'v*'
pull_request:
branches:
- 'main'
workflow_dispatch: {}
env:
RUSTFLAGS: -Dwarnings
RUSTDOCFLAGS: -Dwarnings
jobs:
viewer:
runs-on: 'ubuntu-latest'
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: '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@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
View file

@ -1,4 +1,5 @@
*~
/viewer/data
.idea/
cmake-build-debug/
/resource/data
/resource/colors.json
/target

207
CHANGELOG.md Normal file
View 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

View file

@ -1,9 +0,0 @@
cmake_minimum_required(VERSION 2.8.4)
project(MINEDMAP CXX)
find_package(PNG REQUIRED)
find_package(ZLIB REQUIRED)
add_subdirectory(src)

128
CODE_OF_CONDUCT.md Normal file
View 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

File diff suppressed because it is too large Load diff

73
Cargo.toml Normal file
View 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
View 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
View file

@ -1,22 +1,21 @@
Copyright (c) 2015-2019, 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
View file

@ -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.15 (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
![Screenshot](docs/images/MinedMap.png)
![Screenshot](https://raw.githubusercontent.com/neocturne/MinedMap/997a4fb24e89d2cd3c671d77eafaa47084d14304/docs/images/MinedMap.png)
## 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:
![Sign screenshot](https://raw.githubusercontent.com/neocturne/MinedMap/e5d9c813ba3118d04dc7e52e3dc6f48808a69120/docs/images/signs.png)
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
View file

@ -0,0 +1,6 @@
# TODO
## Optimizations
- To check:
- Bulk `block_at()`, possibly other `top_layer()` improvements

View 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"]

View 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
View 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"] }

View 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(())
}

View 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
View 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
View 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
View 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))
}

View 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"

View 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]),
),
];

View 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)
}

File diff suppressed because it is too large Load diff

View 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",
}
}

File diff suppressed because it is too large Load diff

344
crates/resource/src/lib.rs Normal file
View 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
View 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
View 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
View 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: {}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 446 KiB

After

Width:  |  Height:  |  Size: 476 KiB

BIN
docs/images/signs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

87
resource/README.md Normal file
View file

@ -0,0 +1,87 @@
# Resource management
## Scripts
The following scripts can be found in the `resource` directory of this Git
repository. Python 3.8 should be sufficient, older versions may or may not
work.
- `blocklist.py`: Lists all supported block IDs of an unpacked Minecraft JAR, or diffs the ID lists
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 `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 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
to do so. On Linux the downloaded JAR archive can be found at
`~/.minecraft/versions/`.
2. Unpack both versions to different directories. The next part assumes that
the unpacked data is stored in `resource/data/old` and `resource/data/new`;
using the respective Minecraft version numbers instead of `old`
and `new` is advisable.
3. Check the added and removed block types using `blocklist.py`:
```sh
./blocklist.py diff data/old data/new
```
4. Append all new block types to `blocks.json`. The following command can be
used to generate the basic JSON structure:
```sh
./blocklist.py added data/old data/new | jq -R -n -S --tab '[inputs] | map({key: ., value: {}}) | from_entries'
```
5. Edit `blocks.json` until the following command passes without errors:
```sh
./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 (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.
6. When `colors.json` builds successfully, use the following command to sort
`blocks.json` by block ID:
```sh
jq --tab -S < blocks.json > blocks.json.new && mv blocks.json.new blocks.json
```
Then regenerate `colors.json` one last time, so it is sorted as well.
7. Update the source code with the new block colors:
```sh
./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
View 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)

69
resource/blocklist.py Executable file
View file

@ -0,0 +1,69 @@
#!/usr/bin/env python3
import argparse
import os
import sys
def blocklist(path):
blockpath = os.path.join(path, 'assets', 'minecraft', 'blockstates')
try:
return map(
lambda filename: filename[:-5],
filter(
lambda filename: filename.endswith('.json'),
os.listdir(blockpath),
),
)
except FileNotFoundError:
sys.exit('''
Path '{}' not found.
Please pass a directory containing the unpacked contents of a JAR of a recent Minecraft version.
'''.strip().format(blockpath))
def print_blocklist(path):
for block in sorted(blocklist(path)):
print(block)
def diff_blocklist(old, new, cmd):
blocks_old = set(blocklist(old))
blocks_new = set(blocklist(new))
diff = sorted(blocks_old.symmetric_difference(blocks_new))
for block in diff:
if block in blocks_old:
if cmd == 'removed':
print(block)
elif cmd == 'diff':
print('-', block)
else:
if cmd == 'added':
print(block)
elif cmd == 'diff':
print('+', block)
parser = argparse.ArgumentParser(description='List block IDs of an unpacked Minecraft JAR, or diff two versions')
subparsers = parser.add_subparsers(dest='cmd', metavar='COMMAND', required=True)
parse_list = subparsers.add_parser('list', help='list supported blocks')
parse_list.add_argument('DIR')
parse_added = subparsers.add_parser('added', help='list added blocks')
parse_added.add_argument('OLD')
parse_added.add_argument('NEW')
parse_removed = subparsers.add_parser('removed', help='list removed blocks')
parse_removed.add_argument('OLD')
parse_removed.add_argument('NEW')
parse_removed = subparsers.add_parser('diff', help='diff lists of supported blocks')
parse_removed.add_argument('OLD')
parse_removed.add_argument('NEW')
args = parser.parse_args()
if args.cmd == 'list':
print_blocklist(args.DIR)
else:
diff_blocklist(args.OLD, args.NEW, args.cmd)

File diff suppressed because it is too large Load diff

View file

@ -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,26 +35,40 @@ 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},
'opaque': False,
'grass': False,
'foliage': False,
'blue': False,
'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]['blue'] = info.get('blue', 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)

View file

@ -6,7 +6,7 @@ import sys
if len(sys.argv) != 3:
sys.exit('Usage: extract.py <colors.json> <BlockType.inc>')
sys.exit('Usage: generate.py <colors.json> <block_types.rs>')
with open(sys.argv[1]) as f:
colors = json.load(f)
@ -14,14 +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():
print('{"%s", {%s, %s, %s, %s, {%u, %u, %u}}},' % (
name,
['false', 'true'][info['opaque']],
['false', 'true'][info['grass']],
['false', 'true'][info['foliage']],
['false', 'true'][info['blue']],
flags = []
if info['opaque']:
flags.append('Opaque')
if info['grass']:
flags.append('Grass')
if info['foliage']:
flags.append('Foliage')
if info['birch']:
flags.append('Birch')
if info['spruce']:
flags.append('Spruce')
if info['water']:
flags.append('Water')
if info['wall_sign']:
flags.append('WallSign')
flags = 'make_bitflags!(BlockFlag::{' + '|'.join(flags) + '})'
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
View 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
View file

@ -0,0 +1 @@
hard_tabs = true

View file

@ -1,90 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#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));
}
};
}

View file

@ -1,40 +0,0 @@
include_directories(${ZLIB_INCLUDE_DIRS} ${PNG_INCLUDE_DIRS})
add_definitions(${PNG_DEFINITIONS})
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
)
set_target_properties(MinedMap PROPERTIES COMPILE_FLAGS "-std=c++11 -Wall")
target_link_libraries(MinedMap ${ZLIB_LIBRARIES} ${PNG_LIBRARIES})
add_executable(nbtdump
nbtdump.cpp
GZip.cpp
NBT/Tag.cpp
)
set_target_properties(nbtdump PROPERTIES COMPILE_FLAGS "-std=c++11 -Wall")
target_link_libraries(nbtdump ${ZLIB_LIBRARIES})
add_executable(regiondump
regiondump.cpp
GZip.cpp
NBT/Tag.cpp
World/ChunkData.cpp
World/Region.cpp
)
set_target_properties(regiondump PROPERTIES COMPILE_FLAGS "-std=c++11 -Wall")
target_link_libraries(regiondump ${ZLIB_LIBRARIES})
install(TARGETS MinedMap RUNTIME DESTINATION bin)

View file

@ -1,65 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#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(), "unable to open GZip file");
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;
}
}

View file

@ -1,37 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#pragma once
#include <cstdint>
#include <vector>
namespace MinedMap {
std::vector<uint8_t> readGZip(const char *filename);
}

View file

@ -1,101 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#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, "{\n");
std::fprintf(f, " \"mipmaps\" : [\n");
for (size_t level = 0; level < regions.size(); level++) {
int minX, maxX, minZ, maxZ;
std::tie(minX, maxX, minZ, maxZ) = getBounds(level);
std::fprintf(f, " {\n");
std::fprintf(f, " \"info\" : {\n");
std::fprintf(f, " \"minX\" : %i,\n", minX);
std::fprintf(f, " \"maxX\" : %i,\n", maxX);
std::fprintf(f, " \"minZ\" : %i,\n", minZ);
std::fprintf(f, " \"maxZ\" : %i\n", maxZ);
std::fprintf(f, " },\n");
std::fprintf(f, " \"regions\" : [\n");
for (int z = minZ; z <= maxZ; z++) {
std::fprintf(f, " [");
for (int x = minX; x <= maxX; x++) {
std::fprintf(f, "%s", regions[level].count(std::make_pair(x, z)) ? "true" : "false");
if (x < maxX)
std::fprintf(f, ", ");
}
if (z < maxZ)
std::fprintf(f, "],\n");
else
std::fprintf(f, "]\n");
}
std::fprintf(f, " ]\n");
if (level < regions.size() - 1)
std::fprintf(f, " },\n");
else
std::fprintf(f, " }\n");
}
std::fprintf(f, " ],\n");
std::fprintf(f, " \"spawn\" : {\n");
std::fprintf(f, " \"x\" : %li,\n", (long)spawnX);
std::fprintf(f, " \"z\" : %li\n", (long)spawnZ);
std::fprintf(f, " }\n");
std::fprintf(f, "}\n");
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());
}
}
}

View file

@ -1,83 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#pragma once
#include <climits>
#include <cstddef>
#include <cstdint>
#include <set>
#include <tuple>
#include <utility>
#include <vector>
namespace MinedMap {
class Info {
private:
std::vector<std::set<std::pair<int, int>>> regions;
std::vector<std::tuple<int, int, int, int>> bounds;
int32_t spawnX, spawnZ;
public:
Info() : spawnX(0), spawnZ(0) {
addMipmapLevel();
}
std::tuple<int, int, int, int> getBounds(size_t level) const {
return bounds[level];
}
void addRegion(int x, int z, size_t level) {
regions[level].insert(std::make_pair(x, z));
std::tuple<int, int, int, int> &b = bounds[level];
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() {
regions.emplace_back();
bounds.emplace_back(INT_MAX, INT_MIN, INT_MAX, INT_MIN);
}
size_t getMipmapLevel() const {
return regions.size()-1;
}
void setSpawn(const std::pair<int32_t, int32_t> &v) {
std::tie(spawnX, spawnZ) = v;
}
void writeJSON(const char *filename) const;
};
}

View file

@ -1,336 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#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 <sstream>
#include <system_error>
#include <sys/types.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/stat.h>
namespace MinedMap {
static const size_t DIM = World::Region::SIZE*World::Chunk::SIZE;
static void addChunk(Resource::Color image[DIM*DIM], uint8_t lightmap[2*DIM*DIM], size_t X, size_t Z, const World::ChunkData *data) {
World::Chunk chunk(data);
World::Chunk::Blocks layer = chunk.getTopLayer();
for (size_t x = 0; x < World::Chunk::SIZE; x++) {
for (size_t z = 0; z < World::Chunk::SIZE; z++) {
size_t i = (Z*World::Chunk::SIZE+z)*DIM + X*World::Chunk::SIZE+x;
const World::Block &block = layer.blocks[x][z];
image[i] = block.getColor();
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) {
std::fscanf(f, "%" SCNd64, &v);
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 void writeImage(const std::string &output, const uint8_t *data, bool colored, int64_t t) {
const std::string tmpfile = output + ".tmp";
try {
PNG::write(tmpfile.c_str(), data, DIM, DIM, colored);
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;
}
}
static void doRegion(const std::string &input, const std::string &output, const std::string &output_light) {
int64_t intime;
{
struct stat instat;
if (stat(input.c_str(), &instat) < 0) {
std::fprintf(stderr, "Unable to stat %s: %s\n", input.c_str(), std::strerror(errno));
return;
}
#ifdef _WIN32
intime = (int64_t)instat.st_mtime * 1000000;
#else
intime = (int64_t)instat.st_mtim.tv_sec * 1000000 + instat.st_mtim.tv_nsec / 1000;
#endif
}
{
struct stat s;
if (stat(output.c_str(), &s) == 0) {
int64_t outtime = readStamp(output);
if (intime <= outtime) {
std::printf("%s is up-to-date.\n", output.c_str());
return;
}
}
}
std::printf("Generating %s from %s...\n", output.c_str(), input.c_str());
try {
std::unique_ptr<Resource::Color[]> image(new Resource::Color[DIM*DIM]);
std::memset(image.get(), 0, 4*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(), [&] (size_t X, size_t Z, const World::ChunkData *chunk) { addChunk(image.get(), lightmap.get(), X, Z, chunk); });
writeImage(output, reinterpret_cast<const uint8_t*>(image.get()), true, intime);
writeImage(output_light, lightmap.get(), false, intime);
}
catch (const std::exception& ex) {
std::fprintf(stderr, "Failed to generate %s: %s\n", output.c_str(), ex.what());
}
}
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 bool makeMipmap(const std::string &dir, size_t level, size_t x, size_t z, bool colored) {
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;
size_t 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, colored, 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 void makeMipmaps(const std::string &dir, Info *info) {
int minX, maxX, minZ, maxZ;
std::tie(minX, maxX, minZ, maxZ) = info->getBounds(0);
while (minX < -1 || maxX > 0 || minZ < -1 || maxZ > 0) {
info->addMipmapLevel();
size_t level = info->getMipmapLevel();
makeDir(dir + "/map/" + format(level));
makeDir(dir + "/light/" + format(level));
minX = (minX-1)/2;
maxX = maxX/2;
minZ = (minZ-1)/2;
maxZ = maxZ/2;
for (int x = minX; x <= maxX; x++) {
for (int z = minZ; z <= maxZ; z++) {
if (makeMipmap(dir + "/map", level, x, z, true))
info->addRegion(x, z, level);
makeMipmap(dir + "/light", level, x, z, false);
}
}
}
}
}
int main(int argc, char *argv[]) {
using namespace MinedMap;
if (argc < 3) {
std::fprintf(stderr, "Usage: %s <data directory> <output directory>\n", argv[0]);
return 1;
}
std::string inputdir(argv[1]);
std::string regiondir = inputdir + "/region";
std::string outputdir(argv[2]);
makeDir(outputdir + "/map");
makeDir(outputdir + "/map/0");
makeDir(outputdir + "/light");
makeDir(outputdir + "/light/0");
DIR *dir = opendir(regiondir.c_str());
if (!dir) {
std::fprintf(stderr, "Unable to read input directory: %s\n", std::strerror(errno));
return 1;
}
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);
std::string name(entry->d_name), outname = name.substr(0, name.length()-3) + "png";
doRegion(regiondir + "/" + name, outputdir + "/map/0/" + outname, outputdir + "/light/0/" + outname);
}
closedir(dir);
World::Level level((inputdir + "/level.dat").c_str());
info.setSpawn(level.getSpawn());
makeMipmaps(outputdir, &info);
info.writeJSON((outputdir + "/info.json").c_str());
return 0;
}

View file

@ -1,87 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#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];
}
};
}
}

View file

@ -1,63 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#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;
}
};
}
}

View file

@ -1,82 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#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);
}
};
}
}

View file

@ -1,63 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#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;
}
};
}
}

View file

@ -1,51 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#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 {
}
};
}
}

View file

@ -1,62 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#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;
}
};
}
}

View file

@ -1,87 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#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]);
}
};
}
}

View file

@ -1,63 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#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);
}
};
}
}

View file

@ -1,78 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#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 << "]";
}
};
}
}

View file

@ -1,87 +0,0 @@
/*
Copyright (c) 2015-2018, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#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]);
}
};
}
}

View file

@ -1,63 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#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);
}
};
}
}

View file

@ -1,63 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#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);
}
};
}
}

View file

@ -1,63 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#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() << "\"";
}
};
}
}

View file

@ -1,91 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#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));
}
}
}

View file

@ -1,103 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#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;
}
}
}

View file

@ -1,156 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#include "PNG.hpp"
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <memory>
#include <system_error>
#include <png.h>
namespace MinedMap {
namespace PNG {
void write(const char *filename, const uint8_t *data, size_t width, size_t height, bool colored) {
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, colored ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_GRAY_ALPHA,
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[(colored ? 4 : 2)*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, bool colored) {
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) != (colored ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_GRAY_ALPHA))
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[(colored ? 4 : 2)*i*width], row_pointers[i], (colored ? 4 : 2)*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, bool colored) {
if (!file)
return;
size_t b = (colored ? 4 : 2);
std::unique_ptr<uint8_t[]> input(new uint8_t[b*width*height]);
read(file, input.get(), width, height, colored);
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, bool colored, const char *nw, const char *ne, const char *sw, const char *se) {
size_t size = (colored ? 4 : 2)*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, colored);
readScaled(data.get(), width/2, 0, ne, width, height, colored);
readScaled(data.get(), 0, height/2, sw, width, height, colored);
readScaled(data.get(), width/2, height/2, se, width, height, colored);
write(output, data.get(), width, height, colored);
}
}
}

View file

@ -1,41 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#pragma once
#include <cstddef>
#include <cstdint>
namespace MinedMap {
namespace PNG {
void write(const char *filename, const uint8_t *data, size_t width, size_t height, bool colored);
void read(const char *filename, uint8_t *data, size_t width, size_t height, bool colored);
void mipmap(const char *output, size_t width, size_t height, bool colored, const char *nw, const char *ne, const char *sw, const char *se);
}
}

View file

@ -1,373 +0,0 @@
/*
Copyright (c) 2015, 2018, Matthias Schiffer <mschiffer@universe-factory.net>
Copyright (c) 2019, Roman Shishkin <spark@uwtech.org>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#include "Biome.hpp"
#include "BlockType.hpp"
#include "../Util.hpp"
namespace MinedMap {
namespace Resource {
static Biome::FloatColor operator+(const Biome::FloatColor &a, const Biome::FloatColor &b){
return Biome::FloatColor {
a.r+b.r,
a.g+b.g,
a.b+b.b,
};
}
static Biome::FloatColor & operator*=(Biome::FloatColor &a, const Biome::FloatColor &b) {
a.r *= b.r;
a.g *= b.g;
a.b *= b.b;
return a;
}
static Biome::FloatColor operator*(float s, const Biome::FloatColor &c) {
return Biome::FloatColor {
s*c.r,
s*c.g,
s*c.b,
};
}
static Biome::FloatColor colorFromParams(float temp, float rain, bool grass) {
const Biome::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 Biome::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 Biome::FloatColor *colors = grass ? grassColors : foliageColors;
return colors[0] + temp*colors[1] + rain*colors[2];
}
Biome::FloatColor Biome::getGrassColor(float temp, float rain) const {
return colorFromParams(temp, rain, true);
}
Biome::FloatColor Biome::getFoliageColor(float temp, float rain) const {
return colorFromParams(temp, rain, false);
}
Biome::FloatColor Biome::getWaterColor(float, float) const {
return {0.161f, 0.365f, 0.996f};
}
Color Biome::getBlockColor(const BlockType *type, unsigned height) const {
Biome::FloatColor c = {
float(type->color.r),
float(type->color.g),
float(type->color.b),
};
float t = clamp(temp - std::max(0.0f, (height-64)/600.0f), 0, 1);
float r = clamp(rain, 0, 1) * t;
if (type->grass)
c *= getGrassColor(t, r);
if (type->foliage)
c *= getFoliageColor(t, r);
if (type->blue)
c *= getWaterColor(t, r);
float h = 0.5f + height * 0.005f;
return Color {
uint8_t(clamp(c.r * h, 0, 255)),
uint8_t(clamp(c.g * h, 0, 255)),
uint8_t(clamp(c.b * h, 0, 255)),
0xff,
};
}
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);
}
virtual FloatColor getWaterColor(float, float) const {
return {0.141f, 0.365f, 0.680f};
}
public:
SwampBiome(float temp0, float rain0) : Biome(temp0, rain0) {}
};
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);
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);
extern const Biome *const BIOME_DEFAULT = &BiomeDefault;
/*
* TODO: Add proper temp/rain values for oceans 44-50
*
* It does not matter much at the moment, as we do not compute
* the water color anyways
*/
const Biome *const 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 */ &BiomeFrozen, /* Frozen Ocean */
/* 11 */ &BiomeFrozen, /* 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 */ &BiomeDefault, /* Warm Ocean */
/* 45 */ &BiomeDefault, /* Lukewarm Ocean */
/* 46 */ &BiomeDefault, /* Cold Ocean */
/* 47 */ &BiomeDefault, /* Deep Warm Ocean */
/* 48 */ &BiomeDefault, /* Deep Lukewarm Ocean */
/* 49 */ &BiomeDefault, /* Deep Cold Ocean */
/* 50 */ &BiomeFrozen, /* Deep Frozen Ocean */
/* 51 */ nullptr,
/* 52 */ nullptr,
/* 53 */ nullptr,
/* 54 */ nullptr,
/* 55 */ nullptr,
/* 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 */
};
}
}

View file

@ -1,61 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#pragma once
#include "Color.hpp"
namespace MinedMap {
namespace Resource {
class BlockType;
class Biome {
public:
struct FloatColor {
float r, g, b;
};
private:
float temp, rain;
protected:
virtual FloatColor getGrassColor(float temp, float rain) const;
virtual FloatColor getFoliageColor(float temp, float rain) const;
virtual FloatColor getWaterColor(float temp, float rain) const;
public:
Biome(float temp0, float rain0) : temp(temp0), rain(rain0) {}
Color getBlockColor(const BlockType *type, unsigned height) const;
};
extern const Biome *const BIOME_DEFAULT;
extern const Biome *const BIOMES[256];
}
}

View file

@ -1,87 +0,0 @@
/*
Copyright (c) 2015-2018, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#include "BlockType.hpp"
namespace MinedMap {
namespace Resource {
const std::unordered_map<std::string, BlockType> BlockType::Types = {
#include "BlockType.inc"
};
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"
};
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();
}
}

View file

@ -1,60 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#pragma once
#include <cstdint>
#include <string>
#include <unordered_map>
namespace MinedMap {
namespace Resource {
struct BlockType {
private:
static const std::unordered_map<std::string, BlockType> Types;
public:
static const BlockType * lookup(const std::string &name);
bool opaque;
bool grass;
bool foliage;
bool blue;
struct {
uint8_t r, g, b;
} color;
};
struct LegacyPalette {
const BlockType *types[256][16];
};
extern const LegacyPalette LEGACY_BLOCK_TYPES;
}
}

View file

@ -1,683 +0,0 @@
{"minecraft:acacia_button", {false, false, false, false, {0, 0, 0}}},
{"minecraft:acacia_door", {true, false, false, false, {167, 95, 60}}},
{"minecraft:acacia_fence_gate", {true, false, false, false, {168, 90, 50}}},
{"minecraft:acacia_fence", {true, false, false, false, {168, 90, 50}}},
{"minecraft:acacia_leaves", {true, false, true, false, {149, 148, 148}}},
{"minecraft:acacia_log", {true, false, false, false, {150, 88, 55}}},
{"minecraft:acacia_planks", {true, false, false, false, {168, 90, 50}}},
{"minecraft:acacia_pressure_plate", {true, false, false, false, {168, 90, 50}}},
{"minecraft:acacia_sapling", {true, false, false, false, {118, 117, 23}}},
{"minecraft:acacia_sign", {true, false, false, false, {168, 90, 50}}},
{"minecraft:acacia_slab", {true, false, false, false, {168, 90, 50}}},
{"minecraft:acacia_stairs", {true, false, false, false, {168, 90, 50}}},
{"minecraft:acacia_trapdoor", {true, false, false, false, {156, 87, 51}}},
{"minecraft:acacia_wall_sign", {false, false, false, false, {0, 0, 0}}},
{"minecraft:acacia_wood", {true, false, false, false, {103, 96, 86}}},
{"minecraft:activator_rail", {true, false, false, false, {115, 87, 74}}},
{"minecraft:air", {false, false, false, false, {0, 0, 0}}},
{"minecraft:allium", {false, false, false, false, {0, 0, 0}}},
{"minecraft:andesite", {true, false, false, false, {136, 136, 136}}},
{"minecraft:andesite_slab", {true, false, false, false, {136, 136, 136}}},
{"minecraft:andesite_stairs", {true, false, false, false, {136, 136, 136}}},
{"minecraft:andesite_wall", {true, false, false, false, {136, 136, 136}}},
{"minecraft:anvil", {true, false, false, false, {72, 72, 72}}},
{"minecraft:attached_melon_stem", {true, true, false, false, {141, 142, 141}}},
{"minecraft:attached_pumpkin_stem", {true, true, false, false, {139, 139, 139}}},
{"minecraft:azure_bluet", {false, false, false, false, {0, 0, 0}}},
{"minecraft:bamboo", {true, false, false, false, {93, 144, 19}}},
{"minecraft:bamboo_sapling", {false, false, false, false, {0, 0, 0}}},
{"minecraft:barrel", {true, false, false, false, {134, 100, 58}}},
{"minecraft:barrier", {false, false, false, false, {0, 0, 0}}},
{"minecraft:beacon", {true, false, false, false, {117, 220, 215}}},
{"minecraft:bedrock", {true, false, false, false, {85, 85, 85}}},
{"minecraft:beehive", {true, false, false, false, {180, 146, 90}}},
{"minecraft:bee_nest", {true, false, false, false, {202, 160, 74}}},
{"minecraft:beetroots", {true, false, false, false, {93, 91, 30}}},
{"minecraft:bell", {true, false, false, false, {253, 235, 110}}},
{"minecraft:birch_button", {false, false, false, false, {0, 0, 0}}},
{"minecraft:birch_door", {true, false, false, false, {220, 209, 176}}},
{"minecraft:birch_fence_gate", {true, false, false, false, {192, 175, 121}}},
{"minecraft:birch_fence", {true, false, false, false, {192, 175, 121}}},
{"minecraft:birch_leaves", {true, false, true, false, {130, 129, 130}}},
{"minecraft:birch_log", {true, false, false, false, {193, 179, 135}}},
{"minecraft:birch_planks", {true, false, false, false, {192, 175, 121}}},
{"minecraft:birch_pressure_plate", {true, false, false, false, {192, 175, 121}}},
{"minecraft:birch_sapling", {true, false, false, false, {127, 160, 79}}},
{"minecraft:birch_sign", {true, false, false, false, {192, 175, 121}}},
{"minecraft:birch_slab", {true, false, false, false, {192, 175, 121}}},
{"minecraft:birch_stairs", {true, false, false, false, {192, 175, 121}}},
{"minecraft:birch_trapdoor", {true, false, false, false, {207, 194, 157}}},
{"minecraft:birch_wall_sign", {false, false, false, false, {0, 0, 0}}},
{"minecraft:birch_wood", {true, false, false, false, {216, 215, 210}}},
{"minecraft:black_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:black_bed", {false, false, false, false, {0, 0, 0}}},
{"minecraft:black_carpet", {true, false, false, false, {20, 21, 25}}},
{"minecraft:black_concrete", {true, false, false, false, {8, 10, 15}}},
{"minecraft:black_concrete_powder", {true, false, false, false, {25, 26, 31}}},
{"minecraft:black_glazed_terracotta", {true, false, false, false, {67, 30, 32}}},
{"minecraft:black_shulker_box", {true, false, false, false, {25, 25, 29}}},
{"minecraft:black_stained_glass", {true, false, false, false, {25, 25, 25}}},
{"minecraft:black_stained_glass_pane", {true, false, false, false, {24, 24, 24}}},
{"minecraft:black_terracotta", {true, false, false, false, {37, 22, 16}}},
{"minecraft:black_wall_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:black_wool", {true, false, false, false, {20, 21, 25}}},
{"minecraft:blast_furnace", {true, false, false, false, {80, 80, 81}}},
{"minecraft:blue_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:blue_bed", {false, false, false, false, {0, 0, 0}}},
{"minecraft:blue_carpet", {true, false, false, false, {53, 57, 157}}},
{"minecraft:blue_concrete", {true, false, false, false, {44, 46, 143}}},
{"minecraft:blue_concrete_powder", {true, false, false, false, {70, 73, 166}}},
{"minecraft:blue_glazed_terracotta", {true, false, false, false, {47, 64, 139}}},
{"minecraft:blue_ice", {true, false, false, false, {116, 167, 253}}},
{"minecraft:blue_orchid", {false, false, false, false, {0, 0, 0}}},
{"minecraft:blue_shulker_box", {true, false, false, false, {43, 45, 140}}},
{"minecraft:blue_stained_glass", {true, false, false, false, {51, 76, 178}}},
{"minecraft:blue_stained_glass_pane", {true, false, false, false, {48, 73, 171}}},
{"minecraft:blue_terracotta", {true, false, false, false, {74, 59, 91}}},
{"minecraft:blue_wall_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:blue_wool", {true, false, false, false, {53, 57, 157}}},
{"minecraft:bone_block", {true, false, false, false, {209, 206, 179}}},
{"minecraft:bookshelf", {true, false, false, false, {162, 130, 78}}},
{"minecraft:brain_coral_block", {true, false, false, false, {207, 91, 159}}},
{"minecraft:brain_coral_fan", {false, false, false, false, {0, 0, 0}}},
{"minecraft:brain_coral", {false, false, false, false, {0, 0, 0}}},
{"minecraft:brain_coral_wall_fan", {false, false, false, false, {0, 0, 0}}},
{"minecraft:brewing_stand", {true, false, false, false, {123, 101, 81}}},
{"minecraft:bricks", {true, false, false, false, {150, 97, 83}}},
{"minecraft:brick_slab", {true, false, false, false, {150, 97, 83}}},
{"minecraft:brick_stairs", {true, false, false, false, {150, 97, 83}}},
{"minecraft:brick_wall", {true, false, false, false, {150, 97, 83}}},
{"minecraft:brown_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:brown_bed", {false, false, false, false, {0, 0, 0}}},
{"minecraft:brown_carpet", {true, false, false, false, {114, 71, 40}}},
{"minecraft:brown_concrete", {true, false, false, false, {96, 59, 31}}},
{"minecraft:brown_concrete_powder", {true, false, false, false, {125, 84, 53}}},
{"minecraft:brown_glazed_terracotta", {true, false, false, false, {119, 106, 85}}},
{"minecraft:brown_mushroom_block", {true, false, false, false, {149, 111, 81}}},
{"minecraft:brown_mushroom", {false, false, false, false, {0, 0, 0}}},
{"minecraft:brown_shulker_box", {true, false, false, false, {106, 66, 35}}},
{"minecraft:brown_stained_glass", {true, false, false, false, {102, 76, 51}}},
{"minecraft:brown_stained_glass_pane", {true, false, false, false, {97, 73, 48}}},
{"minecraft:brown_terracotta", {true, false, false, false, {77, 51, 35}}},
{"minecraft:brown_wall_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:brown_wool", {true, false, false, false, {114, 71, 40}}},
{"minecraft:bubble_column", {true, false, false, true, {177, 177, 177}}},
{"minecraft:bubble_coral_block", {true, false, false, false, {165, 26, 162}}},
{"minecraft:bubble_coral_fan", {false, false, false, false, {0, 0, 0}}},
{"minecraft:bubble_coral", {false, false, false, false, {0, 0, 0}}},
{"minecraft:bubble_coral_wall_fan", {false, false, false, false, {0, 0, 0}}},
{"minecraft:cactus", {true, false, false, false, {85, 127, 43}}},
{"minecraft:cake", {true, false, false, false, {248, 222, 214}}},
{"minecraft:campfire", {true, false, false, false, {110, 88, 54}}},
{"minecraft:carrots", {true, false, false, false, {81, 123, 37}}},
{"minecraft:cartography_table", {true, false, false, false, {104, 87, 67}}},
{"minecraft:carved_pumpkin", {true, false, false, false, {198, 118, 24}}},
{"minecraft:cauldron", {true, false, false, false, {73, 72, 74}}},
{"minecraft:cave_air", {false, false, false, false, {0, 0, 0}}},
{"minecraft:chain_command_block", {true, false, false, false, {131, 161, 147}}},
{"minecraft:chest", {true, false, false, false, {162, 130, 78}}},
{"minecraft:chipped_anvil", {true, false, false, false, {72, 72, 72}}},
{"minecraft:chiseled_quartz_block", {true, false, false, false, {231, 226, 218}}},
{"minecraft:chiseled_red_sandstone", {true, false, false, false, {181, 97, 31}}},
{"minecraft:chiseled_sandstone", {true, false, false, false, {223, 214, 170}}},
{"minecraft:chiseled_stone_bricks", {true, false, false, false, {119, 118, 119}}},
{"minecraft:chorus_flower", {true, false, false, false, {151, 120, 151}}},
{"minecraft:chorus_plant", {true, false, false, false, {93, 57, 93}}},
{"minecraft:clay", {true, false, false, false, {160, 166, 179}}},
{"minecraft:coal_block", {true, false, false, false, {16, 15, 15}}},
{"minecraft:coal_ore", {true, false, false, false, {116, 116, 116}}},
{"minecraft:coarse_dirt", {true, false, false, false, {119, 85, 59}}},
{"minecraft:cobblestone", {true, false, false, false, {127, 127, 127}}},
{"minecraft:cobblestone_slab", {true, false, false, false, {127, 127, 127}}},
{"minecraft:cobblestone_stairs", {true, false, false, false, {127, 127, 127}}},
{"minecraft:cobblestone_wall", {true, false, false, false, {127, 127, 127}}},
{"minecraft:cobweb", {true, false, false, false, {228, 233, 234}}},
{"minecraft:cocoa", {true, false, false, false, {156, 94, 43}}},
{"minecraft:command_block", {true, false, false, false, {181, 136, 108}}},
{"minecraft:comparator", {true, false, false, false, {166, 161, 159}}},
{"minecraft:composter", {true, false, false, false, {88, 61, 23}}},
{"minecraft:cornflower", {false, false, false, false, {0, 0, 0}}},
{"minecraft:conduit", {true, false, false, false, {159, 139, 113}}},
{"minecraft:cracked_stone_bricks", {true, false, false, false, {118, 117, 118}}},
{"minecraft:crafting_table", {true, false, false, false, {119, 73, 42}}},
{"minecraft:creeper_head", {false, false, false, false, {0, 0, 0}}},
{"minecraft:creeper_wall_head", {false, false, false, false, {0, 0, 0}}},
{"minecraft:cut_red_sandstone", {true, false, false, false, {181, 97, 31}}},
{"minecraft:cut_red_sandstone_slab", {true, false, false, false, {181, 97, 31}}},
{"minecraft:cut_sandstone", {true, false, false, false, {223, 214, 170}}},
{"minecraft:cut_sandstone_slab", {true, false, false, false, {223, 214, 170}}},
{"minecraft:cyan_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:cyan_bed", {false, false, false, false, {0, 0, 0}}},
{"minecraft:cyan_carpet", {true, false, false, false, {21, 137, 145}}},
{"minecraft:cyan_concrete", {true, false, false, false, {21, 119, 136}}},
{"minecraft:cyan_concrete_powder", {true, false, false, false, {36, 147, 157}}},
{"minecraft:cyan_glazed_terracotta", {true, false, false, false, {52, 118, 125}}},
{"minecraft:cyan_shulker_box", {true, false, false, false, {20, 121, 135}}},
{"minecraft:cyan_stained_glass", {true, false, false, false, {76, 127, 153}}},
{"minecraft:cyan_stained_glass_pane", {true, false, false, false, {73, 122, 147}}},
{"minecraft:cyan_terracotta", {true, false, false, false, {86, 91, 91}}},
{"minecraft:cyan_wall_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:cyan_wool", {true, false, false, false, {21, 137, 145}}},
{"minecraft:damaged_anvil", {true, false, false, false, {72, 72, 72}}},
{"minecraft:dandelion", {false, false, false, false, {0, 0, 0}}},
{"minecraft:dark_oak_button", {false, false, false, false, {0, 0, 0}}},
{"minecraft:dark_oak_door", {true, false, false, false, {76, 51, 25}}},
{"minecraft:dark_oak_fence_gate", {true, false, false, false, {66, 43, 20}}},
{"minecraft:dark_oak_fence", {true, false, false, false, {66, 43, 20}}},
{"minecraft:dark_oak_leaves", {true, false, true, false, {150, 150, 150}}},
{"minecraft:dark_oak_log", {true, false, false, false, {64, 42, 21}}},
{"minecraft:dark_oak_planks", {true, false, false, false, {66, 43, 20}}},
{"minecraft:dark_oak_pressure_plate", {true, false, false, false, {66, 43, 20}}},
{"minecraft:dark_oak_sapling", {true, false, false, false, {61, 90, 30}}},
{"minecraft:dark_oak_sign", {true, false, false, false, {66, 43, 20}}},
{"minecraft:dark_oak_slab", {true, false, false, false, {66, 43, 20}}},
{"minecraft:dark_oak_stairs", {true, false, false, false, {66, 43, 20}}},
{"minecraft:dark_oak_trapdoor", {true, false, false, false, {75, 49, 23}}},
{"minecraft:dark_oak_wall_sign", {false, false, false, false, {0, 0, 0}}},
{"minecraft:dark_oak_wood", {true, false, false, false, {60, 46, 26}}},
{"minecraft:dark_prismarine", {true, false, false, false, {51, 91, 75}}},
{"minecraft:dark_prismarine_slab", {true, false, false, false, {51, 91, 75}}},
{"minecraft:dark_prismarine_stairs", {true, false, false, false, {51, 91, 75}}},
{"minecraft:daylight_detector", {true, false, false, false, {130, 116, 94}}},
{"minecraft:dead_brain_coral", {false, false, false, false, {0, 0, 0}}},
{"minecraft:dead_brain_coral_block", {true, false, false, false, {124, 117, 114}}},
{"minecraft:dead_brain_coral_fan", {false, false, false, false, {0, 0, 0}}},
{"minecraft:dead_brain_coral_wall_fan", {false, false, false, false, {0, 0, 0}}},
{"minecraft:dead_bubble_coral", {false, false, false, false, {0, 0, 0}}},
{"minecraft:dead_bubble_coral_block", {true, false, false, false, {131, 123, 119}}},
{"minecraft:dead_bubble_coral_fan", {false, false, false, false, {0, 0, 0}}},
{"minecraft:dead_bubble_coral_wall_fan", {false, false, false, false, {0, 0, 0}}},
{"minecraft:dead_bush", {true, false, false, false, {107, 78, 40}}},
{"minecraft:dead_fire_coral", {false, false, false, false, {0, 0, 0}}},
{"minecraft:dead_fire_coral_block", {true, false, false, false, {131, 123, 119}}},
{"minecraft:dead_fire_coral_fan", {false, false, false, false, {0, 0, 0}}},
{"minecraft:dead_fire_coral_wall_fan", {false, false, false, false, {0, 0, 0}}},
{"minecraft:dead_horn_coral", {false, false, false, false, {0, 0, 0}}},
{"minecraft:dead_horn_coral_block", {true, false, false, false, {133, 126, 122}}},
{"minecraft:dead_horn_coral_fan", {false, false, false, false, {0, 0, 0}}},
{"minecraft:dead_horn_coral_wall_fan", {false, false, false, false, {0, 0, 0}}},
{"minecraft:dead_tube_coral", {false, false, false, false, {0, 0, 0}}},
{"minecraft:dead_tube_coral_block", {true, false, false, false, {130, 123, 119}}},
{"minecraft:dead_tube_coral_fan", {false, false, false, false, {0, 0, 0}}},
{"minecraft:dead_tube_coral_wall_fan", {false, false, false, false, {0, 0, 0}}},
{"minecraft:detector_rail", {true, false, false, false, {123, 104, 90}}},
{"minecraft:diamond_block", {true, false, false, false, {98, 237, 228}}},
{"minecraft:diamond_ore", {true, false, false, false, {125, 142, 141}}},
{"minecraft:diorite", {true, false, false, false, {188, 188, 188}}},
{"minecraft:diorite_slab", {true, false, false, false, {188, 188, 188}}},
{"minecraft:diorite_stairs", {true, false, false, false, {188, 188, 188}}},
{"minecraft:diorite_wall", {true, false, false, false, {188, 188, 188}}},
{"minecraft:dirt", {true, false, false, false, {134, 96, 67}}},
{"minecraft:dispenser", {true, false, false, false, {110, 109, 109}}},
{"minecraft:dragon_egg", {true, false, false, false, {12, 9, 15}}},
{"minecraft:dragon_head", {false, false, false, false, {0, 0, 0}}},
{"minecraft:dragon_wall_head", {false, false, false, false, {0, 0, 0}}},
{"minecraft:dried_kelp_block", {true, false, false, false, {50, 58, 38}}},
{"minecraft:dropper", {true, false, false, false, {110, 109, 109}}},
{"minecraft:emerald_block", {true, false, false, false, {42, 203, 87}}},
{"minecraft:emerald_ore", {true, false, false, false, {117, 136, 124}}},
{"minecraft:enchanting_table", {true, false, false, false, {128, 75, 85}}},
{"minecraft:ender_chest", {true, false, false, false, {15, 10, 24}}},
{"minecraft:end_gateway", {true, false, false, false, {15, 10, 24}}},
{"minecraft:end_portal_frame", {true, false, false, false, {91, 120, 97}}},
{"minecraft:end_portal", {true, false, false, false, {15, 10, 24}}},
{"minecraft:end_rod", {false, false, false, false, {0, 0, 0}}},
{"minecraft:end_stone", {true, false, false, false, {219, 222, 158}}},
{"minecraft:end_stone_bricks", {true, false, false, false, {218, 224, 162}}},
{"minecraft:end_stone_brick_slab", {true, false, false, false, {218, 224, 162}}},
{"minecraft:end_stone_brick_stairs", {true, false, false, false, {218, 224, 162}}},
{"minecraft:end_stone_brick_wall", {true, false, false, false, {218, 224, 162}}},
{"minecraft:farmland", {true, false, false, false, {81, 44, 15}}},
{"minecraft:fern", {false, false, false, false, {0, 0, 0}}},
{"minecraft:fire", {true, false, false, false, {211, 140, 53}}},
{"minecraft:fire_coral_block", {true, false, false, false, {163, 35, 46}}},
{"minecraft:fire_coral_fan", {false, false, false, false, {0, 0, 0}}},
{"minecraft:fire_coral", {false, false, false, false, {0, 0, 0}}},
{"minecraft:fire_coral_wall_fan", {false, false, false, false, {0, 0, 0}}},
{"minecraft:fletching_table", {true, false, false, false, {197, 180, 133}}},
{"minecraft:flower_pot", {true, false, false, false, {124, 68, 53}}},
{"minecraft:frosted_ice", {true, false, false, false, {140, 181, 252}}},
{"minecraft:furnace", {true, false, false, false, {110, 109, 109}}},
{"minecraft:glass", {true, false, false, false, {175, 213, 219}}},
{"minecraft:glass_pane", {true, false, false, false, {211, 239, 244}}},
{"minecraft:glowstone", {true, false, false, false, {171, 131, 84}}},
{"minecraft:gold_block", {true, false, false, false, {246, 208, 61}}},
{"minecraft:gold_ore", {true, false, false, false, {143, 140, 125}}},
{"minecraft:granite", {true, false, false, false, {149, 103, 85}}},
{"minecraft:granite_slab", {true, false, false, false, {149, 103, 85}}},
{"minecraft:granite_stairs", {true, false, false, false, {149, 103, 85}}},
{"minecraft:granite_wall", {true, false, false, false, {149, 103, 85}}},
{"minecraft:grass_block", {true, true, false, false, {147, 147, 147}}},
{"minecraft:grass", {false, false, false, false, {0, 0, 0}}},
{"minecraft:grass_path", {true, false, false, false, {148, 121, 65}}},
{"minecraft:gravel", {true, false, false, false, {131, 127, 126}}},
{"minecraft:gray_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:gray_bed", {false, false, false, false, {0, 0, 0}}},
{"minecraft:gray_carpet", {true, false, false, false, {62, 68, 71}}},
{"minecraft:gray_concrete", {true, false, false, false, {54, 57, 61}}},
{"minecraft:gray_concrete_powder", {true, false, false, false, {76, 81, 84}}},
{"minecraft:gray_glazed_terracotta", {true, false, false, false, {83, 90, 93}}},
{"minecraft:gray_shulker_box", {true, false, false, false, {55, 58, 62}}},
{"minecraft:gray_stained_glass", {true, false, false, false, {76, 76, 76}}},
{"minecraft:gray_stained_glass_pane", {true, false, false, false, {73, 73, 73}}},
{"minecraft:gray_terracotta", {true, false, false, false, {57, 42, 35}}},
{"minecraft:gray_wall_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:gray_wool", {true, false, false, false, {62, 68, 71}}},
{"minecraft:green_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:green_bed", {false, false, false, false, {0, 0, 0}}},
{"minecraft:green_carpet", {true, false, false, false, {84, 109, 27}}},
{"minecraft:green_concrete", {true, false, false, false, {73, 91, 36}}},
{"minecraft:green_concrete_powder", {true, false, false, false, {97, 119, 44}}},
{"minecraft:green_glazed_terracotta", {true, false, false, false, {117, 142, 67}}},
{"minecraft:green_shulker_box", {true, false, false, false, {79, 100, 31}}},
{"minecraft:green_stained_glass", {true, false, false, false, {102, 127, 51}}},
{"minecraft:green_stained_glass_pane", {true, false, false, false, {97, 122, 48}}},
{"minecraft:green_terracotta", {true, false, false, false, {76, 83, 42}}},
{"minecraft:green_wall_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:green_wool", {true, false, false, false, {84, 109, 27}}},
{"minecraft:grindstone", {true, false, false, false, {142, 142, 142}}},
{"minecraft:hay_block", {true, false, false, false, {165, 139, 12}}},
{"minecraft:heavy_weighted_pressure_plate", {true, false, false, false, {220, 220, 220}}},
{"minecraft:honey_block", {true, false, false, false, {251, 185, 52}}},
{"minecraft:honeycomb_block", {true, false, false, false, {229, 148, 29}}},
{"minecraft:hopper", {true, false, false, false, {75, 74, 75}}},
{"minecraft:horn_coral_block", {true, false, false, false, {216, 199, 66}}},
{"minecraft:horn_coral_fan", {false, false, false, false, {0, 0, 0}}},
{"minecraft:horn_coral", {false, false, false, false, {0, 0, 0}}},
{"minecraft:horn_coral_wall_fan", {false, false, false, false, {0, 0, 0}}},
{"minecraft:ice", {true, false, false, false, {145, 183, 253}}},
{"minecraft:infested_chiseled_stone_bricks", {true, false, false, false, {119, 118, 119}}},
{"minecraft:infested_cobblestone", {true, false, false, false, {127, 127, 127}}},
{"minecraft:infested_cracked_stone_bricks", {true, false, false, false, {118, 117, 118}}},
{"minecraft:infested_mossy_stone_bricks", {true, false, false, false, {115, 121, 105}}},
{"minecraft:infested_stone_bricks", {true, false, false, false, {122, 121, 122}}},
{"minecraft:infested_stone", {true, false, false, false, {125, 125, 125}}},
{"minecraft:iron_bars", {true, false, false, false, {136, 139, 135}}},
{"minecraft:iron_block", {true, false, false, false, {220, 220, 220}}},
{"minecraft:iron_door", {true, false, false, false, {193, 192, 192}}},
{"minecraft:iron_ore", {true, false, false, false, {136, 130, 127}}},
{"minecraft:iron_trapdoor", {true, false, false, false, {202, 202, 202}}},
{"minecraft:item_frame", {false, false, false, false, {0, 0, 0}}},
{"minecraft:jack_o_lantern", {true, false, false, false, {214, 152, 52}}},
{"minecraft:jigsaw", {true, false, false, false, {80, 69, 81}}},
{"minecraft:jukebox", {true, false, false, false, {93, 64, 47}}},
{"minecraft:jungle_button", {false, false, false, false, {0, 0, 0}}},
{"minecraft:jungle_door", {true, false, false, false, {163, 119, 84}}},
{"minecraft:jungle_fence_gate", {true, false, false, false, {160, 115, 80}}},
{"minecraft:jungle_fence", {true, false, false, false, {160, 115, 80}}},
{"minecraft:jungle_leaves", {true, false, true, false, {156, 154, 143}}},
{"minecraft:jungle_log", {true, false, false, false, {149, 109, 70}}},
{"minecraft:jungle_planks", {true, false, false, false, {160, 115, 80}}},
{"minecraft:jungle_pressure_plate", {true, false, false, false, {160, 115, 80}}},
{"minecraft:jungle_sapling", {true, false, false, false, {47, 81, 16}}},
{"minecraft:jungle_sign", {true, false, false, false, {160, 115, 80}}},
{"minecraft:jungle_slab", {true, false, false, false, {160, 115, 80}}},
{"minecraft:jungle_stairs", {true, false, false, false, {160, 115, 80}}},
{"minecraft:jungle_trapdoor", {true, false, false, false, {152, 110, 77}}},
{"minecraft:jungle_wall_sign", {false, false, false, false, {0, 0, 0}}},
{"minecraft:jungle_wood", {true, false, false, false, {85, 67, 25}}},
{"minecraft:kelp", {false, false, false, false, {0, 0, 0}}},
{"minecraft:kelp_plant", {true, false, false, false, {86, 130, 42}}},
{"minecraft:ladder", {false, false, false, false, {0, 0, 0}}},
{"minecraft:lantern", {true, false, false, false, {106, 91, 83}}},
{"minecraft:lapis_block", {true, false, false, false, {30, 67, 140}}},
{"minecraft:lapis_ore", {true, false, false, false, {99, 110, 132}}},
{"minecraft:large_fern", {true, true, false, false, {125, 125, 125}}},
{"minecraft:lava", {true, false, false, false, {212, 90, 18}}},
{"minecraft:lectern", {true, false, false, false, {173, 137, 83}}},
{"minecraft:lever", {false, false, false, false, {0, 0, 0}}},
{"minecraft:light_blue_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:light_blue_bed", {false, false, false, false, {0, 0, 0}}},
{"minecraft:light_blue_carpet", {true, false, false, false, {58, 175, 217}}},
{"minecraft:light_blue_concrete", {true, false, false, false, {35, 137, 198}}},
{"minecraft:light_blue_concrete_powder", {true, false, false, false, {74, 180, 213}}},
{"minecraft:light_blue_glazed_terracotta", {true, false, false, false, {94, 164, 208}}},
{"minecraft:light_blue_shulker_box", {true, false, false, false, {49, 163, 212}}},
{"minecraft:light_blue_stained_glass", {true, false, false, false, {102, 153, 216}}},
{"minecraft:light_blue_stained_glass_pane", {true, false, false, false, {97, 147, 208}}},
{"minecraft:light_blue_terracotta", {true, false, false, false, {113, 108, 137}}},
{"minecraft:light_blue_wall_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:light_blue_wool", {true, false, false, false, {58, 175, 217}}},
{"minecraft:light_gray_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:light_gray_bed", {false, false, false, false, {0, 0, 0}}},
{"minecraft:light_gray_carpet", {true, false, false, false, {142, 142, 134}}},
{"minecraft:light_gray_concrete", {true, false, false, false, {125, 125, 115}}},
{"minecraft:light_gray_concrete_powder", {true, false, false, false, {154, 154, 148}}},
{"minecraft:light_gray_glazed_terracotta", {true, false, false, false, {144, 166, 167}}},
{"minecraft:light_gray_shulker_box", {true, false, false, false, {124, 124, 115}}},
{"minecraft:light_gray_stained_glass", {true, false, false, false, {153, 153, 153}}},
{"minecraft:light_gray_stained_glass_pane", {true, false, false, false, {147, 147, 147}}},
{"minecraft:light_gray_terracotta", {true, false, false, false, {135, 106, 97}}},
{"minecraft:light_gray_wall_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:light_gray_wool", {true, false, false, false, {142, 142, 134}}},
{"minecraft:light_weighted_pressure_plate", {true, false, false, false, {246, 208, 61}}},
{"minecraft:lilac", {true, false, false, false, {154, 125, 147}}},
{"minecraft:lily_of_the_valley", {false, false, false, false, {0, 0, 0}}},
{"minecraft:lily_pad", {true, true, false, false, {133, 133, 133}}},
{"minecraft:lime_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:lime_bed", {false, false, false, false, {0, 0, 0}}},
{"minecraft:lime_carpet", {true, false, false, false, {112, 185, 25}}},
{"minecraft:lime_concrete", {true, false, false, false, {94, 168, 24}}},
{"minecraft:lime_concrete_powder", {true, false, false, false, {125, 189, 41}}},
{"minecraft:lime_glazed_terracotta", {true, false, false, false, {162, 197, 55}}},
{"minecraft:lime_shulker_box", {true, false, false, false, {99, 172, 23}}},
{"minecraft:lime_stained_glass", {true, false, false, false, {127, 204, 25}}},
{"minecraft:lime_stained_glass_pane", {true, false, false, false, {122, 196, 24}}},
{"minecraft:lime_terracotta", {true, false, false, false, {103, 117, 52}}},
{"minecraft:lime_wall_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:lime_wool", {true, false, false, false, {112, 185, 25}}},
{"minecraft:loom", {true, false, false, false, {142, 119, 91}}},
{"minecraft:magenta_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:magenta_bed", {false, false, false, false, {0, 0, 0}}},
{"minecraft:magenta_carpet", {true, false, false, false, {189, 68, 179}}},
{"minecraft:magenta_concrete", {true, false, false, false, {169, 48, 159}}},
{"minecraft:magenta_concrete_powder", {true, false, false, false, {192, 83, 184}}},
{"minecraft:magenta_glazed_terracotta", {true, false, false, false, {208, 100, 191}}},
{"minecraft:magenta_shulker_box", {true, false, false, false, {173, 54, 163}}},
{"minecraft:magenta_stained_glass", {true, false, false, false, {178, 76, 216}}},
{"minecraft:magenta_stained_glass_pane", {true, false, false, false, {171, 73, 208}}},
{"minecraft:magenta_terracotta", {true, false, false, false, {149, 88, 108}}},
{"minecraft:magenta_wall_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:magenta_wool", {true, false, false, false, {189, 68, 179}}},
{"minecraft:magma_block", {true, false, false, false, {142, 63, 31}}},
{"minecraft:melon", {true, false, false, false, {111, 144, 30}}},
{"minecraft:melon_stem", {true, true, false, false, {153, 153, 153}}},
{"minecraft:mossy_cobblestone", {true, false, false, false, {110, 118, 94}}},
{"minecraft:mossy_cobblestone_slab", {true, false, false, false, {110, 118, 94}}},
{"minecraft:mossy_cobblestone_stairs", {true, false, false, false, {110, 118, 94}}},
{"minecraft:mossy_cobblestone_wall", {true, false, false, false, {110, 118, 94}}},
{"minecraft:mossy_stone_bricks", {true, false, false, false, {115, 121, 105}}},
{"minecraft:mossy_stone_brick_slab", {true, false, false, false, {115, 121, 105}}},
{"minecraft:mossy_stone_brick_stairs", {true, false, false, false, {115, 121, 105}}},
{"minecraft:mossy_stone_brick_wall", {true, false, false, false, {115, 121, 105}}},
{"minecraft:moving_piston", {false, false, false, false, {0, 0, 0}}},
{"minecraft:mushroom_stem", {true, false, false, false, {203, 196, 185}}},
{"minecraft:mycelium", {true, false, false, false, {111, 98, 101}}},
{"minecraft:nether_brick_fence", {true, false, false, false, {44, 21, 26}}},
{"minecraft:nether_bricks", {true, false, false, false, {44, 21, 26}}},
{"minecraft:nether_brick_slab", {true, false, false, false, {44, 21, 26}}},
{"minecraft:nether_brick_stairs", {true, false, false, false, {44, 21, 26}}},
{"minecraft:nether_brick_wall", {true, false, false, false, {44, 21, 26}}},
{"minecraft:nether_portal", {true, false, false, false, {89, 11, 192}}},
{"minecraft:nether_quartz_ore", {true, false, false, false, {117, 65, 62}}},
{"minecraft:netherrack", {true, false, false, false, {97, 38, 38}}},
{"minecraft:nether_wart_block", {true, false, false, false, {114, 3, 2}}},
{"minecraft:nether_wart", {true, false, false, false, {111, 18, 19}}},
{"minecraft:note_block", {true, false, false, false, {88, 58, 40}}},
{"minecraft:oak_button", {false, false, false, false, {0, 0, 0}}},
{"minecraft:oak_door", {true, false, false, false, {139, 110, 65}}},
{"minecraft:oak_fence_gate", {true, false, false, false, {162, 130, 78}}},
{"minecraft:oak_fence", {true, false, false, false, {162, 130, 78}}},
{"minecraft:oak_leaves", {true, false, true, false, {144, 144, 144}}},
{"minecraft:oak_log", {true, false, false, false, {151, 121, 73}}},
{"minecraft:oak_planks", {true, false, false, false, {162, 130, 78}}},
{"minecraft:oak_pressure_plate", {true, false, false, false, {162, 130, 78}}},
{"minecraft:oak_sapling", {true, false, false, false, {77, 106, 40}}},
{"minecraft:oak_sign", {true, false, false, false, {162, 130, 78}}},
{"minecraft:oak_slab", {true, false, false, false, {162, 130, 78}}},
{"minecraft:oak_stairs", {true, false, false, false, {162, 130, 78}}},
{"minecraft:oak_trapdoor", {true, false, false, false, {124, 99, 56}}},
{"minecraft:oak_wall_sign", {false, false, false, false, {0, 0, 0}}},
{"minecraft:oak_wood", {true, false, false, false, {109, 85, 50}}},
{"minecraft:observer", {true, false, false, false, {98, 98, 98}}},
{"minecraft:obsidian", {true, false, false, false, {15, 10, 24}}},
{"minecraft:orange_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:orange_bed", {false, false, false, false, {0, 0, 0}}},
{"minecraft:orange_carpet", {true, false, false, false, {240, 118, 19}}},
{"minecraft:orange_concrete", {true, false, false, false, {224, 97, 0}}},
{"minecraft:orange_concrete_powder", {true, false, false, false, {227, 131, 31}}},
{"minecraft:orange_glazed_terracotta", {true, false, false, false, {154, 147, 91}}},
{"minecraft:orange_shulker_box", {true, false, false, false, {234, 106, 8}}},
{"minecraft:orange_stained_glass", {true, false, false, false, {216, 127, 51}}},
{"minecraft:orange_stained_glass_pane", {true, false, false, false, {208, 122, 48}}},
{"minecraft:orange_terracotta", {true, false, false, false, {161, 83, 37}}},
{"minecraft:orange_tulip", {false, false, false, false, {0, 0, 0}}},
{"minecraft:orange_wall_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:orange_wool", {true, false, false, false, {240, 118, 19}}},
{"minecraft:oxeye_daisy", {false, false, false, false, {0, 0, 0}}},
{"minecraft:packed_ice", {true, false, false, false, {141, 180, 250}}},
{"minecraft:peony", {true, false, false, false, {129, 126, 139}}},
{"minecraft:petrified_oak_slab", {true, false, false, false, {162, 130, 78}}},
{"minecraft:pink_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:pink_bed", {false, false, false, false, {0, 0, 0}}},
{"minecraft:pink_carpet", {true, false, false, false, {237, 141, 172}}},
{"minecraft:pink_concrete", {true, false, false, false, {213, 101, 142}}},
{"minecraft:pink_concrete_powder", {true, false, false, false, {228, 153, 181}}},
{"minecraft:pink_glazed_terracotta", {true, false, false, false, {235, 154, 181}}},
{"minecraft:pink_shulker_box", {true, false, false, false, {230, 121, 157}}},
{"minecraft:pink_stained_glass", {true, false, false, false, {242, 127, 165}}},
{"minecraft:pink_stained_glass_pane", {true, false, false, false, {233, 122, 159}}},
{"minecraft:pink_terracotta", {true, false, false, false, {161, 78, 78}}},
{"minecraft:pink_tulip", {false, false, false, false, {0, 0, 0}}},
{"minecraft:pink_wall_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:pink_wool", {true, false, false, false, {237, 141, 172}}},
{"minecraft:piston_head", {true, false, false, false, {154, 127, 87}}},
{"minecraft:piston", {true, false, false, false, {110, 104, 96}}},
{"minecraft:player_head", {false, false, false, false, {0, 0, 0}}},
{"minecraft:player_wall_head", {false, false, false, false, {0, 0, 0}}},
{"minecraft:podzol", {true, false, false, false, {91, 63, 24}}},
{"minecraft:polished_andesite", {true, false, false, false, {132, 134, 133}}},
{"minecraft:polished_andesite_slab", {true, false, false, false, {132, 134, 133}}},
{"minecraft:polished_andesite_stairs", {true, false, false, false, {132, 134, 133}}},
{"minecraft:polished_diorite", {true, false, false, false, {192, 193, 194}}},
{"minecraft:polished_diorite_slab", {true, false, false, false, {192, 193, 194}}},
{"minecraft:polished_diorite_stairs", {true, false, false, false, {192, 193, 194}}},
{"minecraft:polished_granite", {true, false, false, false, {154, 106, 89}}},
{"minecraft:polished_granite_slab", {true, false, false, false, {154, 106, 89}}},
{"minecraft:polished_granite_stairs", {true, false, false, false, {154, 106, 89}}},
{"minecraft:poppy", {false, false, false, false, {0, 0, 0}}},
{"minecraft:potatoes", {true, false, false, false, {84, 135, 47}}},
{"minecraft:potted_acacia_sapling", {true, false, false, false, {118, 117, 23}}},
{"minecraft:potted_allium", {true, false, false, false, {158, 137, 183}}},
{"minecraft:potted_azure_bluet", {true, false, false, false, {169, 204, 127}}},
{"minecraft:potted_bamboo", {true, false, false, false, {93, 144, 19}}},
{"minecraft:potted_birch_sapling", {true, false, false, false, {127, 160, 79}}},
{"minecraft:potted_blue_orchid", {true, false, false, false, {47, 162, 168}}},
{"minecraft:potted_brown_mushroom", {true, false, false, false, {153, 116, 92}}},
{"minecraft:potted_cactus", {true, false, false, false, {85, 127, 43}}},
{"minecraft:potted_cornflower", {true, false, false, false, {79, 121, 146}}},
{"minecraft:potted_dandelion", {true, false, false, false, {147, 172, 43}}},
{"minecraft:potted_dark_oak_sapling", {true, false, false, false, {61, 90, 30}}},
{"minecraft:potted_dead_bush", {true, false, false, false, {107, 78, 40}}},
{"minecraft:potted_fern", {true, true, false, false, {124, 124, 124}}},
{"minecraft:potted_jungle_sapling", {true, false, false, false, {47, 81, 16}}},
{"minecraft:potted_lily_of_the_valley", {true, false, false, false, {123, 174, 95}}},
{"minecraft:potted_oak_sapling", {true, false, false, false, {77, 106, 40}}},
{"minecraft:potted_orange_tulip", {true, false, false, false, {93, 142, 30}}},
{"minecraft:potted_oxeye_daisy", {true, false, false, false, {179, 202, 143}}},
{"minecraft:potted_pink_tulip", {true, false, false, false, {99, 157, 78}}},
{"minecraft:potted_poppy", {true, false, false, false, {128, 64, 37}}},
{"minecraft:potted_red_mushroom", {true, false, false, false, {216, 75, 67}}},
{"minecraft:potted_red_tulip", {true, false, false, false, {89, 128, 32}}},
{"minecraft:potted_spruce_sapling", {true, false, false, false, {44, 60, 36}}},
{"minecraft:potted_white_tulip", {true, false, false, false, {93, 164, 71}}},
{"minecraft:potted_wither_rose", {true, false, false, false, {41, 44, 23}}},
{"minecraft:powered_rail", {true, false, false, false, {137, 109, 74}}},
{"minecraft:prismarine_bricks", {true, false, false, false, {99, 171, 158}}},
{"minecraft:prismarine_brick_slab", {true, false, false, false, {99, 171, 158}}},
{"minecraft:prismarine_brick_stairs", {true, false, false, false, {99, 171, 158}}},
{"minecraft:prismarine", {true, false, false, false, {99, 156, 151}}},
{"minecraft:prismarine_slab", {true, false, false, false, {99, 156, 151}}},
{"minecraft:prismarine_stairs", {true, false, false, false, {99, 156, 151}}},
{"minecraft:prismarine_wall", {true, false, false, false, {99, 156, 151}}},
{"minecraft:pumpkin", {true, false, false, false, {198, 118, 24}}},
{"minecraft:pumpkin_stem", {true, true, false, false, {154, 154, 154}}},
{"minecraft:purple_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:purple_bed", {false, false, false, false, {0, 0, 0}}},
{"minecraft:purple_carpet", {true, false, false, false, {121, 42, 172}}},
{"minecraft:purple_concrete", {true, false, false, false, {100, 31, 156}}},
{"minecraft:purple_concrete_powder", {true, false, false, false, {131, 55, 177}}},
{"minecraft:purple_glazed_terracotta", {true, false, false, false, {109, 48, 152}}},
{"minecraft:purple_shulker_box", {true, false, false, false, {103, 32, 156}}},
{"minecraft:purple_stained_glass", {true, false, false, false, {127, 63, 178}}},
{"minecraft:purple_stained_glass_pane", {true, false, false, false, {122, 61, 171}}},
{"minecraft:purple_terracotta", {true, false, false, false, {118, 70, 86}}},
{"minecraft:purple_wall_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:purple_wool", {true, false, false, false, {121, 42, 172}}},
{"minecraft:purpur_block", {true, false, false, false, {169, 125, 169}}},
{"minecraft:purpur_pillar", {true, false, false, false, {171, 129, 171}}},
{"minecraft:purpur_slab", {true, false, false, false, {169, 125, 169}}},
{"minecraft:purpur_stairs", {true, false, false, false, {169, 125, 169}}},
{"minecraft:quartz_block", {true, false, false, false, {235, 229, 222}}},
{"minecraft:quartz_pillar", {true, false, false, false, {235, 230, 224}}},
{"minecraft:quartz_slab", {true, false, false, false, {235, 229, 222}}},
{"minecraft:quartz_stairs", {true, false, false, false, {235, 229, 222}}},
{"minecraft:rail", {true, false, false, false, {125, 111, 88}}},
{"minecraft:red_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:red_bed", {false, false, false, false, {0, 0, 0}}},
{"minecraft:red_carpet", {true, false, false, false, {160, 39, 34}}},
{"minecraft:red_concrete", {true, false, false, false, {142, 32, 32}}},
{"minecraft:red_concrete_powder", {true, false, false, false, {168, 54, 50}}},
{"minecraft:red_glazed_terracotta", {true, false, false, false, {181, 59, 53}}},
{"minecraft:red_mushroom_block", {true, false, false, false, {200, 46, 45}}},
{"minecraft:red_mushroom", {false, false, false, false, {0, 0, 0}}},
{"minecraft:red_nether_bricks", {true, false, false, false, {69, 7, 9}}},
{"minecraft:red_nether_brick_slab", {true, false, false, false, {69, 7, 9}}},
{"minecraft:red_nether_brick_stairs", {true, false, false, false, {69, 7, 9}}},
{"minecraft:red_nether_brick_wall", {true, false, false, false, {69, 7, 9}}},
{"minecraft:red_sand", {true, false, false, false, {190, 102, 33}}},
{"minecraft:red_sandstone", {true, false, false, false, {181, 97, 31}}},
{"minecraft:red_sandstone_slab", {true, false, false, false, {181, 97, 31}}},
{"minecraft:red_sandstone_stairs", {true, false, false, false, {181, 97, 31}}},
{"minecraft:red_sandstone_wall", {true, false, false, false, {181, 97, 31}}},
{"minecraft:red_shulker_box", {true, false, false, false, {140, 31, 30}}},
{"minecraft:red_stained_glass", {true, false, false, false, {153, 51, 51}}},
{"minecraft:red_stained_glass_pane", {true, false, false, false, {147, 48, 48}}},
{"minecraft:redstone_block", {true, false, false, false, {175, 24, 5}}},
{"minecraft:redstone_lamp", {true, false, false, false, {95, 54, 30}}},
{"minecraft:redstone_ore", {true, false, false, false, {133, 107, 107}}},
{"minecraft:redstone_torch", {false, false, false, false, {0, 0, 0}}},
{"minecraft:redstone_wall_torch", {false, false, false, false, {0, 0, 0}}},
{"minecraft:redstone_wire", {true, false, false, false, {175, 24, 5}}},
{"minecraft:red_terracotta", {true, false, false, false, {143, 61, 46}}},
{"minecraft:red_tulip", {false, false, false, false, {0, 0, 0}}},
{"minecraft:red_wall_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:red_wool", {true, false, false, false, {160, 39, 34}}},
{"minecraft:repeater", {true, false, false, false, {160, 157, 156}}},
{"minecraft:repeating_command_block", {true, false, false, false, {129, 111, 176}}},
{"minecraft:rose_bush", {true, false, false, false, {131, 66, 37}}},
{"minecraft:sand", {true, false, false, false, {219, 207, 163}}},
{"minecraft:sandstone", {true, false, false, false, {223, 214, 170}}},
{"minecraft:sandstone_slab", {true, false, false, false, {223, 214, 170}}},
{"minecraft:sandstone_stairs", {true, false, false, false, {223, 214, 170}}},
{"minecraft:sandstone_wall", {true, false, false, false, {223, 214, 170}}},
{"minecraft:scaffolding", {true, false, false, false, {174, 134, 80}}},
{"minecraft:seagrass", {false, false, false, false, {0, 0, 0}}},
{"minecraft:sea_lantern", {true, false, false, false, {172, 199, 190}}},
{"minecraft:sea_pickle", {true, false, false, false, {90, 97, 39}}},
{"minecraft:shulker_box", {true, false, false, false, {139, 96, 139}}},
{"minecraft:sign", {true, false, false, false, {162, 130, 78}}},
{"minecraft:skeleton_skull", {false, false, false, false, {0, 0, 0}}},
{"minecraft:skeleton_wall_skull", {false, false, false, false, {0, 0, 0}}},
{"minecraft:slime_block", {true, false, false, false, {111, 192, 91}}},
{"minecraft:smithing_table", {true, false, false, false, {57, 58, 70}}},
{"minecraft:smoker", {true, false, false, false, {84, 82, 80}}},
{"minecraft:smooth_quartz", {true, false, false, false, {235, 229, 222}}},
{"minecraft:smooth_quartz_slab", {true, false, false, false, {235, 229, 222}}},
{"minecraft:smooth_quartz_stairs", {true, false, false, false, {235, 229, 222}}},
{"minecraft:smooth_red_sandstone", {true, false, false, false, {181, 97, 31}}},
{"minecraft:smooth_red_sandstone_slab", {true, false, false, false, {181, 97, 31}}},
{"minecraft:smooth_red_sandstone_stairs", {true, false, false, false, {181, 97, 31}}},
{"minecraft:smooth_sandstone", {true, false, false, false, {223, 214, 170}}},
{"minecraft:smooth_sandstone_slab", {true, false, false, false, {223, 214, 170}}},
{"minecraft:smooth_sandstone_stairs", {true, false, false, false, {223, 214, 170}}},
{"minecraft:smooth_stone", {true, false, false, false, {158, 158, 158}}},
{"minecraft:smooth_stone_slab", {true, false, false, false, {158, 158, 158}}},
{"minecraft:snow_block", {true, false, false, false, {249, 254, 254}}},
{"minecraft:snow", {true, false, false, false, {249, 254, 254}}},
{"minecraft:soul_sand", {true, false, false, false, {81, 62, 50}}},
{"minecraft:spawner", {true, false, false, false, {36, 46, 62}}},
{"minecraft:sponge", {true, false, false, false, {195, 192, 74}}},
{"minecraft:spruce_button", {false, false, false, false, {0, 0, 0}}},
{"minecraft:spruce_door", {true, false, false, false, {106, 80, 48}}},
{"minecraft:spruce_fence_gate", {true, false, false, false, {114, 84, 48}}},
{"minecraft:spruce_fence", {true, false, false, false, {114, 84, 48}}},
{"minecraft:spruce_leaves", {true, false, true, false, {126, 126, 126}}},
{"minecraft:spruce_log", {true, false, false, false, {108, 80, 46}}},
{"minecraft:spruce_planks", {true, false, false, false, {114, 84, 48}}},
{"minecraft:spruce_pressure_plate", {true, false, false, false, {114, 84, 48}}},
{"minecraft:spruce_sapling", {true, false, false, false, {44, 60, 36}}},
{"minecraft:spruce_sign", {true, false, false, false, {114, 84, 48}}},
{"minecraft:spruce_slab", {true, false, false, false, {114, 84, 48}}},
{"minecraft:spruce_stairs", {true, false, false, false, {114, 84, 48}}},
{"minecraft:spruce_trapdoor", {true, false, false, false, {103, 79, 47}}},
{"minecraft:spruce_wall_sign", {false, false, false, false, {0, 0, 0}}},
{"minecraft:spruce_wood", {true, false, false, false, {58, 37, 16}}},
{"minecraft:sticky_piston", {true, false, false, false, {110, 104, 96}}},
{"minecraft:stone", {true, false, false, false, {125, 125, 125}}},
{"minecraft:stonecutter", {true, false, false, false, {123, 118, 111}}},
{"minecraft:stone_bricks", {true, false, false, false, {122, 121, 122}}},
{"minecraft:stone_brick_slab", {true, false, false, false, {122, 121, 122}}},
{"minecraft:stone_brick_stairs", {true, false, false, false, {122, 121, 122}}},
{"minecraft:stone_brick_wall", {true, false, false, false, {122, 121, 122}}},
{"minecraft:stone_button", {false, false, false, false, {0, 0, 0}}},
{"minecraft:stone_pressure_plate", {true, false, false, false, {125, 125, 125}}},
{"minecraft:stone_slab", {true, false, false, false, {125, 125, 125}}},
{"minecraft:stone_stairs", {true, false, false, false, {125, 125, 125}}},
{"minecraft:stripped_acacia_log", {true, false, false, false, {166, 91, 51}}},
{"minecraft:stripped_acacia_wood", {true, false, false, false, {174, 92, 59}}},
{"minecraft:stripped_birch_log", {true, false, false, false, {191, 171, 116}}},
{"minecraft:stripped_birch_wood", {true, false, false, false, {196, 176, 118}}},
{"minecraft:stripped_dark_oak_log", {true, false, false, false, {65, 44, 22}}},
{"minecraft:stripped_dark_oak_wood", {true, false, false, false, {96, 76, 49}}},
{"minecraft:stripped_jungle_log", {true, false, false, false, {165, 122, 81}}},
{"minecraft:stripped_jungle_wood", {true, false, false, false, {171, 132, 84}}},
{"minecraft:stripped_oak_log", {true, false, false, false, {160, 129, 77}}},
{"minecraft:stripped_oak_wood", {true, false, false, false, {177, 144, 86}}},
{"minecraft:stripped_spruce_log", {true, false, false, false, {105, 80, 46}}},
{"minecraft:stripped_spruce_wood", {true, false, false, false, {115, 89, 52}}},
{"minecraft:structure_block", {true, false, false, false, {88, 74, 90}}},
{"minecraft:structure_void", {false, false, false, false, {0, 0, 0}}},
{"minecraft:sugar_cane", {true, false, false, false, {148, 192, 101}}},
{"minecraft:sunflower", {true, false, false, false, {246, 196, 54}}},
{"minecraft:sweet_berry_bush", {true, false, false, false, {68, 77, 50}}},
{"minecraft:tall_grass", {true, true, false, false, {151, 149, 151}}},
{"minecraft:tall_seagrass", {true, false, false, false, {59, 139, 14}}},
{"minecraft:terracotta", {true, false, false, false, {152, 94, 67}}},
{"minecraft:tnt", {true, false, false, false, {142, 62, 53}}},
{"minecraft:torch", {false, false, false, false, {0, 0, 0}}},
{"minecraft:trapped_chest", {true, false, false, false, {162, 130, 78}}},
{"minecraft:tripwire_hook", {false, false, false, false, {0, 0, 0}}},
{"minecraft:tripwire", {false, false, false, false, {0, 0, 0}}},
{"minecraft:tube_coral_block", {true, false, false, false, {49, 87, 206}}},
{"minecraft:tube_coral_fan", {false, false, false, false, {0, 0, 0}}},
{"minecraft:tube_coral", {false, false, false, false, {0, 0, 0}}},
{"minecraft:tube_coral_wall_fan", {false, false, false, false, {0, 0, 0}}},
{"minecraft:turtle_egg", {true, false, false, false, {228, 226, 191}}},
{"minecraft:vine", {true, true, false, false, {116, 116, 116}}},
{"minecraft:void_air", {false, false, false, false, {0, 0, 0}}},
{"minecraft:wall_sign", {false, false, false, false, {0, 0, 0}}},
{"minecraft:wall_torch", {false, false, false, false, {0, 0, 0}}},
{"minecraft:water", {true, false, false, true, {177, 177, 177}}},
{"minecraft:wet_sponge", {true, false, false, false, {171, 181, 70}}},
{"minecraft:wheat", {true, false, false, false, {166, 151, 73}}},
{"minecraft:white_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:white_bed", {false, false, false, false, {0, 0, 0}}},
{"minecraft:white_carpet", {true, false, false, false, {233, 236, 236}}},
{"minecraft:white_concrete", {true, false, false, false, {207, 213, 214}}},
{"minecraft:white_concrete_powder", {true, false, false, false, {225, 227, 227}}},
{"minecraft:white_glazed_terracotta", {true, false, false, false, {188, 212, 202}}},
{"minecraft:white_shulker_box", {true, false, false, false, {215, 220, 221}}},
{"minecraft:white_stained_glass", {true, false, false, false, {255, 255, 255}}},
{"minecraft:white_stained_glass_pane", {true, false, false, false, {246, 246, 246}}},
{"minecraft:white_terracotta", {true, false, false, false, {209, 178, 161}}},
{"minecraft:white_tulip", {false, false, false, false, {0, 0, 0}}},
{"minecraft:white_wall_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:white_wool", {true, false, false, false, {233, 236, 236}}},
{"minecraft:wither_rose", {false, false, false, false, {0, 0, 0}}},
{"minecraft:wither_skeleton_skull", {false, false, false, false, {0, 0, 0}}},
{"minecraft:wither_skeleton_wall_skull", {false, false, false, false, {0, 0, 0}}},
{"minecraft:yellow_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:yellow_bed", {false, false, false, false, {0, 0, 0}}},
{"minecraft:yellow_carpet", {true, false, false, false, {248, 197, 39}}},
{"minecraft:yellow_concrete", {true, false, false, false, {240, 175, 21}}},
{"minecraft:yellow_concrete_powder", {true, false, false, false, {232, 199, 54}}},
{"minecraft:yellow_glazed_terracotta", {true, false, false, false, {234, 192, 88}}},
{"minecraft:yellow_shulker_box", {true, false, false, false, {248, 188, 29}}},
{"minecraft:yellow_stained_glass", {true, false, false, false, {229, 229, 51}}},
{"minecraft:yellow_stained_glass_pane", {true, false, false, false, {221, 221, 48}}},
{"minecraft:yellow_terracotta", {true, false, false, false, {186, 133, 35}}},
{"minecraft:yellow_wall_banner", {false, false, false, false, {0, 0, 0}}},
{"minecraft:yellow_wool", {true, false, false, false, {248, 197, 39}}},
{"minecraft:zombie_head", {false, false, false, false, {0, 0, 0}}},
{"minecraft:zombie_wall_head", {false, false, false, false, {0, 0, 0}}},

View file

@ -1,40 +0,0 @@
/*
Copyright (c) 2018, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#pragma once
#include <cstdint>
namespace MinedMap {
namespace Resource {
struct Color {
uint8_t r, g, b, a;
};
}
}

View file

@ -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"),

View file

@ -1,37 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#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) {}
};

View file

@ -1,47 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#pragma once
#include <algorithm>
#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);
}
}

View file

@ -1,57 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#pragma once
#include "../NBT/CompoundTag.hpp"
#include "../Resource/Biome.hpp"
#include "../Resource/BlockType.hpp"
namespace MinedMap {
namespace World {
struct Block {
const Resource::BlockType *type;
unsigned height;
uint8_t blockLight;
uint8_t biome;
Resource::Color getColor() const {
if (!type || !type->opaque)
return Resource::Color {};
return (Resource::BIOMES[biome] ?: Resource::BIOME_DEFAULT)->getBlockColor(type, height);
}
operator bool() const {
return type;
}
};
}
}

View file

@ -1,139 +0,0 @@
/*
Copyright (c) 2015-2019, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#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) {
level = assertValue(data->getRoot().get<NBT::CompoundTag>("Level"));
std::shared_ptr<const NBT::ListTag> 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);
else if (biomesIntArray && biomesIntArray->getLength() == SIZE*SIZE)
biomeIntsPre115 = std::move(biomesIntArray);
else if (biomesByteArray && biomesByteArray->getLength() == SIZE*SIZE)
biomeBytes = std::move(biomesByteArray);
else
throw std::invalid_argument("corrupt biome data");
auto dataVersionTag = data->getRoot().get<NBT::IntTag>("DataVersion");
uint32_t dataVersion = dataVersionTag ? dataVersionTag->getValue() : 0;
for (auto &sTag : *sectionsTag) {
auto s = std::dynamic_pointer_cast<const NBT::CompoundTag>(sTag);
std::unique_ptr<Section> section = Section::makeSection(s, dataVersion);
size_t Y = section->getY();
sections.resize(Y);
sections.push_back(std::move(section));
}
}
uint8_t Chunk::getBiome(size_t x, size_t y, size_t z) const {
if (x > SIZE || y > MAXY || z > SIZE)
throw std::invalid_argument("corrupt chunk data");
if (biomeInts)
return biomeInts->getValue((y/BGROUP)*BSIZE*BSIZE + (z/BGROUP)*BSIZE + (x/BGROUP));
else if (biomeIntsPre115)
return biomeIntsPre115->getValue(z*SIZE + x);
else
return biomeBytes->getValue(z*SIZE + x);
}
bool Chunk::getBlock(Block *block, const Section *section, size_t x, size_t y, size_t z, uint8_t prev_light) const {
if (block->height > 0)
return false;
const Resource::BlockType *type = section->getBlockStateAt(x, y, z);
if (!type || !type->opaque)
return false;
if (!block->type) {
block->type = type;
block->blockLight = prev_light;
block->biome = getBiome(x, y, z);
}
if (type->blue)
return false;
block->height = SIZE*section->getY() + y;
return true;
}
Chunk::Blocks Chunk::getTopLayer() const {
size_t done = 0;
Blocks ret = {};
uint8_t prev_light[SIZE][SIZE] = {};
for (ssize_t Y = sections.size() - 1; Y >= 0; Y--) {
if (done == SIZE*SIZE)
break;
if (!sections[Y]) {
std::memset(prev_light, 0, sizeof(prev_light));
continue;
}
const Section *section = sections[Y].get();
for (ssize_t y = SIZE-1; y >= 0; y--) {
if (done == SIZE*SIZE)
break;
for (size_t z = 0; z < SIZE; z++) {
for (size_t x = 0; x < SIZE; x++) {
if (getBlock(&ret.blocks[x][z], section, x, y, z, prev_light[x][z]))
done++;
prev_light[x][z] = section->getBlockLightAt(x, y, z);
}
}
}
}
return ret;
}
}
}

View file

@ -1,88 +0,0 @@
/*
Copyright (c) 2015-2018, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#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:
static const size_t SIZE = Section::SIZE;
static const size_t MAXY = 256;
static const size_t BGROUP = 4;
static const size_t BSIZE = SIZE / BGROUP;
static const size_t BMAXY = MAXY / BGROUP;
struct Blocks {
Block blocks[SIZE][SIZE];
};
private:
std::shared_ptr<const NBT::CompoundTag> level;
std::vector<std::unique_ptr<Section>> sections;
std::shared_ptr<const NBT::ByteArrayTag> biomeBytes;
std::shared_ptr<const NBT::IntArrayTag> biomeIntsPre115;
std::shared_ptr<const NBT::IntArrayTag> biomeInts;
uint8_t getBiome(size_t x, size_t y, size_t z) const;
bool getBlock(Block *block, const Section *section, size_t x, size_t y, size_t z, uint8_t prev_light) const;
public:
Chunk(const ChunkData *data);
const NBT::CompoundTag & getLevel() const {
return *level;
}
const Resource::BlockType * getBlockStateAt(size_t x, size_t y, size_t z) const {
size_t Y = y / SIZE;
if (Y >= sections.size() || !sections[Y])
return nullptr;
return sections[Y]->getBlockStateAt(x, y % SIZE, z);
}
Blocks getTopLayer() const;
};
}
}

View file

@ -1,97 +0,0 @@
/*
Copyright (c) 2015-2018, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#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));
}
}
}

View file

@ -1,60 +0,0 @@
/*
Copyright (c) 2015-2018, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#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 NBT::CompoundTag & getRoot() const {
return *root;
}
};
}
}

View file

@ -1,56 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#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);
}
}
}

View file

@ -1,52 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#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;
};
}
}

View file

@ -1,105 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#include "Region.hpp"
#include <fstream>
#include <iostream>
namespace MinedMap {
namespace World {
Region::ChunkMap Region::processHeader(const uint8_t header[4096]) {
ChunkMap map;
for (size_t z = 0; z < 32; z++) {
for (size_t x = 0; x < 32; x++) {
const uint8_t *p = &header[128*z + x*4];
size_t offset = (p[0] << 16) | (p[1] << 8) | p[2];
if (!offset)
continue;
size_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::failbit | 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()) {
auto it = chunkMap.find(i);
if (it == chunkMap.end()) {
file.ignore(4096);
i++;
continue;
}
size_t x, z, len;
std::tie(x, z, len) = it->second;
if (seen[x][z])
throw std::invalid_argument("duplicate chunk");
seen[x][z] = true;
c++;
uint8_t buffer[len * 4096];
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");
}
}
}

View file

@ -1,60 +0,0 @@
/*
Copyright (c) 2015, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#pragma once
#include "Chunk.hpp"
#include <functional>
#include <memory>
#include <stdexcept>
#include <tuple>
#include <unordered_map>
namespace MinedMap {
namespace World {
class Region {
public:
static const size_t SIZE = 32;
typedef std::function<void (size_t, size_t, const ChunkData *)> ChunkVisitor;
Region() = delete;
private:
typedef std::tuple<size_t, size_t, size_t> ChunkDesc;
typedef std::unordered_map<size_t, ChunkDesc> ChunkMap;
static ChunkMap processHeader(const uint8_t header[4096]);
public:
static void visitChunks(const char *filename, const ChunkVisitor &visitor);
};
}
}

View file

@ -1,127 +0,0 @@
/*
Copyright (c) 2015-2019, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#include "Section.hpp"
#include "../NBT/ByteTag.hpp"
#include "../NBT/StringTag.hpp"
#include "../Util.hpp"
#include <cstdio>
namespace MinedMap {
namespace World {
Section::Section(const std::shared_ptr<const NBT::CompoundTag> &section) {
Y = assertValue(section->get<NBT::ByteTag>("Y"))->getValue();
blockLight = section->get<NBT::ByteArrayTag>("BlockLight");
}
const Resource::BlockType * Section::getBlockStateAt(size_t, size_t, size_t) const {
return nullptr;
}
std::unique_ptr<Section> Section::makeSection(const std::shared_ptr<const NBT::CompoundTag> &section, uint32_t 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, 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(size_t x, size_t y, size_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> &section,
std::shared_ptr<const NBT::LongArrayTag> &&blockStates0,
const std::shared_ptr<const NBT::ListTag> &paletteData,
uint32_t dataVersion
) : Section(section), blockStates(blockStates0) {
if (blockStates->getLength() % 64)
throw std::invalid_argument("corrupt section data");
bits = blockStates->getLength() / 64;
if (bits > 16)
throw std::invalid_argument("unsupported block state bits");
mask = (1u << bits) - 1;
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);
}
}
const Resource::BlockType * PaletteSection::getBlockStateAt(size_t x, size_t y, size_t z) const {
size_t index = bits * getIndex(x, y, z);
size_t pos = index >> 3;
size_t shift = index & 7;
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);
}
}
}

View file

@ -1,137 +0,0 @@
/*
Copyright (c) 2015-2019, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#pragma once
#include "../NBT/ByteArrayTag.hpp"
#include "../NBT/CompoundTag.hpp"
#include "../NBT/ListTag.hpp"
#include "../NBT/LongArrayTag.hpp"
#include "../Resource/BlockType.hpp"
#include <cstdint>
#include <stdexcept>
namespace MinedMap {
namespace World {
class Section {
public:
static const size_t SIZE = 16;
private:
size_t Y;
std::shared_ptr<const NBT::ByteArrayTag> blockLight;
protected:
static size_t getIndex(size_t x, size_t y, size_t z) {
if (x >= SIZE || y >= SIZE || z >= SIZE)
throw std::range_error("Chunk::getIndex(): bad coordinates");
return SIZE*SIZE*y + SIZE*z + x;
}
static uint8_t getHalf(const uint8_t *v, size_t x, size_t y, size_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> &section);
public:
virtual ~Section() {}
size_t getY() const { return Y; };
virtual const Resource::BlockType * getBlockStateAt(size_t x, size_t y, size_t z) const;
uint8_t getBlockLightAt(size_t x, size_t y, size_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> &section, 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(size_t x, size_t y, size_t z) const {
return blocks->getValue(getIndex(x, y, z));
}
uint8_t getDataAt(size_t x, size_t y, size_t z) const {
return getHalf(data->getPointer(), x, y, z);
}
public:
LegacySection(
const std::shared_ptr<const NBT::CompoundTag> &section,
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(size_t x, size_t y, size_t z) const;
};
class PaletteSection : public Section {
private:
std::shared_ptr<const NBT::LongArrayTag> blockStates;
std::vector<const Resource::BlockType *> palette;
size_t bits;
uint16_t mask;
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> &section,
std::shared_ptr<const NBT::LongArrayTag> &&blockStates0,
const std::shared_ptr<const NBT::ListTag> &paletteData,
uint32_t dataVersion
);
virtual const Resource::BlockType * getBlockStateAt(size_t x, size_t y, size_t z) const;
};
}
}

327
src/core/common.rs Normal file
View 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,
);
}

View 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
View 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 &regions.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
View 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, &regions).run()?;
let tiles = TileMipmapper::new(config, &regions).run()?;
EntityCollector::new(config, &regions).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
View 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))
}
}

View 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
View 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
View 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
View 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
View 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(&region_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, &region_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
View 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
View file

@ -0,0 +1,4 @@
//! Input/output functions
pub mod fs;
pub mod storage;

73
src/io/storage.rs Normal file
View 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()))
}

21
src/main.rs Normal file
View file

@ -0,0 +1,21 @@
#![doc = env!("CARGO_PKG_DESCRIPTION")]
#![warn(missing_docs)]
#![warn(clippy::missing_docs_in_private_items)]
#[cfg(feature = "jemalloc-auto")]
extern crate minedmap_default_alloc;
mod core;
mod io;
mod util;
mod world;
use minedmap_nbt as nbt;
use minedmap_resource as resource;
use minedmap_types as types;
use anyhow::Result;
fn main() -> Result<()> {
core::cli()
}

View file

@ -1,55 +0,0 @@
/*
Copyright (c) 2018, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#include "Buffer.hpp"
#include "GZip.hpp"
#include "Util.hpp"
#include "NBT/Tag.hpp"
#include <cstdio>
#include <iostream>
#include <stdexcept>
int main(int argc, char *argv[]) {
using namespace MinedMap;
if (argc != 2) {
std::fprintf(stderr, "Usage: %s <nbtfile>\n", argv[0]);
return 1;
}
std::vector<uint8_t> buffer = readGZip(argv[1]);
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");
std::cout << *tag.second << std::endl;
return 0;
}

View file

@ -1,51 +0,0 @@
/*
Copyright (c) 2018, Matthias Schiffer <mschiffer@universe-factory.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
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.
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.
*/
#include "Buffer.hpp"
#include "GZip.hpp"
#include "Util.hpp"
#include "NBT/Tag.hpp"
#include "World/Region.hpp"
#include <cstdio>
#include <iostream>
int main(int argc, char *argv[]) {
using namespace MinedMap;
if (argc != 2) {
std::fprintf(stderr, "Usage: %s <regionfile>\n", argv[0]);
return 1;
}
World::Region::visitChunks(argv[1], [&] (size_t X, size_t Z, const World::ChunkData *chunk) {
std::cout << "Chunk(" << X << ", " << Z << "): "
<< chunk->getRoot() << std::endl;
});
return 0;
}

Some files were not shown because too many files have changed in this diff Show more