minibia-tools

Minibia-tools

minibia-tools.html (the World Compendium page) loads data/derived/compendium.json and data/icons.json at runtime, plus the per-floor spawn map terrain images (data/derived/minimap-z*.png).

Updating game data

  1. Put fresh downloads in data/raw/ - only files exactly as downloaded from minibia.com, same names (the list, with where each comes from, is in data/raw/README.md):
    • library.json — monsters, items, npcs, spells, runes, achievements. Exported manually (the Library page is Cloudflare-gated).
    • monster-spawns.json, npc-trades.json, definitions.json, npc-poi.json — the /play/*.json files.
    • minimap.bin.gz — the client’s minimap, kept gzipped as served; used for the Spawn Map terrain, stairs and pathfinding.
    • python scripts/fetch-live.py can download all but library.json in one go, if automated access is OK with the site. Hand-made inputs (e.g. floor-connection-labels.json) live in data/manual/, not here.
  2. See what changed since the last scrape (and whether a download was missed): python scripts/diff-raw.py compares data/raw/ with origin/main - which files are updated/unchanged/missing; monsters (loot per item, attacks/damage, stats), items, NPCs, spells, runes, achievements field by field; spawn points per monster; item definitions added/removed/changed with the owner’s notes; the creatures the game files already have corpses for but that aren’t in the library or spawns yet (new creatures waiting for their place in the world); and where the map changed. It adds the update to data/derived/changes.json, shown on the site’s hidden Data versions page (the link at the bottom of the page, or #versions; every update is kept and selectable, and each changed map area has a button that shows it on the map), plus a Markdown copy in data/changes/<date>.md.
  3. Regenerate the derived data, in this order:
    python scripts/gen-minimap.py            # minimap.bin.gz -> data/derived/minimap-z*.png + minimap.meta.json
    python scripts/build-data.py             # data/raw/*  -> data/derived/compendium.json (+ merchant.json, CHANGELOG.md)
    python scripts/gen-floor-plausibility.py # appends a floor bitmask to every spawnMap point (needs the two above)
    python scripts/gen-floor-connections.py  # detects stairs/ladders/holes and guesses which floor(s) each connects to (needs the two above)
    python scripts/gen-known-floors.py path/to/tibia-export.json  # optional: known spawn floors from a real-Tibia spawn export (see below)
    
  4. Commit data/raw/, data/derived/, data/changes/ and the updated CHANGELOG.md. GitHub Pages picks it up — no other build step.

build-data.py also writes data/derived/merchant.json (for a merchant-ledger page this repo doesn’t have yet) and .js twins of both (window.__MINIBIA_COMPENDIUM__ = ... etc., for loading via <script src> instead of fetch() when a page needs to work over file://). data/raw/npc-trades.json and data/raw/definitions.json are currently empty placeholders ({}) — build-data.py requires them to exist, but nothing here reads merchant.json yet.

scripts/gen-floor-plausibility.py (not one of the original 4 scripts — added to restore the “Hide implausible floors” toggle, which the switch to raw-cluster spawn data had silently turned into a no-op) computes, per spawn point, which of the 16 floors are plausible from the terrain alone — void detection, then a walkability classification derived from the client’s .dat minimap-color/not-walkable data (BLOCKING_IDX in the script), checked over a window around the point rather than a single pixel. It appends the result as a 4th element to each spawnMap point ([x, y, count, floorMask]); a point with no confident floor falls back to plausible-everywhere rather than guessing wrong. See the script’s docstring for the full algorithm.

scripts/gen-floor-connections.py (also not one of the original 4 - added for the map’s “Stairs & holes” layer and the pathfinder) finds every tile the minimap draws as a stairs/ladder/hole/ropehole (a flat yellow - but the same yellow also paints some roofs and ground, so only staircase-sized patches of up to 12 tiles that aren’t rings count; that skips ~20,000 roof/ground tiles and leaves ~14,300 markers) and works out which adjacent floor each connects to, purely from the terrain. Each marker tile goes one way only (up or down, never both) and pairs with the marker directly above/below it, preferring the best shape match - a 3-wide staircase pairs with the 3-wide one under it, not a 1-tile stair it touches - else one within 2 tiles that isn’t already going the same way. A ramp is often drawn wider on one floor than the other (e.g. a 2x2 yellow block below a 1-wide top): only the tiles stacked exactly under/over the other floor’s marker change floor, so once a patch has a stacked pair its other tiles (the drawn foot of the ramp, like x32547 y32076 z7 beside the real ramp tile x32548) don’t pair by distance with their own ramp or a neighbouring stair - they’re dropped (~1,600 tiles), and the pathfinder and bot waypoints step onto the real ramp tile. Pairing is per tile, so an up and a down stair side by side in one yellow patch stay separate. A column of identical markers splits into stacked pairs from the top down, which is how ladder towers alternate ladder and hole; a plain walkable-vs-void asymmetry with no marker is a weaker guess, a light grey tile sitting exactly on top of a yellow marker that isn’t itself going down is the top of a stairs/trap door (the minimap only draws the arrival end yellow for those - ~370 in this snapshot), a marker with nothing walkable on either neighbouring floor is a dead end (dropped - the decorative/blocked holes the client also draws yellow, ~6%), and anything still ambiguous is written to a separate floorConnectionsUnknown list instead of guessed (~2% of markers, in this snapshot). The World Compendium page can step through that unknown list and let a human call each one by eye - see “Hidden map tools” below - and export the answers as JSON; drop that file at data/manual/floor-connection-labels.json and rerun this script to fold them in as confirmed, overriding the algorithm. See the script’s docstring for the full method.

The same script also writes data/derived/slopes.json: ramps and pyramid sides are drawn as bands of the stairs yellow (too big for a staircase, so never a stairs marker). Their outer tiles are walkable “pre-ramp” ground; the tiles that sit directly under something drawn on the floor above are where walking climbs onto it (on the Ankrahmun pyramids and every other yellow ramp alike - ~5,700 tiles), and the pathfinder never steps on those. It also writes data/derived/door-candidates.json, the possible doors the Find path tool uses: every tile of 1-thick grey or red wall whose two opposite walkable sides fall in different 8-connected areas of that floor (a diagonal step between two blocked tiles counts, as in the game), and can’t reach each other through other floors either - walking plus the detected stairs/ladders/holes, linked the way the pathfinder links them, both ways. Walls you could walk around, on this floor or via stairs (e.g. a walled city entered by its stairs, like the wall at x33247 y32815 z7), never qualify (~2,300 dropped this way), so a route only assumes a door where there’s no other way in. On top of that the pathfinder treats doors as a last resort per route: when start and goal are both on open ground it uses no door at all (so it can’t cut through a tower room built into a city wall either), when one end is in a small enclosed area (under 10,000 tiles reachable without a door - a house, a tower room) it only allows doors into or out of that area, and only if that finds nothing does it search again with every door allowed but each one costing 250 steps, so a route that needs one real door somewhere doesn’t also start cutting through tower rooms elsewhere. (Mountain rock and stone walls share one minimap grey, so a 1-tile rock ridge can still show up as an assumed door when nothing else connects - marked with a white square like every assumed door.)

Monster cards have a Hunting section, computed in the browser from data already in compendium.json: expected loot weight per kill (each sellable drop’s chance × average quantity × the item’s weight - empty bags and unsellable junk don’t count) and gold per oz, gold and XP per 100 HP dealt, and spawn clustering from the spawnMap points - total spawns, hunting areas (spawns chained no more than 25 tiles apart, on a shared plausible floor), and the best spot (most of that monster within 12 tiles of one point, with a map link and who else spawns there). Loot rows show each item’s weight and gp/oz. The monster list can be sorted by all of these (gold per oz, lightest loot, gold/XP per 100 HP, best spot, best spot value, biggest hunting area, spawns in world).

On the Spawn Map, clicking (or tapping) anywhere that isn’t a floor-connection marker copies that tile’s coordinates, e.g. x32480 y31644 z10; the readout next to the map buttons shows the tile under the cursor.

scripts/gen-known-floors.py deduces which floor our spawn clusters are on from a real-Tibia monster-database export (JSON with "monster": {"name"} and "spots": [{"spawns": [{"x","y","z"}]}] blocks). Minibia’s world is largely the real Tibia map, so a cluster with real spawns of the same monster within 8 tiles takes their floor(s) - only floors the terrain check (gen-floor-plausibility.py) already allows, nearest first; monsters match by name, plus a few aliases for Minibia renames (Beholder = Bonelord…). The export itself isn’t kept in the repo - only the small result, data/derived/known-floors.json ({"radius", "monsters": {name: [[x, y, floorMask], ...]}}). Where the match is exact it also keeps the real spawns’ own tiles: Minibia’s clusters are its spawns grouped, at the rounded average of their tiles, so when count real spawns of the monster within 8 tiles average to exactly the cluster’s tile (the nearest ones, or else the only such set), all on floors the terrain allows and none claimed by another cluster, the row gets their offsets and floors ([x, y, floorMask, [dx, dy, z, ...]]). Where the average is off, Minibia’s spawns sit elsewhere, so that cluster keeps only its floor. On the current export: 10,989 of the 15,219 spawn clusters of the 272 matching monsters got a known floor (9,097 a single floor), and 3,725 of those their spawn tiles (7,669 spawns); the rest are Minibia-only areas/monsters. The Spawn Map’s Real-Tibia floors toggle (on by default) shows those clusters only on their floor, with a white ring and the floor in the tooltip - and clusters with spawn tiles as one dot per monster on its own tile and floor (the tooltip names the tile and the cluster it belongs to) instead of a “cluster of N” dot. Monster cards show a per-floor count. The in-page Preview a Tibia export tool applies the same rules. Spawn dots sit in the middle of their tile.

Back

Before every jump to another view - a tab, a cross-link to an item, NPC or monster, 📍 / “show on map”, a stairs marker, the Data versions page - the view you’re leaving is remembered: tab, search, sort and filters, open cards, scroll position, and on the map the floor, view and monster chips. A floating ← Back button (bottom centre, shown once there’s something to go back to; it moves up out of the way of the map’s own controls) puts the last one back, and its ▾ lists the last 5 to pick from - picking one also drops the ones after it. The list is kept in localStorage, so it survives a reload on that device.

Map tools

The Spawn Map tab is one workspace: the map with a mode switch above it - Spawns, Find path, Route builder - and a side panel (below the map on narrow screens) for the current mode. The floor control and the cursor’s coordinates sit on the map; Layers holds the display toggles (Real-Tibia floors, Hide implausible floors, Stairs & holes). In Spawns mode the side panel has the difficulty filter and, under Data tools, the curation tools. To show particular monsters, type a name in the search box and press Enter (or pick a suggestion): it becomes a chip under the box - tap a chip to toggle it, × to remove it - and the map shows every monster whose chip is on (a full monster name matches exactly; other text matches every monster containing it, e.g. “orc”). Chips are remembered in the browser; the 📍 on a monster card switches the map to just that monster’s chip.

scripts/build-bundle.py packages multiple tool pages (site/index.html, merchant-ledger.html, compendium.html, accounts.html) plus the minimap PNGs into a single self-contained dist/minibia-tools.html. It’s kept here for reference but isn’t wired up — this repo only has the one compendium page, served directly via GitHub Pages rather than bundled.

data/icons.json (item icon images, base64 PNG keyed by the library item’s cid) is rendered from the game client’s sprite files, which are too big to keep in the repo (Minibia.spr is ~50MB). When new items show up without an icon, get Minibia.spr, Minibia.patch.spr and Minibia.dat (from https://minibia.com/data/760/<name>, or the browser cache after playing) and run:

python scripts/gen-icons.py --spr path/Minibia.spr --patch path/Minibia.patch.spr --dat path/Minibia.dat --virtual-items path/virtual-items.js

It only adds icons for items that don’t have one, leaving existing icons as they are (--all re-renders everything). Each item’s sid is its .dat object id. Items whose sid is 30000+ are recolors the client makes itself, defined in its src/data/virtual-items.js. Pass that file with --virtual-items to render them; only exact color-map recipes are supported. See the script’s docstring for the file formats.

Because the page fetches JSON at runtime, it needs to be served over HTTP (e.g. GitHub Pages, or python3 -m http.server locally) rather than opened directly as a file:// URL.