Unified API, networking, and save data for map mods.
Used in Antique Atlas 4 and Hoofprint. Requires Connector and FFAPI on (neo)forge.
Other names considered: Polaris, Ichnite, Trackway, Lay of the Land, Worldsense, and Lithography.
Player Usage
Surveyor is a library for map mod developers! You shouldn't need to download it alone.
Commands
/surveyor
- display information about your map exploration, including sharing./surveyor share [username]
- request/accept sharing map exploration with a player./surveyor unshare
- stop sharing map exploration (leave your "sharing group")/surveyor landmarks
- view a cross-world landmark breakdown by type./surveyor landmarks get [type]
- lists each landmark of that type in the current world./surveyor landmarks remove [type] [pos]
removes a waypoint or landmark (op 2) from the world./surveyor landmarks add [type] [pos] [name]
add a waypoint to the world at the specified position./surveyor landmarks global [type] [pos] [name]
(op 2) add a landmark to the world at the specified position.
Configuration
Surveyor has extensive start-time configuration in config/surveyor.toml
.
It's various systems can be turned on and off, and map sharing tweaked finely.
Library Features
Surveyor relieves the scanning, saving, and networking responsibilities from dependent map mods.
In general, Surveyor:
- Records terrain, structure, and "landmark" data suitable for maps as the world is changed.
- Enables live map sharing between players by tracking individual exploration of the map.
- Sends the client structures as they're discovered or shared.
- Syncs map data and landmarks (e.g. waypoints) when sharing or on client data loss.
- Exposes a generic API for map mod integrations (e.g. adding map markers to important locations).
- Only adds 2-5% to save size, using an efficient format both in-memory and on-disk.
Surveyor's data deliberately preserves key details, designed to allow any abitrary map mod to use it:
- Terrain is a top-down view of blocks with height, biome, light level, and water depth.
- Terrain contains multiple layers, allowing for usable cave and nether maps.
- Structures have IDs, pieces, tags, and even full piece NBT for smaller structures intact.
- Landmarks generically represent other positional map data - e.g. waypoints, POIs, or faction claims.
Notice: Surveyor is still in early releases.
- The API might break several times during 0.x
- The networking format will break several times during 0.x.
- The save format will likely break on the change to 1.x
- Javadoc is very limited
Developers
repositories {
maven { url 'https://repo.sleeping.town/' }
}
dependencies {
modImplementation 'folk.sisby:surveyor:0.3.0+1.20'
include 'folk.sisby:surveyor:0.3.0+1.20'
}
Examples
- Antique Atlas 4 - A stylized client-side map mod.
- SurveyorSurveyor - An enhanced-vanilla style java map image generator, using raw surveyor save files.
- Surveystones - A mixed-side addon that automatically adds landmarks for waystones from Fabric Waystones.
Core Concepts
The World Summary holds all of surveyor's data for a world. It can be accessed using WorldSummary.of(World)
.
Chunk Summaries (or the "Terrain Summary") represent the world viewed from above. This includes the top layer of blocks, along with their biome, height, block light level, and the depth of water above them.
Structure Summaries represent an in-world structure (called StructureStart
in yarn) - they include map-critical information for identifying the structure and its pieces, even full NBT for structures with under 10 pieces, but not any actual blocks.
Landmarks are a way to represent all other positional information on-map. They have unique serialization per-type, and are uniquely keyed by their type and position to prevent overlaps.
Exploration is a record of what chunks, structures, and landmarks a player should be able to see.
A player explores a chunk when they're sent it, explores a structure when they stand in (or look at) one of its pieces, and explores an (unowned) landmark when they explore the chunk it's in.
Terrain Summary Layers
To facilitate cave mapping, Surveyor records the top layer of blocks at multiple height levels (layer heights).
The Overworld scans for floors in these layers:
- 257-319 - usually empty
- 62-256 - surface terrain
- 0-61 - sea floors and riverbeds, ravines, and caves
- -64-0 - deepslate caves
The Nether scans for floors in these layers:
- 127-255 - usually flat bedrock
- 71-126 - high caves and outcrops
- 41-70 - mid-level outcrops and walkways
- 0-40 - the lava sea and shores
Roughly speaking, Surveyor will accept any non-clear solid block below a 2-high walk-space as a floor.
It will also detect "carpets" (non-clear non-solid blocks) above these floors and use those instead.
Surveyor supports any layer height configuration, but currently lacks the API/config to change this for specific dimensions.
Note that the amount of layers doesn't affect how mods display the map, only how often cave floors will be occluded by floors above them.
Map Mods
Click to show the map mod guide
Quick reminder that surveyor should replace any existing world scanning logic
You should never need to look at the currently loaded chunks - If some information is missing, let us know!
Initial Setup
Client map mods should always use SurveyorClientEvents
- this ensures only explored areas will be provided in singleplayer.
Tune into WorldLoad
and queue up the provided keys for rendering.
This event will trigger when the client world has access to surveyor data and the player is available.
terrain
contains all available chunks by region. WorldTerrainSummary.toKeys()
converts this into ChunkPos.
structures
contains all structure starts by key + ChunkPos.
landmarks
contains all landmarks (POIs, waypoints, death markers, etc.) by type + BlockPos.
You can get these from the world summary later using keySet()
methods - check the event implementation.
Pass in SurveyorClient.getExploration()
to ensure unexplored areas are hidden.
Live Updates
Also tune into TerrainUpdated
, StructuresAdded
, LandmarksAdded
to add to your render queues.
These fire whenever the client player should see something new (usually via exploration).
They can also fire before ClientPlayerLoad
, so let any of them create your map data.
Tune into LandmarksRemoved
as well but without a queue - just remove from your map/queue directly.
Terrain Rendering
First, generate a top layer (with any desired height limits) using get(ChunkPos).toSingleLayer()
.
This will produce a raw layer summary of one-dimensional arrays:
- exists - True where a floor exists, false otherwise - where false, all other fields are junk.
- depths - The distance of the floor below your specified world height. so y = worldHeight - depth.
- blocks - The floor block. Indexed per-region via
getBlockPalette(ChunkPos)
. - biomes - The floor biome. Indexed per-region via
getBiomePalette(ChunkPos)
. - lightLevels - The block light level directly above the floor (i.e. the block light for its top face). 0-15.
- waterLights - The block light level directly above the water's surface (if there is one). 0-15.
- waterDepths - How deep the contiguous water above the floor is.
- All other liquid surfaces are considered floors, but water is special-cased.
- The sea floor (e.g. sand) is recorded, and this depth value indicates the water surface instead.
- This allows maps to show water depth shading, but also hide water completely if desired.
All arrays can be indexed by x * 16 + z
, where x and z are relative to the chunk.
Use these arrays to render and store map data for that chunk (pixels, buffers, whichever).
Remember that you'll be rendering hundreds of thousands of chunks here - optimize this process hard.
Structure Rendering
Along with the key and ChunkPos, you can get the type and any tags using getType(key)
and getTags(key)
.
You can access a full summary of the structure (e.g. to draw its bounding boxes) using get(key, ChunkPos)
.
This includes piece data like boxes, direction, IDs, etc.
Landmark Rendering & Management
Along with the type and BlockPos, you can get a full landmark using get(type, BlockPos)
.
By default, this can include a dye color, a text name, the owner's UUID, and a texture (could be from another map mod).
You should have a method of rendering a landmark using just this information.
To improve how landmarks are displayed, you can use instanceof
to check for additional data, e.g. HasBlockBox
.
To add a waypoint landmark, just make a SimplePointLandmark
owned by the player and use put(Landmark)
.
This will save to disk and send a copy to the server.
Player Rendering
You can use SurveyorClient.getFriends()
to get a set of players to draw on the map.
This includes both the client player, online "friends" (map sharing group members), and offline friends.
The players are represented abstractly, providing UUID, username, global position, yaw, and online status.
Landmark Integrations
Click to show the landmark integration guide
Landmark types can be registered via the registry in Landmarks
.
This allows you to set and serialize custom data relevant to your landmark.
Your landmark can usually be a record. Check the builtins for an example.
To make extra landmark data accessible to map mods, always declare a new Has
interface to access it from.
To place a landmark, just use WorldSummary.of(world).landmarks().put(Landmark)
.
This works fine on either side - adding a landmark on the server will send it to the client and vice-versa.
Landmark types can't yet have fallback types - so use a simple type (or PR a new one!) if your mod is only on one side.
Afterword
All mods are built on the work of many others.
Special thanks to:
Ampflower, Falkreon, Garden, Kat, Solo, Crosby, Lemma, Leo, Jasmine, Aqua, Wonder, Infinidoge, Emi, and Una.
We're open to better ways to implement our mods. If you see something odd and have an idea, let us know!