Discord Rich Presence: Difference between revisions
Document upcoming 0.1.9 Discord Rich Presence: server Lua functions, callback, opt-in and owner setup; not a release |
Release 0.1.9 / BUILD264 / Protocol42: availability and download setup; preserve experimental limits |
||
| Line 1: | Line 1: | ||
= Discord Rich Presence = | = Discord Rich Presence = | ||
'''Available from 0.1.9 | '''Available from 0.1.9. Use matching updated clients and server.''' | ||
Server Lua can describe a player's current activity in Discord: server name, location, occupation, images/tooltips, elapsed or remaining time, a displayed player count and up to two website links. The player must opt in in the launcher. This is optional presentation, not Discord account linking or gameplay authentication. | Server Lua can describe a player's current activity in Discord: server name, location, occupation, images/tooltips, elapsed or remaining time, a displayed player count and up to two website links. The player must opt in in the launcher. This is optional presentation, not Discord account linking or gameplay authentication. | ||
Latest revision as of 13:26, 20 October 2026
Discord Rich Presence
Available from 0.1.9. Use matching updated clients and server.
Server Lua can describe a player's current activity in Discord: server name, location, occupation, images/tooltips, elapsed or remaining time, a displayed player count and up to two website links. The player must opt in in the launcher. This is optional presentation, not Discord account linking or gameplay authentication.
Discord Presence configuration provides the complete copyable server-owner resource and setup instructions for Windows and Linux dedicated servers.
Setup and platform support
- Create an application in the Discord Developer Portal. Copy its public Application ID from General Information as a quoted string. It is not a Discord guild/user ID, bot token or Client Secret.
- Optional: upload images under the application's Rich Presence / Art Assets settings and use the resulting lowercase asset keys. This G1R:MP API supports uploaded keys only, not external image URLs.
- Configure server Lua as described below or use Discord Presence configuration. A Windows or Linux dedicated server does not need Discord installed, a bot, Python, a Discord SDK or additional credentials.
- The player runs the Discord desktop app, allows activity sharing in Discord, enables Discord Rich Presence in G1R:MP launcher Settings, saves and rejoins. The launcher setting is off by default and takes effect on the next join. Server scripts cannot override it.
The game-side connection supports the Windows client. Discord in a browser alone is insufficient. Linux/Wine game-side transport is not qualified; this does not restrict Linux dedicated servers. The main Playing title comes from the Discord application name; this Lua API does not expose an arbitrary title override.
Functions
All eight functions are server-side and use the exact case shown.
| Function | Meaning |
|---|---|
| setDiscordApplicationId | Set the calling resource's public application ID. |
| getDiscordApplicationId | Read that resource's configured ID, or an empty string. |
| getDiscordTimestamp | Current UTC Unix timestamp in seconds. |
| setPlayerDiscordPresence | Replace a connected player's complete activity profile. |
| updatePlayerDiscordPresence | Atomically merge changes; false removes a field. |
| clearPlayerDiscordPresence | Clear the caller's profile and release ownership. |
| getPlayerDiscordPresence | Copy of current fields plus read-only owner/applicationId metadata, or nil. |
| getPlayerDiscordPresenceStatus | Last client diagnostic status, not public-visibility proof. |
The application ID is configured per resource. Changing it affects subsequent set/update calls; it does not automatically rewrite existing profiles. IDs must be decimal strings of 17..20 digits, nonzero, no leading zero, within unsigned 64-bit range. Never use a Lua number for a Discord ID: it can lose precision. Player IDs are connected numeric integers, not names or Discord accounts.
Set/update/clear and setDiscordApplicationId return ok, reason: true with an empty reason on success, false with an explanatory string on rejection. An accepted set/update means queued on the server, not already visible on Discord. A malformed patch is rejected without partially changing the old profile.
Profile fields
A flat table; all fields are optional. An empty table is a basic Playing activity, not a clear request. Unknown fields are rejected.
| Fields | Type / limit | Behavior |
|---|---|---|
| details, state | string, 128 UTF-8 bytes each | Public activity/server and location/job text. |
| largeImage, smallImage | string, 128 ASCII bytes | Uploaded asset keys containing only letters, digits, underscores or hyphens. Copy the saved lowercase key from Discord. |
| largeText, smallText | string, 128 UTF-8 bytes | Hover text for the corresponding artwork. |
| startTimestamp, endTimestamp | integer, 0..4102444800 | Unix seconds; zero disables that timestamp. A nonzero end requires a nonzero start and must be at least the start. |
| partyId | string, 128 UTF-8 bytes | Public, non-secret display-group identifier. Requires a nonzero partyMax. |
| partySize, partyMax | integer, 0..1000000 | Current displayed count and capacity; size must not exceed max. A nonzero max requires partyId. Set all three party fields together. |
| button1Label, button2Label | string, 32 UTF-8 bytes each | Text for at most two links. |
| button1Url, button2Url | string, 512 ASCII bytes each | HTTPS only; label and URL must be supplied together. Button 2 requires button 1. |
Text must be valid UTF-8 without control characters or embedded NULs. Limits are bytes, not character counts. Empty text omits that value from the Discord activity. Link validation disallows credentials, explicit port syntax, backslashes and non-HTTPS/custom schemes. Internationalized hosts/paths must use a valid ASCII HTTPS representation accepted by the validator.
Party fields only display counts; they do not create a lobby, reserve slots, count connected players automatically or permit joining. Buttons open website/community links through Discord; automatic Join/Spectate is not implemented. Do not put passwords, tokens, player IPs, account names or private character information into public fields.
Basic example
Place this in a server script in the resource that will own the presence. Replace the Application ID placeholder before enabling it. The production starter also handles already-connected players when the resource starts.
local configured, reason = setDiscordApplicationId("YOUR_PUBLIC_APPLICATION_ID")
if not configured then
outputDebugString("Discord configuration: " .. reason)
return
end
addEventHandler("onPlayerJoin", root, function(playerId)
local ok, failure = setPlayerDiscordPresence(playerId, {
details = "My Gothic RP server",
state = "Creating a character",
startTimestamp = getDiscordTimestamp()
})
if not ok then outputDebugString("Discord presence: " .. failure) end
end)
From the same resource's trusted server gameplay logic, after deciding the player's location or job:
local function showForgeActivity(playerId)
return updatePlayerDiscordPresence(playerId, {
state = "Working at the forge",
smallImage = "smith",
smallText = "Blacksmith"
})
end
local function removeJobArtwork(playerId)
return updatePlayerDiscordPresence(playerId, {
smallImage = false, smallText = false
})
end
Upload the smith asset before using it. False is the explicit patch-removal marker; nil is absent from Lua tables and does not remove an existing field. After clear, update returns presence-not-set; use set to start again.
Timers, counts and links
Use getDiscordTimestamp, not getTickCount (which is monotonic milliseconds). Set start only for elapsed time. For a timed activity:
local function showTimedActivity(playerId, connectedCount, configuredCapacity)
local now = getDiscordTimestamp()
return updatePlayerDiscordPresence(playerId, {
state = "Taking part in a server event",
startTimestamp = now,
endTimestamp = now + 600,
partyId = "my-rp-event",
partySize = connectedCount,
partyMax = configuredCapacity,
button1Label = "Server website",
button1Url = "https://example.com/",
button2Label = "Community",
button2Url = "https://discord.gg/YOUR_INVITE"
})
end
Replace placeholder links with your own. Pass authoritative counts within the limits. Your gamemode decides when to refresh them; this example is not a polling loop. Use false for all related fields to remove a party or both parts of a link.
Status callback
onPlayerDiscordPresenceStatus reports diagnostic changes to the current owning resource only:
addEventHandler("onPlayerDiscordPresenceStatus", root, function(playerId, status)
outputDebugString("Discord player " .. tostring(playerId) .. ": " .. status)
end)
| Status | Meaning / action |
|---|---|
| unsupported | No compatible client report yet, old client, or capability expired. |
| disabled | Player opted out locally. The server cannot override this. |
| idle | Compatible client has no active profile to publish. |
| connecting | Waiting to connect/apply the current profile; allow update delay. |
| unavailable | Desktop Discord unavailable or its local connection was lost. |
| applied | Acknowledgement of the matching local Discord activity request. |
| error | Client/Discord rejected or failed the operation; check configuration. |
| cleared | Client reports no active profile after clearing. |
| disconnected | Getter result for an invalid/offline player. |
These are client-supplied advisory diagnostics. Applied is not proof of identity, guild membership, permissions, public visibility or an authorized game action. Do not award rewards or gate permissions on it. No Discord user ID, username, avatar, friends list or token is forwarded to the server.
Not every listed status is guaranteed to produce a callback. After clear there is no owner, so the former owner receives no cleanup callback; the getter may retain the last report until the next client poll. Disconnected is a getter result, not a promised quit notification. Callback delivery is deferred by the server; it is not a synchronous result of set/update.
Ownership, cleanup and performance
- One resource owns each player's active profile. Another gets
presence-owned-by-another-resource; it cannot silently overwrite or clear that profile. Inspect the getter's owner to find competing scripts. - Integrate dynamic changes into that same resource. Alternatively move both configuration and profile creation into the main gamemode and do not start the standalone resource. Do not run two competing presence resources.
- Resource stop/fault releases its profiles. Disconnect removes server state and normal game-session reset clears the client activity. Unexpected loss has a 45-second heartbeat expiry fallback.
- State is not stored in a database. Reapply it on join/resource start. The starter's elapsed time restarts on those events.
- Updates are coalesced, normally at most one per 15 seconds; rapid intermediate values may be skipped. Clears bypass the update delay. Identical activity is not repeatedly sent to Discord.
- Discord reconnection retries are bounded to 15-second intervals; handshake/acknowledgement timeouts are 10 seconds. Display timing also depends on Discord.
- Pipe work runs off the game/UI thread. The worker starts only with local opt-in and an active profile; with no profile it sleeps. Disabled clients do not start it.
- Existing Lua APIs are unchanged. Separate versioned messages are capability-negotiated; old clients ignore this extension rather than gaining support for it.
Troubleshooting and qualification
First test without images, links or counts. Verify the public Application ID, matching updated client/server, desktop Discord, activity-sharing settings, saved launcher opt-in and a fresh join. Enable the starter's status logging temporarily.
application-id-required / application-id-must-be-a-decimal-string: configure the ID in the resource doing the set/update. presence-not-set: set before updating. player-not-connected / invalid-player: check the numeric game player ID. presence-owned-by-another-resource: resolve duplicate owners. Other rejections identify an invalid field/type/range or incompatible combination.
Check actual display from another Discord account, then state changes, images, clear, quit/rejoin, resource stop/start, Discord restart and opt-out. Automated schema/Lua/IPC lifecycle tests have passed; end-to-end public Discord visibility still requires an owner-provided application and real-client testing. A local acknowledgement alone does not certify what other people see.
This feature does not implement account linking, bots, guild-role checks, Discord voice, OAuth login, automatic Join/Spectate, invitations or game-slot authentication. Do not expose an arbitrary client-controlled setter from your gamemode.
References
The native G1R:MP integration uses local desktop activity RPC. Server owners do not install the Social SDK or copy its OAuth examples. The supported fields are the G1R:MP subset listed above.