AnvilHeart

Modding

AnvilHeart loads Lua mods at startup. Mods use the same scripting API that our own gameplay is written in, so you can add items, villagers, events, interface screens and whole new systems. Everything lives under the global digi: 152 calls across 17 namespaces, listed below.

Mods run in a sandbox: there is no io, os is cut down to clock and date functions, and the string metatable is locked, and each mod gets its own environment and require.

Entities are opaque handles. Pass them back into the API, compare them with == and check them with digi.valid before use after a frame boundary.

Installing mods

Put each mod in its own folder inside the mods folder next to the game, then start AnvilHeart. Mods with a higher priority load first, and a mod with a broken manifest is skipped with a warning in the log instead of stopping the game.

Your first mod

A mod is a folder with a mod.json manifest and an entry script. Higher priority mods load first; ties load in folder name order.

mods/
  better_forge/
    mod.json
    main.lua
    assets/
{
  "name": "Better Forge",
  "version": "1.0.0",
  "description": "Faster heating, hotter coals.",
  "entry": "main.lua",
  "priority": 10
}

Events

Subscribe with digi.on(name, fn) and raise your own with digi.emit(name, ...).

EventArgumentsWhen
updatedtEvery frame.
sceneLoadedAfter a scene and its mods finish loading.
areaChangedareaThe local player entered a different area.
npcArrivedentityAn NPC reached the end of its path.
npcBlockedentityAn NPC could not find or follow a path.
netJoiningA join attempt started.
netConnectedThis client connected to a host.
netDisconnectedreasonThe session ended or the host went away.
netRejectedreasonThe host refused the join.
netPlayerJoinedpeer, name, uidA player joined the session.
netPlayerLeftpeerA player left the session.
net:<name>payload, fromA custom message sent with digi.net.send.

digi

Entities, events, transforms, spawning and the core game loop.

CallReturns
digi.assetExists(path: string)
digi.component(name: string, definition: table)
digi.dashPlayer(vx: number, vz: number, seconds: number)
digi.data(path: string)any
digi.destroy(entity: entity)
digi.emit(event: string, ...)
digi.facePlayer(x: number, z: number)
digi.find(name: string)any
digi.findAll(name: string)
digi.log
digi.model(entity: entity)string
digi.name(entity: entity)string
digi.on(event: string, function: function)
digi.parent(entity: entity)any
digi.paused()
digi.player()any
digi.playerForward()
digi.playerInput()
digi.playerPosition()
digi.position(entity: entity)
digi.quit()
digi.reload()
digi.rotation(entity: entity)
digi.scale(entity: entity)
digi.scene()
digi.setLight(entity: entity, intensity: number, range?: number)
digi.setModel(entity: entity, path: string)
digi.setPaused(paused: boolean)
digi.setPlayerInput(enabled: boolean)
digi.setPlayerMotion(speed: number, traction: number)
digi.setPosition(entity: entity, x: number, y: number, z: number, localOnly?: boolean)
digi.setRotation(entity: entity, pitch: number, yaw: number, roll: number, localOnly?: boolean)
digi.setScale(entity: entity, x: number, y?: number, z?: number, localOnly?: boolean)
digi.setVisible(entity: entity, visible: boolean, localOnly?: boolean)
digi.spawn(description: any, x?: number, y?: number, z?: number)any
digi.spawnLocal(description: table)any
digi.time()
digi.valid(entity: entity)
digi.warn(message: string)

digi.animator

Animation graph state and triggers on skinned entities.

CallReturns
digi.animator.set(entity: entity, parameter: string, value: any)
digi.animator.state(entity: entity)string
digi.animator.trigger(entity: entity, name: string)

digi.appearance

Per-entity model swaps, mesh masks and material overrides.

CallReturns
digi.appearance.get(entity: entity)
digi.appearance.meshes(entity: entity)
digi.appearance.set(entity: entity, appearance: any)

digi.area

Interiors and other areas that live inside the one world.

CallReturns
digi.area.current()
digi.area.enter(id: string, x: number, y: number, z: number, facingDegrees?: number)
digi.area.list()
digi.area.move(entity: entity, id: string, x: number, y: number, z: number, facingDegrees?: number)
digi.area.name(id?: string)string
digi.area.of(entity: entity)string

digi.audio

Sound playback and volume.

CallReturns
digi.audio.play(path: string, options?: table)
digi.audio.play3d(path: string, x: number, y: number, z: number, options?: table)
digi.audio.setVolume(bus: string, volume: number)
digi.audio.stop(id: any, fade?: number)
digi.audio.volume(bus: string)

digi.camera

Camera position, projection and scripted overrides.

CallReturns
digi.camera.override(x: number, y: number, z: number, yawDegrees: number, pitchDegrees: number, fovDegrees?: number)
digi.camera.position()
digi.camera.project(x: number, y: number, z: number)any
digi.camera.release()

digi.environment

Time of day, sky, fog and weather.

CallReturns
digi.environment.get()
digi.environment.hour()
digi.environment.set(values: table)
digi.environment.setHour(hour: number)
digi.environment.setTimeScale(scale: number)
digi.environment.timeScale()

digi.input

Actions, axes, rebinding and gamepad state.

CallReturns
digi.input.axis(axis: string)
digi.input.axisKey(axis: string, index: integer)
digi.input.bindings(action: string)
digi.input.cancelCapture()
digi.input.capture(mode?: string)
digi.input.captured()any
digi.input.capturing()
digi.input.down(action: string)
digi.input.hasGamepad()
digi.input.mouseCaptured()
digi.input.padFace(button: string)
digi.input.padFamily()
digi.input.pressed(action: string)
digi.input.released(action: string)
digi.input.setMouseCaptured(captured: boolean)
digi.input.usingGamepad()

digi.memory

Values kept across scene reloads for the session.

CallReturns
digi.memory.get(key: string)any
digi.memory.set(key: string, value: any)

digi.nav

Navigation grids and pathfinding.

CallReturns
digi.nav.build(id: string, minX: number, minZ: number, maxX: number, maxZ: number, options?: table)
digi.nav.buildArea(id: string, options?: table)integer
digi.nav.path(id: string, x1: number, y1: number, z1: number, x2: number, y2: number, z2: number)any
digi.nav.walkable(id: string, x: number, y: number, z: number)

digi.net

Co-op sessions, players, authority and custom messages.

CallReturns
digi.net.appearance(peer: integer)
digi.net.authority()
digi.net.host(port?: any)
digi.net.isClient()
digi.net.isHost()
digi.net.join(address: string, port?: any)
digi.net.leave()
digi.net.localPeer()
digi.net.name(peer?: integer)string
digi.net.playerArea(peer: integer)string
digi.net.playerPosition(peer: integer)any
digi.net.players()
digi.net.role()
digi.net.send(target: entity, name: string, payload?: any)
digi.net.setAppearance(appearance: any)
digi.net.setName(name: string)
digi.net.transport()

digi.npc

Moving kinematic NPCs along navigation paths.

CallReturns
digi.npc.face(entity: entity, x: number, z: number)
digi.npc.moveTo(entity: entity, x: number, y: number, z: number, speed?: number)
digi.npc.moving(entity: entity)
digi.npc.stop(entity: entity)

digi.physics

Raycasts, overlaps, velocity and impulses.

CallReturns
digi.physics.gravity()
digi.physics.impulse(entity: entity, x: number, y: number, z: number)
digi.physics.overlapBox(cx: number, cy: number, cz: number, hx: number, hy: number, hz: number, yawDegrees?: number)
digi.physics.raycast(ox: number, oy: number, oz: number, dx: number, dy: number, dz: number, distance: number)any
digi.physics.setGravity(x: number, y: number, z: number)
digi.physics.setVelocity(entity: entity, x: number, y: number, z: number)
digi.physics.velocity(entity: entity)

digi.settings

Player settings with defaults, validation and saving.

CallReturns
digi.settings.all()
digi.settings.defaults()
digi.settings.get(key: string)
digi.settings.reset(section?: string)
digi.settings.resolutions()
digi.settings.save()
digi.settings.set(key: string, value: any)

digi.steam

Steam identity, friends, lobbies, presence and achievements.

CallReturns
digi.steam.achieved(name: string)
digi.steam.available()
digi.steam.deck()
digi.steam.friends()
digi.steam.id()
digi.steam.invite()
digi.steam.join(friendId: string)
digi.steam.keyboard(x: integer, y: integer, width: integer, height: integer)
digi.steam.lobby()string
digi.steam.name()
digi.steam.setPresence(key: string, value: string)
digi.steam.unlock(name: string)

digi.storage

Save slots in the game's save folder.

CallReturns
digi.storage.exists(name: string)
digi.storage.load(name: string)any
digi.storage.remove(name: string)
digi.storage.save(name: string, value: any)

digi.ui

RmlUi documents, text, classes and gamepad navigation.

CallReturns
digi.ui.activate(doc: string)
digi.ui.clearNavigation(doc: string)
digi.ui.focusRect(doc: string)any
digi.ui.hide(doc: string)
digi.ui.navigate(doc: string, dx?: integer, dy?: integer)
digi.ui.on(doc: string, id: string, event: string, handler: function)
digi.ui.setClass(doc: string, id: string, name: string, enabled: boolean)
digi.ui.setProperty(doc: string, id: string, property: string, value: string)
digi.ui.setRml(doc: string, id: string, rml: string)
digi.ui.setText(doc: string, id: string, text: any)
digi.ui.show(doc: string, modal?: boolean)
digi.ui.value(doc: string, id: string)
digi.ui.visible(doc: string)