Game audio

From Wiki G1R-MP G1 Remake Multiplayer
Jump to navigation Jump to search

Planned for update 0.1.7. These functions are not included in the public 0.1.6 release.

Game audio lets a client resource play built-in Gothic Remake sound effects, music and localized dialogue. Use catalog keys rather than filenames. The recordings remain in the player's installed game; this API does not redistribute them.

All functions on this page are client-side and use the exact lower-camel names shown below. Existing resource-file audio and voice chat are separate systems and remain unchanged.

API overview

Function Purpose Immediate return
playSoundFromGame Request listener-relative / 2D playback. Sound handle or false.
playSound3DFromGame Request playback at a world position. Sound handle or false.
stopGameSound Stop one owned sound, including a pending start. Request ID or false.
setGameSoundVolume Set an owned sound's volume. Request ID or false.
setGameSoundPaused Pause or resume an owned sound. Request ID or false.
setGameSound3DPosition Move a source created with the 3D function. Request ID or false.
setGameSound3DMinMaxDistance Change that 3D source's attenuation distances. Request ID or false.
getGameSoundState Read the latest accumulated state of an owned handle. Table or false.
preloadGameSound Request preparation and native capability information. Request ID or false.
getGameSoundInfo Read catalog metadata plus this resource's last successful preload metadata. Table or false.
getGameSoundCatalog Search and paginate the built-in catalog. Page table or false.

onClientGameSoundResult reports asynchronous results and lifecycle changes to the owning resource.

Find a sound

local page = getGameSoundCatalog("Diego", "dialogue", 0, 20)
if page then
    for _, item in ipairs(page.items) do
        outputDebugString(item.key .. " | " .. item.category .. " | " .. item.speaker)
    end
    outputDebugString("Matching entries: " .. tostring(page.total))
end

The defaults are an empty query, all categories, offset 0 and a page size of 50. Categories are "", "sfx", "music" and "dialogue". Search matches key/speaker, case-insensitively for ASCII names. A catalog entry identifies an allowed request; it does not guarantee that every game build or installed voice language contains a playable recording. The complete identifier list is in Game dialogue catalog, not just the examples below.

Examples of catalog keys:

  • event:/SFX/UI/SFX_UI_Jingle_LevelUp
  • event:/SFX/Objects/Fire/SFX_OBJ_Fire_Campfire_BurnLoop
  • event:/MUSIC/Locations/Explore/oldCamp/oldCampRing_day_explore
  • voice/INFO_DIEGO_BARRIERE_11_01

Playback and asynchronous results

Install the result handler before requesting playback:

addEventHandler("onClientGameSoundResult", resourceRoot,
    function(requestId, ok, reason, dataJson)
        local data = fromJSON(dataJson)
        if not ok then
            outputDebugString("Game audio failed: " .. tostring(reason))
        end
        if type(data) == "table" and data.id and data.state then
            outputDebugString("Sound " .. tostring(data.id) .. ": " .. data.state)
        end
    end)

local sound = playSoundFromGame(
    "event:/SFX/UI/SFX_UI_Jingle_LevelUp", {volume = 0.5})
if not sound then
    outputDebugString("Game audio request was not accepted.")
end

A returned handle means the request was accepted, not that the sound is already playing. The play request ID equals its sound handle. Later control calls return their own request IDs; the result's data.id identifies the affected sound. requestId == 0 identifies an unsolicited state/lifecycle notification.

The event's fourth argument is JSON text, not a Lua table. Decode it with fromJSON and check its type. Results are operation-dependent; do not assume every field is present.

Typical states are loading, starting, playing, paused, followed by finished, stopped or failed. States may be skipped; short sounds can finish before the next script poll. The terminal states cannot be restarted: create a new handle to play again.

confirmed is context-sensitive: a playing/finished observation confirms native playback was observed; a successful explicit stop may also set it to true to confirm the stop. Cancellation before startup can yield stopped with confirmed=false. It never proves that a player heard the sound. Check state together with confirmed, not the flag alone.

3D placement and controls

Coordinates and distances are in metres, not Unreal centimetres. Supply a position in the same coordinate system as the multiplayer world-position API:

local fire

function startExampleFire(x, y, z)
    if fire then stopGameSound(fire) end
    fire = playSound3DFromGame(
        "event:/SFX/Objects/Fire/SFX_OBJ_Fire_Campfire_BurnLoop",
        x, y, z, {volume = 0.6, minDistance = 1, maxDistance = 30})
    return fire
end

function moveExampleFire(x, y, z)
    if not fire then return false end
    return setGameSound3DPosition(fire, x, y, z)
end

function stopExampleFire()
    if not fire then return false end
    local requestId = stopGameSound(fire)
    fire = nil
    return requestId
end

After successful playback, the same resource can use setGameSoundVolume(fire, 0.2), setGameSoundPaused(fire, true), setGameSoundPaused(fire, false) and setGameSound3DMinMaxDistance(fire, 1, 8). Observe the asynchronous results rather than assuming that a truthy request ID means a completed control operation. Position and distance controls reject handles created with the non-3D function.

Options may be omitted, nil or an empty table. Defaults are {volume = 1, minDistance = 1, maxDistance = 30}. Unknown option names are rejected. Use actual numbers and booleans of the documented types; arbitrary wrong types can raise Lua argument errors rather than returning false.

Localized dialogue

local voice = playSoundFromGame("voice/INFO_DIEGO_BARRIERE_11_01", {volume = 0.8})
if not voice then
    outputDebugString("Dialogue request was rejected.")
end

The game's selected voice language determines the recording. Playing a line does not run a conversation graph, quest or subtitle scene. There is no language-override parameter and no substitution of another character's line or an alternate language by this API. Missing recordings or unavailable native facilities produce explicit errors.

The same voice key can be used with playSound3DFromGame for a world-positioned voice. The current implementation uses the game's own archive-aware audio loading rather than accepting a raw recording path.

Preloading and metadata

local key = "event:/SFX/Objects/Fire/SFX_OBJ_Fire_Campfire_BurnLoop"
local requestId = preloadGameSound(key)
-- Wait for this requestId in onClientGameSoundResult.
-- After successful completion, getGameSoundInfo(key) includes native metadata.

Catalog metadata initially includes key, category, speaker, capabilitiesKnown=false and verifiedInGame=false. Successful preload enriches this resource's cached info with fields such as authoredSpatial, oneShot, loopMode and eventSamplesPreloaded. Playback alone does not populate that info cache. verifiedInGame remains false; it is not a per-player listening test result.

Voice preload is a request, not an independently confirmed recording preload. A successful result can contain voicePreloadRequested=true, voicePreloadConfirmed=false and recordingResolved=true. Loaded event samples or a resolved recording do not prove that the voice PCM is ready or audible. Do not turn these flags into a claim that a dialogue has played.

Authored behavior and music

  • Looping, sustain and musical layers come from the authored event. There is no forced-loop option; oneShot=false does not guarantee a simple repeating loop.
  • Non-3D playback of an authored spatial sound uses a source following the local camera, with per-instance attenuation. The reported mode is listener-relative. Truly 2D events report authored-2d; world-positioned playback reports world-3d.
  • A 3D request for an authored 2D event fails with event-is-2d. Metadata and errors should be checked for each chosen key.
  • This API does not stop the game's existing ambient music or change its global mixer/listener settings. Custom music can overlap native music. Game/user audio settings can affect what is heard.
  • There are no bus/VCA, global event, arbitrary programmer-sound, raw pointer, URL or filesystem-path controls.

Multiplayer and lifetime

Playback is local to each client. A server resource may select recipients with triggerClientEvent, and a matching client handler can call these functions. There is no automatic broadcast, late-join replay or sample-accurate cross-client synchronization. Maintain desired persistent ambient sounds in your own resource and create them for newly joined clients when their world is ready.

Handles belong to the client resource that created them. Another resource cannot query/control them. Do not send a handle to another player or store it in a database as a persistent ID. Handles are not reused during the client runtime's lifetime, but become invalid after resource/lifecycle cleanup; older terminal records can be evicted.

Resource stop, disconnect, world replacement or loss of the native owner heartbeat clean up only the instances/preloads owned by this API. A world-reset notification has requestId=0, ok=false, reason=world-reset and an empty data object. Clear your script's saved handles in that case. Native NPC dialogue, other resources' file audio and voice chat are not globally stopped.

Limits and error handling

See Game audio limits for numeric validation, capacity and timeouts. Cold loading can take time; preload a small useful selection instead of the entire catalog, and do not retry every frame.

Immediate false means the request was not accepted: for example an unknown key, invalid numeric range, foreign/ended handle, unavailable gameplay, or full capacity. No asynchronous completion is promised for a rejected request. Accepted requests can still fail later. Examples include event-is-2d, preload-cache-full, native-queue-full, native-loading-timeout, native-timeout, localized-recording-path-missing, playback-not-observed, listener-lost and sound-not-owned-or-ended.

Log the reason and available result fields, stop retry loops, and provide a fallback in your resource. Do not infer acoustic success from request acceptance or from the existence of a catalog entry.