Coming soon toSteam
Modding docsAPI 1

Maps

Example: Crater Canyon uses almost every feature on this page. It has no water, because it is a desert built on Empty (the planet-less Void scene, or the flat Testzone in game versions without it; see base below). A map can also bring its own planets, sun and weather: see Planets.

How a mod map works

A map in Project Avion is a scene of the game that also carries the player spawner, water, networking and more. A mod cannot ship a scene without shipping code, so a mod map is built on top of one of the game's maps: pick a base, hide nothing or some of it, and add terrain, props, lights, sounds, effects and a different sky. Everything is data the game checks, which is also why a server can send your map to joining players safely.

mod.json
"content": { "maps": ["content/maps/*.json"] },
"permissions": ["scenes"]

Your map appears on the map selection screen. Players choose it like any other map.

Definition

content/maps/lagoon.json
{
  "id": "lagoon",
  "name": "Hidden Lagoon",
  "subtitle": "Mod map · tropical",
  "description": "A calm lagoon behind a reef.",
  "players": "1 - 8 Players",
  "color": "#2fb3a3",
  "base": "Testzone",
  "preview": "/preview.png",
  "music": { "file": "/music/lagoon.ogg", "volume": 0.4 },
  "water": { "enabled": true, "level": 2 },

  "environment": {
    "ambient": "#40606a",
    "fog": { "color": "#9fd4d8", "density": 0.003 },
    "sun": { "color": "#fff2d0", "intensity": 1.3, "pitch": 40, "yaw": 120 }
  },

  "spawns": [ { "position": [0, 4, 0], "heading": 90 } ],

  "objects": [
    { "name": "Island", "model": "/models/island.glb", "position": [0, 0, 0], "collider": "mesh" },
    { "name": "Hut", "model": "/models/hut.obj", "position": [12, 2, -4], "rotation": [0, 35, 0], "scale": 1.5, "collider": "box" },
    { "name": "Lantern", "light": { "color": "#ffb060", "range": 12, "intensity": 2.5 }, "position": [12, 4.5, -4] },
    { "name": "Torch fire", "effect": "fire", "position": [10, 2.2, -2], "effectOptions": { "rate": 30, "size": 0.4 } },
    { "name": "Waves", "sound": "/sounds/waves.ogg", "volume": 0.7, "range": 80, "position": [-30, 1, 0] },
    { "name": "Marker", "shape": "cylinder", "position": [0, 1, 20], "scale": [0.4, 2, 0.4], "color": "#f59e0b" }
  ]
}
FieldDescription
idThe map's id. Its full id, used in scripts, is your.mod-id:id.
name, subtitle, description, players, colorHow the map card looks on the selection screen.
baseThe scene it builds on, case-insensitive: Earth, FlatPlanet, OceanPlanet (planets), Testzone (a flat proving ground), RacingMap, Empty (default) for a map that brings its own terrain, or Void (empty space with a sun, for maps with a planet system). Empty loads Void when the game has it, else the flat Testzone; a map with a "system" on Empty/Void loads Void, or FlatPlanet with its planet hidden in game versions without it (the log says so once). The retired names Islands/Space (→ Earth) and Airfields (→ Testzone) still load, with a note in the log. Any other name is reported and replaced by Empty.
previewA picture for the map card.
musicA file, or { "file", "volume" }. Loops while the map is loaded.
hideBaseNames of parts of the base map to hide. Only parts the game marks as hideable are affected.
waterWhether the map has a sea: true, false, or { "enabled", "level" } with level the sea's height in metres (world Y). Leave it out to keep the base map's water. See Water.
systemThe map's own planets, sun and weather. See Planets.
environmentambient light colour, fog (color, density 0–0.2), sun (color, intensity 0–20, pitch and yaw in degrees).
spawnsUp to 64 { "position", "heading" }. Players start at a random one.
objectsUp to 4096 objects (below).

Objects

Each object has a name and a position (three numbers, metres), an optional rotation (three angles in degrees) and a scale (a number or three numbers), and exactly one kind:

KindFields
modelA .glb, .gltf, .obj or .fbx file, color tint, collider
shapecube, sphere, cylinder, capsule or plane, color, collider
light{ "color", "range" (0.1–500), "intensity" (0–100) } point light
soundA .wav, .ogg or .mp3 that loops, with volume (0–1) and range (metres)
effectAn effect preset with effectOptions

collider is none (default), box or mesh (any capitalisation). Give walkable terrain "collider": "mesh".

Mistakes are reported

A map is checked when the mod loads. A value of the wrong kind, a number out of range, a malformed position or colour, or a shape that is not one of the five is an error that names the field (map "lagoon": objects[3].shape: "sphear" is not one of "cube", … (did you mean 'sphere'?)) and the map is not offered. An unknown field or an unknown collider kind is a warning in the mod's log (objects[2].collider: "convex" is not one of "none", "box", "mesh"; using "none"), and the map still loads.

Water

The sea is one flat, wavy surface at a single height that stretches to the horizon. Ships float on it, sails stop at it, and splashes ripple it. None of the game's maps has a flat sea: Earth and OceanPlanet have the planet's own spherical sea, and on planets (Earth, FlatPlanet, OceanPlanet, RacingMap) a flat sea cannot exist. Use water on a flat base (Testzone, Empty).

waterResult
left outThe base map's water (no flat sea on any of the game's maps)
trueA sea at the game's usual height
falseNo flat sea
{ "enabled": true, "level": 2 }A sea at Y 2. Use it to flood a valley in your terrain, or to lower the sea around your island

level may be between -100000 and 100000. Don't add water to a map built on a planet, because "down" there points at the planet's centre.

Coordinates

Positions are metres, Y up. Model files use the usual right-handed convention (Blender, glTF, OBJ, FBX), and the game is left-handed, so a model's Z is mirrored when it loads. Something at z = 10 in your modelling tool is at z = -10 in the map. Place objects by walking there in the game and reading your position (world.me() in the script editor's Console, or with /lua), or by mirroring the Z of positions you measured in your modelling tool.

Scripts on maps

Maps are data, and a script adds what changes over time:

scripts/client.lua
-- "lagoon" is this mod's own map (its full id is mod.id .. ":lagoon"). onEnter also runs at once when the
-- mod starts while the map is already loaded (after /mods.reload, or joining late).
maps.onEnter("lagoon", startBirds)
maps.onLeave("lagoon", stopBirds)
FunctionDescription
maps.current()The map being played: a game map id like "Earth", or a mod map's full id
maps.onEnter(id, fn) / maps.onLeave(id, fn)fn runs on every arrival on / departure from the map (and onEnter at once if already there). id is a full id, a game map, or one of your own maps' short ids
maps.list()Every map, as { id, name, subtitle, mod }
maps.spawnPoints()The current mod map's spawns
maps.load(id) / maps.tryLoad(id)Server only, needs scenes: the map the host's next session starts on. The game does not change map in the middle of a session. tryLoad returns false, reason instead of raising when the game refuses.
maps.water()The sea now, as { enabled, level, script }. script is true while a script has set the water rather than the map
maps.setWater(on)Server only, needs world: adds (true) or removes (false) the sea for every player, or { enabled, level } to move it too. Players who join later get the same water. Lasts until the map changes
maps.resetWater()Server only, needs world: back to the water the map declares
scripts/server.lua
-- A tide: the sea rises and falls two metres every ten minutes.
local base = maps.water().level
timer.every(5, function()
  local t = time.now() / 600 * 2 * math.pi
  maps.setWater{ enabled = true, level = base + 2 * math.sin(t) }
end)

Everything a map creates is removed when the players leave it. Objects your script created stay until you destroy them or the mod stops, so remove them in maps.onLeave.