Files
onion-mc/tools/migrate-player.sh
T
logan 769af6fa2d chore(tools): add FTB Chunks -> Xaero's map reveal migration tooling
The swap does not carry players' explored terrain across. FTB Chunks stores no
rendered map (its region zips hold chunks.dat plus five data PNGs, one of which
packs height into an image channel), and Xaero's region format is closed and now
version 6 with per-pixel NBT blobs, so the community converters no longer work.
Rather than convert pixels, prune a copy of the world save down to exactly the
chunks a player revealed and let Xaero's own generator map it.

Validated against real data: extracting the 167-region / 98,170-chunk mask
cross-checks clean against FTB's own pixel data, and Anvil pruning round-trips
byte-identically (15.7 MB -> 471 KB on a live region file, slot-to-chunk mapping
confirmed against 1024 chunk NBT headers).

Deferred for now: needs the world save and one singleplayer pass per player.
See analysis/FTB-XAERO-MAP-MIGRATION.md.
2026-09-30 22:04:56 -04:00

132 lines
4.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# End-to-end driver for the FTB Chunks -> Xaero's World Map reveal transplant.
#
# See analysis/FTB-XAERO-MAP-MIGRATION.md for the reasoning. This script does the
# parts that can be automated; the singleplayer pass in step 3 has to be done by hand
# on a machine that can run the pack.
#
# Usage:
# migrate-player.sh <player> <ftbchunks-data> <world-dir> [--dimension <id>] [--work <dir>]
#
# <player> label used for output paths, e.g. Stirwick
# <ftbchunks-data> that player's local/ftbchunks/data/<uuid>/, or a bare zip/dir
# of region files, or an already-extracted mask .json
# <world-dir> the world save containing region/
#
# Example:
# tools/migrate-player.sh Stirwick ~/Stirwick-ftbchunks ~/server/world \
# --dimension minecraft:overworld --work /var/tmp/xaero-migration
set -euo pipefail
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [ $# -lt 3 ]; then
sed -n '2,20p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
exit 2
fi
player="$1"; ftb="$2"; world="$3"; shift 3
dimension="minecraft:overworld"
work="$(pwd)/xaero-migration"
while [ $# -gt 0 ]; do
case "$1" in
--dimension) dimension="$2"; shift 2 ;;
--work) work="$2"; shift 2 ;;
*) echo "unknown option: $1" >&2; exit 2 ;;
esac
done
out="$work/$player"
mask="$out/mask.json"
sp="$out/sp-world"
echo "player : $player"
echo "ftb data : $ftb"
echo "world : $world"
echo "dimension : $dimension"
echo "output : $out"
echo
if [ ! -d "$world/region" ]; then
echo "error: $world/region not found" >&2
exit 1
fi
mkdir -p "$out"
echo "== 1/2 extracting reveal mask =="
if [ "${ftb##*.}" = "json" ]; then
cp -f "$ftb" "$mask"
echo " using prebuilt mask: $ftb"
python3 - "$mask" <<'PY'
import json, sys
m = json.load(open(sys.argv[1]))
dims = m["dimensions"]
print(f" {m['total_chunks']:,} chunks / "
f"{sum(d['region_count'] for d in dims.values())} regions / "
f"dimension(s) {list(dims)}")
PY
else
python3 "$here/ftbchunks-reveal.py" "$ftb" --out "$mask" --dimension "$dimension" --verify
fi
echo
echo "== 2/2 pruning the world to those chunks (dry run first) =="
python3 "$here/prune-world-to-reveal.py" "$world" "$mask" "$sp" \
--dimension "$dimension" --minimal --dry-run
echo
python3 "$here/prune-world-to-reveal.py" "$world" "$mask" "$sp" \
--dimension "$dimension" --minimal
cat <<EOF
Done. Pruned world: $sp
Now, by hand.
3. Copy $sp to a machine that can run the pack and open it in SINGLEPLAYER once,
as a world folder (the directory holding level.dat and region/):
<game dir>/saves/Stirwick-reveal/
Open it, open the map (M), let it fill, then quit. Xaero's maps what is in the
save, and this save holds only the revealed chunks. Do not walk around --
Minecraft regenerates chunks near the login position and they will show up.
Where the result lands: look under <game dir>/xaero/world-map/ for a folder that
is not Multiplayer_*, e.g. World/null/<map>/. Sort by modification time if
unsure -- those files were just written.
SANITY CHECK: you should end up with one zip per region in the mask
($(python3 -c "import json;print(sum(d['region_count'] for d in json.load(open('$mask'))['dimensions'].values()))") for $player). A handful means it only mapped the spawn area and
you should use 'Convert All Regions' in the world map settings, or report back.
4. Clean the HARVEST before anything else -- this is the step that removes terrain
Minecraft regenerated around the login point. Run it on the harvested folder,
NOT on the player's own map folder:
python3 $here/filter-xaero-regions.py \\
"<game dir>/xaero/world-map/World/null/<map>/" \\
$mask --dimension $dimension
Check the list, then re-run with --delete.
5. Merge the surviving <rx>_<rz>.zip files into the player's existing map folder,
WITHOUT overwriting anything already there (-n), so any terrain they have
explored since the swap is kept:
cp -n <harvested>/*.zip \\
"<their game dir>/xaero/world-map/Multiplayer_advilplug.top/null/<their map instance>/"
The right instance is the mw\$... folder there. If the player has not joined the
server since the swap, have them join once first so Xaero's creates it.
6. Delete $out -- scratch, usually a few hundred MB.
Per-dimension: repeat with --dimension <id> and the matching map folder
(null = overworld, DIM-1 = nether, DIM1 = end).
Other players: each needs their own local/ftbchunks/data/<uuid>/; there is no
server-side copy of this data.
EOF